boofuzz-网络协议模糊测试器
boofuzz
jtpereyda/boofuzz: A fork and successor of the Sulley Fuzzing Framework (github.com)
boofuzz是Sulley框架的继承者,修复了Sulley中存在的bug,并增强了可扩展性
boofuzz的特点
安装简单
pip install boofuzz
易于使用,基于Python脚本编写协议,不需要为协议编写复杂的配置文件
扩展性强,利用callback机制能够方便地实现各种操作
详细的测试记录
boofuzz的缺点
性能较差(相同的benchmark效率约为peach的40%)
缺乏易用的教程文档
安装使用
boofuzz为纯Python项目,使用pip即可直接安装
1 |
|
以项目自带的样例http_simple为例
fuzz目标及会话的创建
1 |
|
http协议请求的定义,boofuzz存在两种协议定义的方式,静态定义和新型定义
静态定义
1 |
|
新型定义
1 |
|
新型定义方式更加地灵活,并且可以通过类的基础、重写实现更多的自定义功能,因此后续只介绍新型协议定义
将定义的请求添加到会话中
1 |
|
使用python拉起一个http服务器
1 |
|
开始模糊测试
1 |
|
运行脚本,boofuzz自动监听26000端口,使用浏览器连接即可查看fuzz日志
boofuzz的完整运行日志保存在boofuzz-results
目录下,使用boo工具打开日志文件即可在web中查看详细的日志记录
1 |
|
boofuzz使用
协议定义
以新型的定义方式为主进行介绍
Request 可以看作是要发送的消息,而 Blocks 则可视为消息内的块, Primitives 则是构成 Block/Request 的元素,这些元素可以是字节、字符串、数字、checksums。
Request
Request为顶级容器,定义一次完整的请求,可包含任何block结构或者primitive结构
1 |
|
Request对象包含names
stack
属性
names
为Request对象包含的所有对象及子对象的字典,以名称为索引可以得到每一个对象
1 |
|
stack
为Request对象的栈结构,按入栈顺序储存包含的对象,不包括子对象
1 |
|
Block
Block
Block是基本的构造块,可包含 primitives(原语)、sizers、checksums 以及其它 blocks。
1
2
3
4
5
6
7
8
9block = Block(name="block1", children=(
String(name="string", default_value="str"),
Delim(name="delim", default_value=":", fuzzable=False),
Block(name="block2", children=(
String(name="string", default_value="num"),
Delim(name="delim", default_value=":", fuzzable=False),
DWord(name="dword", default_value=123, endian='>'),
)),
))与Request相似,Block也存在
stack
属性,但不存在names
属性1
2>>> block.stack
[<String string 'str'>, <Delim delim ':'>, <Block block2 None>]Checksum
Checksum顾名思义,即校验和,计算目标对象的校验和
1
2
3
4
5
6
7
8message2 = Request(name="message", children=(
Checksum(name="checksum", block_name="block", algorithm="crc32", length=4, endian='<', fuzzable=False),
Block(name="block", children=(
String(name="string", default_value="num"),
Delim(name="delim", default_value=":", fuzzable=False),
DWord(name="dword", default_value=123, endian='>'),
)),
))block_name
参数为需要校验的对象的name
,校验的对象可以为Request Block或Primitivesalgorithm
参数为校验和算法,默认为crc32
算法,可设置为crc32, crc32c, adler32, md5, sha1, ipv4, udp
length
校验和长度,默认4字节endian
大小端,默认为<
小端fuzzable
必须手动设置为False
,否则会对校验和进行变异Repeat
Repeat重复渲染其指向的对象,重复的次数可随机变异,也可以由特定的整数对象指定
Repeat对象需要定义在其需要重复的对象后面(实际上不需要)测试中发现Repeat的行为与预期表现不符,无法通过设定
variable
参数实现指定的随机次数例如存在如下结构体定义:
1
2
3
4struct msg_struct {
uint16_t item_num;
int32_t block[item_num] = {value, value, value, ...};
};按照API文档,定义对应的Request如下:
1
2
3
4
5
6
7message = Request(name="msg_struct", children=(
Word(name="item_num", default_value=1, max_num=20, signed=False),
Block(name="block", children=(
DWord(name="value", default_value=0)
)),
Repeat(name="repeat", block_name="block", variable="msg_struct.item_num"),
))但是在渲染时出现如下报错
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
28Traceback (most recent call last):
File "/workspace/boofuzz/test-boofuzz.py", line 156, in <module>
session.fuzz()
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/sessions/session.py", line 957, in fuzz
self._main_fuzz_loop(self._generate_mutations_indefinitely(max_depth=max_depth))
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/sessions/session.py", line 1085, in _main_fuzz_loop
self._fuzz_current_case(mutation_context)
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/sessions/session.py", line 1452, in _fuzz_current_case
self.transmit_fuzz(
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/sessions/session.py", line 859, in transmit_fuzz
data = self.fuzz_node.render(mutation_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/blocks/request.py", line 128, in render
return self.get_child_data(mutation_context=mutation_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/fuzzable_block.py", line 71, in get_child_data
rendered += item.render(mutation_context=mutation_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/fuzzable.py", line 153, in render
return self.encode(value=self.get_value(mutation_context=mutation_context), mutation_context=mutation_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/fuzzable.py", line 178, in get_value
value = self.original_value(test_case_context=mutation_context.protocol_session)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/workspace/venv/lib/python3.11/site-packages/boofuzz/fuzzable.py", line 120, in original_value
return test_case_context.session_variables[self._default_value.name]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: 'msg_struct.item_num'调试发现
session_variables
始终为空,无法通过该方式使用Repeat类型实现预期效果通过对原类型的继承和重写,可以使用自定义的方式实现需要的重复效果,如下代码示例为对
Word
和Repeat
的简单修改,以实现预期的重复效果WordRepeater
继承自Word
,增加了repeater
属性,用于记录变异后实际的重复次数FixRepeat
继承自Repeat
,修改了original_value
方法,通过repeater
属性决定重复的次数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class WordRepeater(Word):
def __init__(self, name=None, default_value=0, *args, **kwargs):
self.repeater = default_value
super(WordRepeater, self).__init__(name, default_value, *args, **kwargs)
def encode(self, value, mutation_context):
self.repeater = value
return super(WordRepeater, self).encode(value, mutation_context)
class FixRepeat(Repeat):
def original_value(self, test_case_context=None):
if isinstance(self._default_value, ProtocolSessionReference):
if test_case_context is None:
return self._default_value.default_value
else:
value = test_case_context.current_message.names[self._default_value.name].repeater
if (value < 1):
value = 1
return value - 1
else:
return self._default_value修改后的消息定义如下:
1
2
3
4
5
6
7message = Request(name="msg_struct", children=(
WordRepeater(name="item_num", default_value=1, max_num=20, signed=False),
Block(name="block", children=(
DWord(name="value", default_value=0)
)),
FixRepeat(name="repeat", block_name="block", variable="msg_struct.item_num"),
))测试效果满足预期,但仍存在问题,重复次数无法为0,至少包含1个元素
1
2
3Test Step: Fuzzing Node 'msg_struct'
Info: Sending 30 bytes...
Transmitted 30 bytes: 07 00 01 00 00 08 01 00 00 08 01 00 00 08 01 00 00 08 01 00 00 08 01 00 00 08 01 00 00 08Size
Size计算目标对象的大小,使用方法与Checksum相同,
block_name
指向需要计算大小的对象的name
fuzzable
需要设置为False
Size包含特殊参数
math
,用于指定长度计算算法,可以使用函数或lambda表达式如下示例,
size
用于计算block
长度,指定长度算法使计算得到的长度为block
长度加CRLF
的长度1
2
3
4
5
6
7
8
9
10
11size_math = lambda x: (x + 2)
message = Request(name="message", children=(
Size(name="size", block_name="block", signed=False, math=size_math, fuzzable=False),
Block(name="block", children=(
DWord(name="value", default_value=0),
Delim(name="delim", default_value=";", fuzzable=False),
String(name="str"),
)),
Static(name="CRLF", default_value=b"\n\r"),
))Aligned
Aligned类似于Block,可以包含子对象,与Block不同的是,会对子对象编码后进行填充对其
1
2
3
4
5
6
7message = Request(name="message", children=(
Aligned(name="align", modulus=4, pattern=b'\x00', children=(
String(name="string", default_value="num", max_len=128),
Delim(name="delim", default_value=":", fuzzable=False),
DWord(name="dword", default_value=123, endian='>'),
)),
))modulus
参数为对其的单位,默认为1,即不对其pattern
参数是用于填充对其的数据,默认为一个空字符children
参数是其包含的子对象
Primitives
Primitive原语类型是boofuzz的基本数据类型,包含整数、字符串等多种类型
Static
静态类型,在模糊测试变异过程中不会发生变异,数据由
default_value
指定,可以为str类型或bytes类型1
Static(name="CRLF", default_value="\r\n")
Simple
Simple从提供的bytes列表进行变异,利用
fuzz_values
列表中的值对默认值进行替换不提供
fuzz_values
参数时,等价与Static
类型1
Simple(name="simple", default_value=b"11", fuzz_values=[b"11", b"22", b"33"])
Delim
分隔符类型,包含空格、回车、换行等多种字符,变异过程对分隔符进行替换、重复或缺少
1
Delim(name="delim", default_value=";")
Group
Group类型与Simple类型类似,利用提供的数据对默认数据进行替换,但是Group类型提供的数据为str类型,可指定编码方式
1
Group(name="Method", values=["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"])
RandomData
随机类型,变异的值随机,与提供的
default_value
无关,变异数据的长度由min_length
和max_length
参数提供,变异的次数由max_mutations
参数提供1
RandomData(name="rand", min_length=4, max_length=10, max_mutations=100)
String
字符串类型,从boofuzz定义好的字符串库进行模糊测试,包含替换、重复等变异方式
1
2String(name="str1", default_value="hello", size=16, padding=b"_")
String(name="str2", default_value="world", max_len=16, encoding="utf-8")size
固定生成数据的长度,不提供该参数表示动态长度padding
用于填充数据长度到固定长度,与size
参数结合使用max_len
生成数据的最大长度encoding
字符串编码方式FromFile
从文件加载变异语料库,将文件按行分隔,每行内容作为变异的内容进行替换
1
FromFile(name="file", default_value=b"test", filename="input.txt", max_len=64)
filename
需要加载的目标文件max_len
每行内容的最大长度,大于该长度的内容不参与变异Mirror
镜像类型,输出与其指向的对象相同的值,
primitive_name
指向需要镜像的对象的名称1
2DWord(name="value", default_value=0)
Mirror(name="mirror", primitive_name="value")BitField
比特类型,boofuzz中最基础的整数类型,后序的
Byte
Word
DWord
QWord
类型与BitField类型类似,只是固定了比特宽度参数,不单独进行介绍1
2BitField(name="bit", default_value=0, width=16, max_num=1000, endian='>',
signed=False, full_range=True, fuzz_values=[2000, 3000, 10000])default_value
默认值,默认为0width
比特宽度,默认为8,1字节max_num
变异的最大值endian
大小端>
<
output_format
输出格式,默认为binary
,可替换为ascii
,ascii
格式每字节的最高bit位为0signed
有符号数,默认为False
,即默认无符号full_range
变异范围,为True
时将在变异所有max_num
范围内的数据fuzz_values
额外提供的变异值列表Bytes
字节流类型,与
String
类型类似,区别是Bytes
类型为字节流,不需要提供编码方式1
2Bytes(name="bytes1", default_value=b"123", size=128, padding=b"\xff")
Bytes(name="bytes2", default_value=b"123", max_len=128)
自定义类型
boofuzz使用纯Python实现,利用类继承机制可以很轻松地实现自定义类型,例如在Repeat
部分实现的FixRepeat
和WordRepeater
类型
1 |
|
通过继承原类型,可以为基础类型如Fuzzable
或FuzzableBlock
,也可以为Word
Size
等类型,重写mutations
方法实现自定义的变异,重写encode
方法实现自定义编码,重写render
方法实现自定义输出渲染方式
Protocol Definition — boofuzz 0.4.2 documentation
连接 目标 会话
Connections
与被测目标的连接对象,基类为ITargetConnection
,包含open/close
send/recv
方法,boofuzz包含如下的连接方式
除了上面的几种连接方式外,也可以通过实现open/close
send/recv
方法实现与任意目标的连接交互
Target
被测试的目标对象
1 |
|
connection
测试目标的连接对象
monitors
监视器对象列表
monitor_alive
监视器活跃时调用的函数列表,以活跃的监视器为参数
repeater
发送时使用的重复器,用于重复多次发送同一条数据
Session
会话为协议交互的构造提供了一个容器,常用参数如下
sleep_time
测试用例之间等待的秒数
pre_send_callbacks
每个测试请求前调用的注册函数列表
post_test_case_callbacks
每个测试用例之后调用的注册函数列表
post_start_target_callbacks
目标启动或重新启动后进程监视器调用的注册函数列表
web_port
监听的Web端口,默认为26000,设置为None禁用Web界面、
web_address
监听的Web地址,默认为localhost
receive_data_after_fuzz
传输完fuzzed消息后尝试接收响应
target
会话的目标
常用方法
connect
1
connect(src, dst=None, callback=None)
在两个请求节点键创建一个连接并注册一个回调函数用于处理源请求与目的请求之间的传输过程
Session 类维持着一个顶级节点(根节点),所有的 requests 初始时都必须连接到该节点
1
2session = Session()
session.connect(session.root, req)如果仅指定src节点,则src节点将默认与根节点连接
1
session.connect(req)
回调函数在src节点请求完成后,dst请求发送前执行(在仅指定src节点时,可视为root为src节点,实际添加的节点为dst节点,因此回调函数在添加的请求前执行)
回调函数示例如下,
session.last_send
session.last_recv
分为别会话前一次发送接收的数据,回调函数存在返回值时,其返回值将作为下一个请求发送的数据1
2
3
4def req_callback(target:Target, fuzz_data_logger, session:Session, test_case_context, *args, **kwargs):
print(session.last_send)
print(session.last_recv)
return b'1111111111111111'fuzz
对整个协议树开始模糊测试
render_graph_graphviz
将会话的请求节点及节点间关系渲染为图形
1
2with open('somefile.png', 'wb') as file:
file.write(session.render_graph_graphviz().create_png())例如:
1
2
3
4
5
6
7
8
9
10session.connect(req1)
session.connect(req4)
session.connect(req1, req2)
session.connect(req1, req3)
session.connect(req4, req2)
session.connect(req2, req5)
session.connect(req5, req3)
session.connect(req1, req6)
session.connect(req6, req7)
session.co
监视器
Monitors 就是监视目标特定行为的组件,一般称之为监视器。监视器可以是被动的,也可以是主动的。 被动的意思是仅对目标进行观察并提供一些数据,而主动的意思则是表明监视器的行为更加的积极,比如直接和目标进行交互等。 更进一步地,有些监视器甚至具有启动、停止、重启目标的能力。
监测目标的崩溃或异常行为可能是一件复杂的事情,主要取决于在目标系统上有哪些可用的工具。 比如对嵌入式设备来说,在其上通常没有现成的能够监测崩溃/异常的工具。
Boofuzz 主要提供了三种监视器实现类:
ProcessMonitor
:从 Windows 和 Unix 进程中收集调试信息的监视器类。该类也可以重启目标进程以及监测段错误。NetworkMonitor
:通过 PCAP 被动地捕获网络流量并将其写入测试用例日志中的监视器类。CallbackMonitor
:用于实现回调函数的监视器类,可以传递给 Session 类。
ProcessMonitor
ProcessMonitor进程监视器,boofuzz测试端作为RPC客户端,所有的命令转发到RPC服务器执行,被测目标需要运行RPC服务器,接收RPC命令执行相应的操作
RPC服务器部分可以通过继承pedrpc.Server
和BaseMonitor
实现,此外还需要添加set_start_commands
set_stop_commands
start_target
stop_target
等方法,使用较为复杂
NetworkMonitor
NetworkMonitor与ProcessMonitor几乎相同,只是删减了部分方法,仍需要自行实现RPC服务器
CallbackMonitor
CallbackMonitor回调监视器用于在fuzz的特定阶段执行指定任务,有如下4个阶段回调
restart_callbacks –> target_restart
pre_send_callbacks –> pre_send
post_test_case_callbacks –> post_send
post_start_target_callbacks –> post_start_target
通过设置特定的回调,能够方便地在测试的过程中实现原本复杂的功能,如测试环境准备、测试环境清理、接口认证、中断连接等
如下示例使用了pred_callback
和post_callback
功能,在测试开始前和结束后与被测目标进行通信
1 |
|
参考连接
jtpereyda/boofuzz: A fork and successor of the Sulley Fuzzing Framework (github.com)
boofuzz: Network Protocol Fuzzing for Humans — boofuzz 0.4.2 documentation