Boofuzz is a fork of and the successor to the venerable Sulley fuzzing framework.
Introduction
Comparing to AFL, a code coverage guided fuzzer aiming at binary programs, boofuzz is based on Sulley, a network protocol fuzzer. As it is for fuzzing network protocols, users should pay more attention to the details of the protocol, so an analysis of a network protocol is really needed, because it determines how users write fuzzing scripts and whether they can find specific bugs.
And:
Like Sulley, boofuzz incorporates all the critical elements of a fuzzer:
Built-in support for serial fuzzing, ethernet- and IP-layer, UDP broadcast.
Better recording of test data – consistent, thorough, clear.
Test result CSV export.
Extensible instrumentation/failure detection.
Much easier install experience!
Far fewer bugs.
Sulley is affectionately named after the giant teal and purple creature from Monsters Inc. due to his fuzziness.
Boofuzz is likewise named after the only creature known to have scared Sulley himself: Boo!
Installation
Boofuzz requires Python 2.7 or ≥ 3.5. Recommended installation requires pip. To ensure forward compatibility, Python 3 is recommended. So If you are using Python 3:
As boofuzz is based on Sulley, the usages and structures of them are almost the same. Besides, for now Sulley gets richer technical materials to search, so we get to know it before learning boofuzz.
First we get to know some words:
Words
Meanings
host
host to start fuzzing (fuzzer)
target
the fuzzing target
agents
monitor target and get traffic
request
data package to send, a request for each data message
session
graph of several data packages, like a state graph of a stateful protocol
The whole architecture is:
We check them one by one. First comes the data generation module.
As Sulley is generation-based, we need to model the protocol and file.
A primitive can be a byte, a string, a number (like integer), a checksum, a delimiter (special symbol in protocols), and other elements of a network protocol. They can be static or dynamic (to be fuzzing parameters).
A block consists of many primitives, and blocks can be nested within each another block. It represents a series of primitives.
A group (not in image, but useful) is to provide a fuzzing parameter with some candidate values (set range). It is often used with block.
A data message consists of many primitives and blocks. Except them we can customize some special primitives –> legos: Email address, IP address or so.
And some utils, like calculating length, checksum and encryption module.
Next comes the session management module.
After building the request (data message), how to combine each of them to form a state machine? Use pgraph, a Python library, to create, edit and render graph. For example:
Each node are connected to form a stateful graph, we can operate each node (consists of primitives) and define some callback functions. When it comes time to fuzz, Sulley walks the graph structure starting with the root node and fuzzing each component along the way. It fuzzes nodes one by one until a path is done, then it steps into another path.
Next is agents proxy module
The agents proxy module is for monitoring and has 3 parts: VMControl, Netmon and Procmon.
VMControl: We normally put target in virtual machine, and use this module to boot, shut or create snapshots for VM. Most important: recover host status when it crashes.
Netmon: Monitor network traffic and save pcap files in the disk. It captures 2-way traffic.
Procmon: On Windows it uses pydbg (another OpenRce project) to record program crashes and monitor the status, then write errors into crash_bin file.
There are some independent utilities like:
crashbin_explorer: Command line utility for exploring the results stored in serialized crash bin files.
The s_initialize("user") starts a new request, the s_string("USER") and s_delim(" ") define dynamic primitives (to be fuzzed), and s_static("\r\n") define static primitives.
Statements like session.connect(s_get("user")) or session.connect(s_get("user"), s_get("pass")) will set a root node or connect 2 nodes on a graph, a stateful graph like what we said in session management.
While define_proto is a newer one, just found it was updated on 2020/10/21:
# it is defined by procmon = ProcessMonitor(target_ip, 26002) procmon.set_options(start_commands=[start_cmd]) # and add it to `session` inside a `target` session = Session( target=Target( connection=TCPSocketConnection(target_ip, 21), monitors=[procmon], ), sleep_time=1, )
Besides procmon, there are netmon module and vmcontrol module that we can add to session inside target, we can see it in fuzz_trillian_jabber.py:
To fuzz network protocol, we need to choose a suitable target: vulnserver, a simple and buggy server program, so it could be a good test target, while not so realworld.
After running, it will listen port 9999.
1 2 3 4 5 6 7 8
> .\vulnserver.exe Starting vulnserver version 1.00 Called essential functiondllversion1.00
This is vulnerable software! Do not allow access from untrusted systems or networks!
Waiting for client connections...
Then we try to connect with nc, and use HELP to see valid commands:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
$ nc x.x.x.x 9999 Welcome to Vulnerable Server! Enter HELP for help. HELP Valid Commands: HELP STATS [stat_value] RTIME [rtime_value] LTIME [ltime_value] SRUN [srun_value] TRUN [trun_value] GMON [gmon_value] GDOG [gdog_value] KSTET [kstet_value] GTER [gter_value] HTER [hter_value] LTER [lter_value] KSTAN [lstan_value] EXIT
We can see that except HELP and EXIT, other commands need a parameter. We can write script to fuzz every commands:
After starting it we can see both vulnserver terminal and boofuzz script terminal are refreshing quickly. When running boofuzz script, visit http://127.0.0.1:26000/ to check the fuzzing status. ![boofuzz Fuzz Control](/imghost/fbss/boofuzz Fuzz Control.png) But we have waited for a long time and found nothing, because of the big range of our test. So we choose to fuzz only 1 command: TRUN. The script changes to be:
It will quickly crash the vulnserver.exe but script keeps running without any signals or pauses. Here we add a callback function for more detailed outputs, to help locating crashing point:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defget_banner(target, my_logger, session, *args, **kwargs): banner_template = "Welcome to Vulnerable Server! Enter HELP for help." try: banner = target.recv(10000) except: print("Unable to connect. Target is down. Exiting.") exit(1)
my_logger.log_check('Receiving banner..') if banner_template instr(banner): my_logger.log_pass('banner received') else: my_logger.log_fail('No banner received') print( "No banner received, exiting..") exit(1) # remember to add it in session session.connect(s_get("trun"), callback=get_banner)
We can even add log recording and process monitor, before it we should know how to install and use process_monitor.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
csv_log = open('fuzz_results.csv', 'wb') my_logger = [FuzzLoggerCsv(file_handle=csv_log)] session = Session( target=Target( connection = SocketConnection(host, port, proto=protocol), procmon=pedrpc.Client(host, 26002), procmon_options = { "proc_name" : "vulnserver.exe", "stop_commands" : ['wmic process where (name="vulnserver") delete'], "start_commands" : ['vulnserver.exe'], } ), fuzz_loggers=my_logger, crash_threshold_element= 1,# Crash how much times until stop )
Have to start process monitor on the target first.
defget_banner(target, my_logger, session, *args, **kwargs): banner_template = "Welcome to Vulnerable Server! Enter HELP for help." try: banner = target.recv(10000) except: print("Unable to connect. Target is down. Exiting.") exit(1)
my_logger.log_check('Receiving banner..') if banner_template instr(banner): my_logger.log_pass('banner received') else: my_logger.log_fail('No banner received') print( "No banner received, exiting..") exit(1)