Fuzz with Boofuzz (the Successor to Sulley)
TyeYeah Lv4

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:

  • Easy and quick data generation.
  • Instrumentation – AKA failure detection.
  • Target reset after failure.
  • Recording of test data.

Unlike Sulley, boofuzz also features:

  • Online documentation.
  • Support for arbitrary communications mediums.
  • 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.
Sulley from Monsters Inc
Boofuzz is likewise named after the only creature known to have scared Sulley himself: Boo!
Boo from Monsters Inc

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:

1
$ pip install boofuzz

For Python 2 the virtualenv is recommended:

1
2
3
4
5
6
7
8
9
10
$ mkdir boofuzz && cd boofuzz
$ virtualenv -p /usr/bin/python env # python 2
$ python3 -m venv env # python 3
$ source env/bin/activate
# or
> env\Scripts\activate.bat # for windows
(env) $ pip install -U pip setuptools
(env) $ pip install boofuzz
# actions
$ deactive

From source

1
2
3
$ git clone https://github.com/jtpereyda/boofuzz.git
$ cd boofuzz
$ pip install .

Intro to Sulley

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:
architecture
We check them one by one. First comes the data generation module.
data generation (to build request)
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.
session management
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:
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
agents
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:
architecture

  • crashbin_explorer: Command line utility for exploring the results stored in serialized crash bin files.
  • … (others get no comments)

So the whole work flow of Sulley is:

  1. Data message modeling
  2. Connect to form state machine
  3. Other modules assist fuzzing

See the SulleyManual for some other details.

Usages of Boofuzz

It is almost the same as Sulley so if you want to master this, read the SulleyManual carefully.

Here we learn by examples given in boofuzz/examples/.

1
2
3
4
5
6
7
8
$ cd boofuzz/examples/
$ ls
ftp_simple.py fuzz_trillian_jabber.py
ftp_with_procmon.py http_simple.py
fuzz_ssl_client.py http_with_body.py
fuzz_ssl_server.py iso8385.py
fuzz_trend_control_manager_20901.py mdns.py
fuzz_trend_server_protect_5168.py tftp_simple.py

From ftp_simple.py we can see there are 2 styles to build requests: the func define_proto_static and the func define_proto (newer).

In Sulley we used to use is this style:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def define_proto_static(session):
"""Same protocol, using the static definition style."""
s_initialize("user")
s_string("USER")
s_delim(" ")
s_string("anonymous")
s_static("\r\n")

s_initialize("pass")
s_string("PASS")
s_delim(" ")
s_string("james")
s_static("\r\n")

s_initialize("stor")
s_string("STOR")
s_delim(" ")
s_string("AAAA")
s_static("\r\n")

s_initialize("retr")
s_string("RETR")
s_delim(" ")
s_string("AAAA")
s_static("\r\n")

session.connect(s_get("user"))
session.connect(s_get("user"), s_get("pass"))
session.connect(s_get("pass"), s_get("stor"))
session.connect(s_get("pass"), s_get("retr"))

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def define_proto(session):
# disable Black formatting to keep custom indentation
# fmt: off
user = Request("user", children=(
String("key", "USER"),
Delim("space", " "),
String("val", "anonymous"),
Static("end", "\r\n"),
))

passw = Request("pass", children=(
String("key", "PASS"),
Delim("space", " "),
String("val", "james"),
Static("end", "\r\n"),
))

stor = Request("stor", children=(
String("key", "STOR"),
Delim("space", " "),
String("val", "AAAA"),
Static("end", "\r\n"),
))

retr = Request("retr", children=(
String("key", "RETR"),
Delim("space", " "),
String("val", "AAAA"),
Static("end", "\r\n"),
))
# fmt: on

session.connect(user)
session.connect(user, passw)
session.connect(passw, stor)
session.connect(passw, retr)

It implements a new and concise method to represent elements in the protocol.

Next we check ftp_with_procmon.py to learn procmon module:

1
2
3
4
5
6
7
8
9
10
11
# 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
def init_message(sock):
init = '<?xml version="1.0" encoding="UTF-8" ?>\n'
init += '<stream:stream to="152.67.137.126" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams">'

sock.send(init)
sock.recv(1024)


sess = sessions.Session(session_filename="audits/trillian.session")
target = sessions.Target(connection=TCPSocketConnection("152.67.137.126", 5298))
target.netmon = pedrpc.Client("152.67.137.126", 26001)
target.procmon = pedrpc.Client("152.67.137.126", 26002)
target.vmcontrol = pedrpc.Client("127.0.0.1", 26003)
target.procmon_options = {"proc_name": "trillian.exe"}

# start up the target.
target.vmcontrol.restart_target()
print("virtual machine up and running")

sess.add_target(target)
sess.pre_send = init_message
sess.connect(sess.root, s_get("chat message"))
sess.fuzz()

Attention: monitor=... is the new method, while procmon=... is deprecated.

The fuzz_ssl_client.py and fuzz_ssl_server.py are about SSL/TLS communacations, communate with certificate.

As for fuzz_trend_control_manager_20901.py and fuzz_trend_server_protect_5168.py, they are just unfinished samples.

Another great example is http_simple.py (perhaps with http_with_body.py). It shows us how to use block and group.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def define_proto(session):
# disable Black formatting to keep custom indentation
# fmt: off
req = Request("HTTP-Request", children=(
Block("Request-Line", children=(
Group("Method", values=["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"]),
Delim("space-1", " "),
String("URI", "/index.html"),
Delim("space-2", " "),
String("HTTP-Version", "HTTP/1.1"),
Static("CRLF", "\r\n"),
)),
Block("Host-Line", children=(
String("Host-Key", "Host:"),
Delim("space", " "),
String("Host-Value", "example.com"),
Static("CRLF", "\r\n"),
)),
Static("CRLF", "\r\n"),
))
# fmt: on

session.connect(req)


def define_proto_static(session):
s_initialize(name="Request")
with s_block("Request-Line"):
s_group("Method", ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"])
s_delim(" ", name="space-1")
s_string("/index.html", name="Request-URI")
s_delim(" ", name="space-2")
s_string("HTTP/1.1", name="HTTP-Version")
s_static("\r\n", name="Request-Line-CRLF")
s_string("Host:", name="Host-Line")
s_delim(" ", name="space-3")
s_string("example.com", name="Host-Line-Value")
s_static("\r\n", name="Host-Line-CRLF")
s_static("\r\n", "Request-CRLF")

session.connect(s_get("Request"))

A block combines several primitives which change values randomly, however a group provides the range of values to a variable.

Then we move to iso8385.py, it provides us with s_random which defines the length of a primitive to change:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s_random(
"30 33 32 37 49 53 4F 37 30 31 30 30 30 30 30 31 31 31 30 F6 F3 00 21 8E E1 A0 08 00 00 00 00 00 00 00 01 31 "
"36 34 32 36 30 30 30 30 30 30 31 35 31 30 33 33 35 31 37 30 30 30 30 30 30 30 30 30 30 30 30 31 30 30 30 30 "
"30 30 30 30 30 30 30 31 30 30 30 31 37 30 39 32 37 32 31 35 33 30 30 30 30 30 30 30 31 30 30 30 30 30 30 30 "
"31 30 30 30 39 32 37 31 39 31 31 31 35 31 32 30 30 30 30 31 36 30 34 30 31 31 31 32 31 36 30 36 35 30 30 30 "
"32 30 30 36 30 30 30 30 30 31 30 32 30 36 38 37 30 31 39 31 31 39 31 35 38 34 30 31 39 30 39 32 30 30 31 30 "
"30 30 33 32 30 30 30 30 30 31 35 30 30 30 30 30 30 33 34 30 42 41 4E 41 4E 41 20 52 45 50 55 42 4C 49 43 20 "
"20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 54 59 20 50 52 30 36 32 50 38 37 30 30 31 34 50 32 "
"35 30 30 31 33 50 38 38 30 30 31 34 50 35 34 30 30 31 52 50 39 35 30 30 32 30 31 50 36 38 30 32 30 30 33 30 "
"30 30 39 32 37 30 32 30 36 38 37 30 31 39 31 31 39 38 34 30 38 34 30 30 30 39 30 33 39 30 30 33 39 30 39 36 "
"30 43 30 46 31 31 39",
min_length=331,
max_length=350,
fuzzable=True,
num_mutations=500,
)

And mdns.py provides us with s_word, s_size and s_repeat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ######## Queries ################
if s_block_start("query"):
if s_block_start("name_chunk"):
s_size("string", length=1)
if s_block_start("string"):
s_string("A" * 10)
s_block_end()
s_block_end()
s_repeat("name_chunk", min_reps=2, max_reps=4, step=1, fuzzable=True, name="aName")

s_group("end", values=["\x00", "\xc0\xb0"]) # very limited pointer fuzzing
s_word(0xC, name="Type", endian=">")
s_word(0x8001, name="Class", endian=">")
s_block_end()
s_repeat("query", 0, 1000, 40, name="queries")

More important, it teaches us how to write callback function:

1
2
3
4
5
6
7
8
9
10
...
def insert_questions(target, fuzz_data_logger, session, node, edge, *args, **kwargs):
node.names["Questions"].value = 1 + node.names["queries"].current_reps
node.names["Authority"].value = 1 + node.names["auth_nameservers"].current_reps

...

sess = Session(target=Target(connection=UDPSocketConnection("224.0.0.251", 5353)))
sess.connect(s_get("query"), callback=insert_questions)
...

Examples

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 function dll version 1.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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from boofuzz import *

def main():
port = 9999
host = 'x.x.x.x'
protocol = 'tcp'

session = Session(
target=Target(
connection = SocketConnection(host, port, proto=protocol),
),
)

s_initialize("fuzzall")
s_group("verbs", values=["STATS", "RTIME", "LTIME", "SRUN", "TRUN", "GMON", "GDOG", "KSTET", "GTER", "HTER", "LTER", "KSTAN"])
if s_block_start("body", group="verbs"):
s_delim(" ", fuzzable=False)
s_string("FUZZ")
s_static("\r\n")
s_block_end()

session.connect(s_get("fuzzall"))
session.fuzz()

if __name__ == "__main__":
main()

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from boofuzz import *

def main():
port = 9999
host = 'x.x.x.x'
protocol = 'tcp'

session = Session(
target=Target(
connection = SocketConnection(host, port, proto=protocol),
),
)

s_initialize("trun")
s_string("TRUN", fuzzable=False)
s_delim(" ", fuzzable=False)
s_string("FUZZ")
s_static("\r\n")

session.connect(s_get("trun"))
session.fuzz()

if __name__ == "__main__":
main()

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
def get_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 in str(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.

1
2
> python process_monitor.py
$ python process_monitor_unix.py

So our final script comes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from boofuzz import *

def main():
port = 9999
host = '192.168.100.79'
protocol = 'tcp'

s_initialize("trun")
s_group("verbs", values=["TRUN","GMON","KSTET"])
# s_group("verbs", values=["STATS", "RTIME", "LTIME", "SRUN", "TRUN", "GMON", "GDOG", "KSTET", "GTER", "HTER", "LTER", "KSTAN"])
if s_block_start("body", group="verbs"):
s_delim(" ", fuzzable=False)
s_string("FUZZ")
s_static("\r\n")
s_block_end()

csv_log = open('fuzz_results.csv', 'wb')
my_logger = [FuzzLoggerCsv(file_handle=csv_log)]
print('------------------------------------------------')
print(type(my_logger))
print(dir(my_logger))
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,
# Well, this statement doesn't work fine on Python3
# Perhaps it is deprecated, or perhaps I wrongly used it
# Need you guys help !!!
crash_threshold_element= 1,# Crash how much times until stop
)

session.connect(s_get("trun"), callback=get_banner)
session.fuzz()

def get_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 in str(banner):
my_logger.log_pass('banner received')
else:
my_logger.log_fail('No banner received')
print( "No banner received, exiting..")
exit(1)

if __name__ == "__main__":
main()

Boofuzz – A helpful guide (OSCE – CTP) is the reference of the codes.

Powered by Hexo & Theme Keep
Total words 135.7k