文章目录
1. 简介
Boofuzz is a fork of and the successor to the venerable Sulley fuzzing framework. Besides numerous bug fixes, boofuzz aims for extensibility. The goal: fuzz everything.
安装
建议使用python虚拟环境。
# sudo apt-get install python3-venv # linux上要安装venv模块
python -m venv env
cd env
.\Scripts\Activate.ps1 # 我用的powershell
# source bin/activate # linux
pip install -U pip setuptools
pip install boofuzz
pip install pcapy impacket
# 或者下载源码执行以下命令安装
# pip install -e .[dev]
因为github源码里有很多示例,并且可以以开发模式安装,所以建议源码安装。
monitors目录下的两个关键文件:
- process_monitor.py,运行在目标机器上;
- network_monitor.py,需要安装依赖
pip install pcapy impacket
;
后续我会用vscode编辑,所以ctrl+shift+p选择python解释器为虚拟环境中的scripts/python.exe。
2. Quickstart
Quickstart — boofuzz 0.4.1 documentation
注释摘自文档。
from boofuzz import *
# There are many kinds of connections that Inherit from BaseSocketConnection, implementing ITargetConnection.
session = Session(
target=Target(
connection=TCPSocketConnection("127.0.0.1", 8021)))
# Each message is a Request object, whose children define the structure for that message.
# Doc: https://boofuzz.readthedocs.io/en/stable/user/protocol-definition.html#protocol-definition
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"),
))
# connect them into a graph
session.connect(user)
session.connect(user, passw)
session.connect(passw, stor)
session.connect(passw, retr)
Gtihub上提供的其它示例:boofuzz/examples at master · jtpereyda/boofuzz (github.com)
需要掌握请求的构造,也有一些示例:boofuzz/request_definitions at master · jtpereyda/boofuzz (github.com)
Fuzz结果会保存在boofuzz-results目录下在sqlite数据库里:
boo open <run-*.db>
响应回调和引用回调数据请参考:
Session.post_test_case_callbacks(list of method)
:- Other Modules — boofuzz 0.4.1 documentation
# https://boofuzz.readthedocs.io/en/stable/_modules/boofuzz/protocol_session.html?highlight=post_test_case_callbacks%20
import attr
[docs]@attr.s
class ProtocolSession(object):
"""Contains a ``session_variables`` dictionary used to store data specific to a single fuzzing test case.
Generally, values in ``session_variables`` will be set in a callback function, e.g. ``post_test_case_callbacks``
(see :class:`Session <boofuzz.Session>`). Variables may be used in a later callback function, or by a
:class:`ProtocolSessionReference <boofuzz.ProtocolSessionReference>` object.
"""
session_variables = attr.ib(factory=dict)
previous_message = attr.ib(default=None)
current_message = attr.ib(default=None)
3. Session, Target, Connections
大概使用方法参考Quickstart.
3.1 Session
Session继承自pgraph.Graph,也就是说会话其实是一个图,由许多节点构成。函数及成员还蛮多的,具体参考注释或文档。
执行fuzz()开始测试后,会自动启动web服务,默认localhost:26000,可以通过查找listen修改。
需要提一下connect方法,这个方法会连接两个节点:
connect(src, dst=None, callback=None) # ->pgraph.Edge
Session.py提供了回调函数的原型:
def example_test_case_callback(self, target, fuzz_data_logger, session, test_case_context, *args, **kwargs)
这个回调还需要再研究一下~
3.2 Target
Available options include:
TCPSocketConnection
UDPSocketConnection
SSLSocketConnection
RawL2SocketConnection
RawL3SocketConnection
SocketConnection (depreciated)
SerialConnection
3.3 Connections
这些类继承自BaseSocketConnection, 实现ITargetConnection接口类
4. Monitors
3个主要的监视器:
- CallbackMonitor
- NetworkMonitor
- ProcessMonitor
5. Logging
有这么几种日志类:
- FuzzLoggerText
- FuzzLoggerCsv
- FuzzLoggerCurses,用于gui显示;
- FuzzLogger,可以理解为logManager.
如果是调试,可以用Request.walk输出定义的协议内容:
logger = FuzzLoggerText();
reqGen = blocks.CURRENT.walk()
# req = Request()
# reqGen = req.walk()
for block in reqGen:
logger.log_info(block);
这个walk函数使用yield返回了一个可迭代对象,这些block的父类Fuzzable是可迭代的:
def __repr__(self):
return "<%s %s %s>" % (
self.__class__.__name__,
self.name,
repr(self.original_value(test_case_context=None)),
)
6. Protocol Definition
下面这句话很重要,参照示例多读几遍理解一下:
Requests are messages, Blocks are chunks within a message, and Primitives are the elements (bytes, strings, numbers, checksums, etc.) that make up a Block/Request.
# children (boofuzz.Fuzzable, optional)
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"),
))
Block, Primitives这些类都是(直接/间接)继承自Fuzzable父类,建议读一下这个类的源码和文档。
Blocks and Primitives
可参考源码blocks目录。用法参考官网。
有以下几个类:
- Block, The basic building block. Can contain primitives, sizers, checksums or other blocks.
- Checksum
- Repeat
- Size
- Aligned
原语有很多, 可参考源码primitives目录:
- Static: Static primitives are fixed and not mutated while fuzzing.
- Simple: Simple bytes value with manually specified fuzz values only.
- Delim: 空格、换行等分隔符.
- Group
- RandomData
- FromFile
- BitField
- Byte/Word…
自定义块/原语的步骤:
- Create an object that inherits from
Fuzzable
orFuzzableBlock
- Override
mutations
and/orencode
. - Optional: Create an accompanying static primitive function. See boofuzz’s init.py file for examples.
- ???
- Profit!
再次强调一下,需要熟悉Fuzzable、 FuzzableBlock这两个类,理解mutations()
、 num_mutations()
和 encode()
等需要实现的方法。
7. Static Protocol Definition
语法参考自Spike protocol fuzzer(kali上自带,源码在网上没找到)。基于数据块的fuzz理论也是spike的特点,并被各种fuzz工具沿用。在数据结构中,尤其是协议,结构与结构直接是有关联的,比如有的字段会表示其它结构的大小、校验值,而spike这种基于数据块的fuzz设计,可以通过设置size、checksum字段,实现函数与结构字段的绑定。
这一部分的函数和spike函数几乎一样,定义位于根目录的__init__.py
。
7.1 Request Manipulation
源码寻找REQUEST MANAGEMENT
这句注释即可。
s_initialize(name)
s_get(name=None)
s_num_mutations()
s_switch(name)
提一下s_switch。程序blocks模块里有一个全局指针CURRENT指向当前的Request,所有用s_initialize初始化的Request都保存在一个全局字典里,用s_switch可以改变CURRENT。
7.2 Block Manipulation
源码搜索BLOCK MANAGEMENT
这句注释。
s_block() # 给当前请求增加block,内部封装了s_block_start(), s_block_end(), 相关数据结构Request.block_stack
s_block_start() # 建议使用封装过的s_block + with语句
s_checksum()
s_repeat() # 测试溢出很有用
s_size()
s_update() # Update the value of the named primitive in the currently open request.
7.3 Primitive Definition
源码搜索PRIMITIVES
这句注释。这部分的函数用来给block增加元素,比如s_binary(), s_group(),s_byte(),,,
比较多,就不列举了。
8. 其它模块
比如dcerpc, CrashBinning, EventHook, 可以参考源码根目录、utils目录等,这一部分以后慢慢补充。
根目录__init__.py
里除了各种3种定义函数,还有s_hex_dump(),可以用来输出可迭代对象(实现了__repr__
方法的类)数据。
Class ProtocolSession, 可作为Session.connect回调函数的参数。
9. Http Examples
路径位于examples/http_simple.py
。
但在看示例之前,先看下request_definitions/http*.py
这几个请求定义示例。
9.1 http request_definitions
如果是使用pip install .[dev]
安装,那执行时会报错:找不到s_initialize,可以把py复制到源码同级目录执行:
test.py
boofuzz-master
结尾添加[日志部分](#5. Logging)提到的代码,即可查看数据结构内容。
9.2 http fuzz
测试目标使用tinyhttpd:
# 开启崩溃转储
$ ulimit -c unlimited && sudo bash -c 'echo %e.core.%p > /proc/sys/kernel/core_pattern'
$ ./httpd
httpd running on port 36245
修改Session的目标连接端口:
session = Session(
target=Target(connection=TCPSocketConnection("127.0.0.1", 36245)),
)
启动http_simple.py开始fuzz:
$ python test.py
[2022-04-26 16:46:07,394] Info: Web interface can be found at http://192.168.56.101:36246
[2022-04-26 16:46:07,407] Test Case: 1: HTTP-Request:[HTTP-Request.Request-Line.Method:0]
[2022-04-26 16:46:07,407] Info: Type: Group
[2022-04-26 16:46:07,407] Info: Opening target connection (127.0.0.1:36245)...
[2022-04-26 16:46:07,408] Info: Connection opened.
[2022-04-26 16:46:07,408] Test Step: Monitor CallbackMonitor#139757745574240[pre=[],post=[],restart=[],post_start_target=[]].pre_send()
[2022-04-26 16:46:07,408] Test Step: Fuzzing Node 'HTTP-Request'
[2022-04-26 16:46:07,408] Info: Sending 48 bytes...
[2022-04-26 16:46:07,408] Transmitted 48 bytes: 48 45 41 44 20 2f 69 6e 64 65 78 2e 68 74 6d 6c 20 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 65 78 61 6d 70 6c 65 2e 63 6f 6d 0d 0a 0d 0a b'HEAD /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n'
[2022-04-26 16:46:07,409] Test Step: Contact target monitors
[2022-04-26 16:46:07,409] Test Step: Cleaning up connections from callbacks
[2022-04-26 16:46:07,409] Check OK: No crash detected.
[2022-04-26 16:46:07,409] Info: Closing target connection...
[2022-04-26 16:46:07,409] Info: Connection closed.
[2022-04-26 16:46:07,412] Test Case: 2: HTTP-Request:[HTTP-Request.Request-Line.Method:1]
[2022-04-26 16:46:07,412] Info: Type: Group
[2022-04-26 16:46:07,412] Info: Opening target connection (127.0.0.1:36245)...
httpd马上退出了,但没有崩溃,因为没有转储生成。
访问下浏览器http://192.168.56.101:36246/可以看到测试界面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JfOs2bmU-1650970027247)(boofuzz.assets/boofuzz-web.png)]
vscode调试httpd,再来一次,发现是httpd没有实现head请求,导致正常退出了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cbase4Ty-1650970027248)(boofuzz.assets/httpd-unimplemented.png)]
于是,修改一下method字段(本来是遍历group里的value):
# Group(name="Method", values=["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"]),
String(name="Method", default_value="GET", Fuzzable=False),
再试一次,发生了SIGPIPE异常:
Thread 2 "httpd" received signal SIGPIPE, Broken pipe.
[Switching to Thread 0x7ffff7800700 (LWP 10437)]
0x00007ffff7bc5c5e in __libc_send (fd=4, buf=0x7ffff77fedd0, len=24, flags=0) at ../sysdeps/unix/sysv/linux/send.c:28
28 return SYSCALL_CANCEL (sendto, fd, buf, len, flags, NULL, 0);
再看下boofuzz日志:
[2022-04-26 18:00:06,026] Test Step: Contact target monitors
[2022-04-26 18:00:06,028] Test Step: Cleaning up connections from callbacks
[2022-04-26 18:00:06,030] Check OK: No crash detected.
[2022-04-26 18:00:06,993] Info: Closing target connection...
能猜到是boofuzz提前关闭连接,导致httpd的send报错,,,
于是在session.py找到close,下断点:
def close(self):
"""
Close connection to the target.
:return: None
"""
self._fuzz_data_logger.log_info("Closing target connection...")
self._target_connection.close()
self._fuzz_data_logger.log_info("Connection closed.")
于是httpd就不崩溃了(一开始还以为崩了呢—_—)。
这个问题可以通过给session指定测试间隔时间来规避:
session = Session(
target=Target(connection=TCPSocketConnection("127.0.0.1", 38503)),
sleep_time=2
)