通过静态分析自动化“可模糊测试”目标发现的框架

进行软件安全评估的漏洞研究人员通常会利用覆盖引导的模糊测试能力,借助 AFL++ 和 libFuzzer 等强大工具。这很重要,因为它自动化了漏洞挖掘过程,并能快速暴露目标中的可利用条件。然而,当遇到庞大而复杂的代码库或闭源二进制文件时,研究人员必须花费大量时间手动审计和逆向工程,以确定哪些函数适合进行基于模糊测试的探索。
Fuzzable 是一个集成 C/C++ 源代码和二进制文件的框架,旨在帮助漏洞研究人员识别适合模糊测试的函数目标。它通过应用多种基于静态分析的启发式方法,精确定位软件中的危险行为及其执行的函数。研究人员随后可以利用该框架生成基本的 harness 模板,用于漏洞挖掘,或集成到持续模糊测试管道中,例如 Google 的 oss-fuzz 项目。
除了作为独立工具运行外,Fuzzable 还作为 Binary Ninja 反汇编器的插件集成,其他反汇编后端的支持也在开发中。
查看最初介绍该工具的博客文章(此处),其中详细介绍了静态分析启发式方法的技术规范以及该工具的诞生背景。该工具还曾在 Black Hat Arsenal USA 2022 上展示。
某些二进制目标可能需要一些清理(例如签名匹配或识别内联函数),因此 fuzzable 主要使用 Binary Ninja 作为反汇编后端,因为它能有效解决这些问题。因此,它可以作为独立工具和插件使用。
由于并非所有人都能使用 Binary Ninja,而且在安全评估中可能需要扩展到云端,因此也支持 angr 作为后备后端。预计将来还会集成其他反汇编器(优先级:Ghidra)。
如果你拥有 Binary Ninja 商业版,请确保安装 API 以支持独立无头使用:
$ python3 /Applications/Binary\ Ninja.app/Contents/Resources/scripts/install_api.py
使用 pip 安装:
$ pip install fuzzable
我们使用 poetry 进行依赖管理和构建。要手动构建,请克隆包含第三方模块的仓库:
$ git clone --recursive https://github.com/ex0dus-0x/fuzzable
手动安装:
$ cd fuzzable/
# 不使用 poetry
$ pip install .
# 使用 poetry
$ poetry install
# 使用 poetry 创建开发虚拟环境
$ poetry shell
现在你可以使用该工具分析二进制文件和/或源代码!
# 分析单个共享对象库二进制文件
$ fuzzable analyze examples/binaries/libbasic.so
# 分析单个 C 源代码文件
$ fuzzable analyze examples/source/libbasic.c
# 分析包含多个 C/C++ 文件和头文件的工作区
$ fuzzable analyze examples/source/source_bundle/
fuzzable 可以通过 Binary Ninja 的插件市场轻松安装:进入 Binary Ninja > 管理插件,搜索并安装。以下是 fuzzable 插件运行时的示例,准确识别适合模糊测试和进一步漏洞评估的目标:

fuzzable 提供了多种选项来更好地调整分析。未来计划支持更多选项,并欢迎提出功能请求。
为了确定可模糊测试性,fuzzable 使用多种启发式方法来确定哪些目标最适合进行动态分析。这些启发式方法使用 scikit-criteria 库进行不同权重的分配,该库利用 多标准决策分析 来确定最佳候选者。指标及其权重如下所示:
如上所述,请查看 技术博客文章 以深入了解这些指标的使用原因和方法。
许多指标很大程度上受 Vincenzo Iozzo 在零知识模糊测试方面的原创工作 启发。
每个要分析的目标都是多样化的,fuzzable 无法处理程序目标中的每一种边缘情况行为。因此,在分析过程中,调整 这些权重可能很重要,以便查看不同结果是否更符合你的用例。要在 CLI 中调整权重,只需指定 --score-weights 参数:
$ fuzzable analyze <TARGET> --score-weights=0.2,0.2,0.2,0.2,0.2
默认情况下,fuzzable 会根据以下标准过滤函数目标:
static 函数,未通过头文件暴露。要查看被 fuzzable 过滤掉的调用,请设置 --list_ignored 标志:
$ fuzzable analyze --list-ignored <TARGET>
在 Binary Ninja 中,可以在 设置 > Fuzzable > 列出忽略的调用 中开启此设置。
如果 fuzzable 错误地过滤掉了应该分析的重要调用,建议使用 --include-* 参数在运行中包含它们:
# 包含所有被过滤掉的非顶层调用
$ fuzzable analyze --include-nontop <TARGET>
# 包含被过滤掉的特定符号
$ fuzzable analyze --include-sym <SYM> <TARGET>
在 Binary Ninja 中,这可以通过 设置 > Fuzzable > 包含非顶层调用 和 要排除的符号 来支持。
现在你已经找到了理想的模糊测试候选函数,fuzzable 还将帮助你生成(几乎)准备好编译的 fuzzing harness,用于基于文件的模糊测试器(如 AFL++、Honggfuzz)或内存模糊测试器(libFuzzer)。在 CLI 中执行以下操作:
# 从候选函数生成 harness
$ fuzzable create-harness target --symbol-name=some_unsafe_call
# 对 harness 进行最少的必要修改
$ vim target_some_unsafe_call_harness.cpp
# 例如使用 AFL-QEMU 编译,编译说明在生成的 harness 注释中
$ clang target_sone_unsafe_call_harness.cpp -no-pie -o target_some_unsafe_call_harness -ldl
# 创建基础种子,理想情况下输入应更为规范
$ mkdir in/
$ echo "seed" >> in/seed
# 开始黑盒模糊测试
$ afl-fuzz -Q -m none -i in/ -o out/ -- ./target_some_unsafe_call_harness
如果目标是源代码,则会使用 通用源代码模板。
如果目标是二进制文件,则会使用 通用黑盒模板,该模板适合使用类似 AFL-QEMU 的模糊测试模拟模式。如果符号未直接导出以通过 dlopen 加载,则会使用 LIEF 创建一个二进制文件的副本作为共享对象。
目前,此功能还比较基础,它仅创建一个包含适当参数的独立 C++ harness,不会自动生成任何运行时行为所需的代码(例如实例化和释放结构)。不过,生成的模板应能让你快速上手。以下是我未来希望实现的一些雄心勃勃的功能:
fuzzable 支持以多种格式生成报告。当前支持的格式包括 JSON、CSV 和 Markdown。这在你将此工具用于自动化、需要以可序列化格式获取输出时非常有用。
在 CLI 中,只需传递 --export 参数,并指定带有相应扩展名的文件名:
$ fuzzable analyze --export=report.json <TARGET>
在 Binary Ninja 中,进入 插件 > Fuzzable > 导出模糊测试报告 > ...,选择要导出的格式和写入路径。
该工具将持续开发,感谢外部维护人员的帮助!
Fuzzable 根据 MIT 许可 进行许可。
| 启发式方法 | 描述 | 权重 |
|---|
| 模糊友好名称 | 符号名称暗示会读取文件/缓冲区输入的行为 | 0.3 |
| 危险汇点 | 参数流入危险调用(如 memcpy) | 0.3 |
| 自然循环 | 基于支配边界检测到的循环数量 | 0.05 |
| 圈复杂度 | 基于边和节点的函数目标复杂度 | 0.05 |
| 覆盖深度 | 目标遍历的被调用者数量 | 0.3 |