boofuzz笔记

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>

响应回调和引用回调数据请参考:

# 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…

自定义块/原语的步骤:

  1. Create an object that inherits from Fuzzable or FuzzableBlock
  2. Override mutations and/or encode.
  3. Optional: Create an accompanying static primitive function. See boofuzz’s init.py file for examples.
  4. ???
  5. 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
    )

10. 参考资料

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值