作者:[email protected]知道创宇404实验室
日期:2023年4月23日
0x00 前言
Jackalope 是一款专用于 Windows/macOS 的黑盒 fuzz 开源工具,相比于 WinAFL 他要小众得多;WinAFL 是基于 DynamoRIO 插桩工具实现的,能够处理复杂的插桩需求,而 Jackalope 是基于 TinyInst,是基于调试器原理实现的轻量级动态检测库,Jackalope 更便于用户理解和自定义开发,也有一定的应用场景。
Jackalope 和 WinAFL 实现原理不同,但使用起来基本差不多,了解过 WinAFL 的小伙伴可以很快掌握这个工具;同时 Jackalope/TinyInst/WinAFL 都出自于 googleprojectzero
团队。本文主要介绍和演示 Jackalope 的使用。
本文实验环境
windows 10 专业版 x64 1909
Visual Studio 2019
Python 3.10.9
0x01 环境配置
首先配置 Visual Studio
开发环境,勾选「使用C++的桌面开发」即可:
随后配置 Python3
环境,注意勾选自动添加环境变量:
0x02 编译
按照官方提供的指南,我们打开 Visual Studio 命令提示符进行编译:
$ cd C:\Users\john\Desktop\Jackalope
$ git clone --recurse-submodules https://github.com/googleprojectzero/TinyInst.git
$ mkdir build
$ cd build
$ cmake -G "Visual Studio 16 2019" -A x64 ..
$ cmake --build . --config Release
执行如下:
编译成功后,可在 [src]/build/Release/
下看到二进制文件 fuzzer.exe
:
0x03 fuzz test
Jackalope 源码中还提供了 test.cpp
测试代码,会自动编译生成 [src]/build/Release/test.exe
,我们使用该二进制文件演示 Jackalope 的使用。
test.cpp
源码中提供了 -f/-m
两个命令行参数,用于区分直接读取文件加载数据还是使用内存映射的方式加载数据,其核心代码如下:
void FUZZ_TARGET_MODIFIERS fuzz(char *name)
被定义为导出函数,其核心逻辑为从文件中读取数据,若数据长度大于 4,且前 4 个字符串等于 0x74736574
也就是 test
时,手动触发空指针访问的错误。
接下来我们对 test.exe
进行 fuzz,构造工作目录,以及提供种子文件 1.txt
如下:
$ cd [src]/build/Release/
$ tree
.
├── in
│?? └── 1.txt
├── out
├── fuzzer.exe
└── test.exe
$ cat in/1.txt
1234
使用如下命令进行 fuzz:
# 指定样本输入目录 '-in in',结果输出目录 '-out out',超时时间为 '-t 1000'ms
# 指定覆盖率收集模块为 '-instrument_module test.exe',目标模块为 '-target_module test.exe',目标函数为 '-target_method fuzz'
# 开启 '-cmp_coverage' 覆盖率比较,可更高效的爆破多字节比较从而发现新路径
$ fuzzer.exe -in in -out out -t 1000 -instrument_module test.exe -target_module test.exe -target_method fuzz -cmp_coverage -- test.exe -f @@
详细命令行参数请参考 Jackalope/TinyInst 的 README.md
执行如下:
运行一段时间后我们便收获了 crash,手动 Ctrl-C
停止 fuzz,其 out
目录结构以及 crash 样本如下:
$ cd [src]/build/Release/
$ tree out
out/
├── crashes
│?? └── access_violation_0000xxxxxxxxx0E0_0000000000000000_1
├── hangs
├── samples
│?? ├── sample_00000
│?? ├── sample_00001
│?? ├── sample_00002
│?? └── sample_00003
├── input_1
└── state.dat
$ cat out/crashes/access_violation_0000xxxxxxxxx0E0_0000000000000000_1
test
实际使用 Jackalope 时,要避免将二进制命名为
test.exe
,因为正常编译 Jackalope 后与fuzzer.exe
同目录下有个官方的test.exe
,该文件会被优先加载。
0x04 持久模式
Jackalope 也和 WinAFL 一样提供了持久模式,也就是启动目标程序一次,重复执行执行目标 fuzz 函数多次,以这种方式减少 fuzz 过程中执行目标程序初始化代码的次数,从而提高 fuzz 效率,在 WinAFL 中使用的参数是 -fuzz_iterations 100
,Jackalope 使用以下一组参数:
# 指定每轮运行 100 次目标函数
-iterations 100
# 开启持久模式 '-persist -loop',指定目标函数参数为 1 个 '-nargs 1'
-persist -loop -nargs 1
使用持久模式对 test.exe
进行 fuzz:
fuzzer.exe -in in -out out -t 1000 -instrument_module test.exe -target_module test.exe -target_method fuzz -iterations 100 -persist -loop -nargs 1 -cmp_coverage -- test.exe -f @@
对比上文可以看到 fuzz 速度大幅提高:
多核CPU的情况下,还可以结合并发模式
-nthreads [n]
完全发挥机器性能。
0x05 兼容自定义异常处理
在程序开发中使用异常处理是一件很常见的事情,但对于基于调试器原理实现的 Jackalope 则是一个问题,当目标程序被调试器附加时发生了异常,会将异常首先传递给调试器进行处理,这就会导致 Jackalope 无法正确执行:若种子文件触发异常则会被视为无效种子文件,若 fuzz 过程中触发异常则会存入到 crash 结果中,但实际上在目标程序中却是一个功能正常的异常处理。
Jackalope(TinyInst) 提供了对异常的兼容处理,使用 -patch_return_addresses
或 -generate_unwind
(需要 UNWIND_INFO version 2,旧版 Windows 不支持) 参数即可,详情可以参考 https://github.com/googleprojectzero/TinyInst#return-address-patching
我们在 test.cpp
中添加自定义异常处理的代码如下:
if (sample_size >= 4) {
......
}
// custom-exception
if (sample_size == 3) {
__try {
throw "THIS IS TEST EXCEPTION";
}
__except (EXCEPTION_EXECUTE_HANDLER) {
printf("ok, try-except size = 3\n");
}
}
......
重新编译 test.exe
后,我们使用 123
作为种子文件,启动 fuzz 的同时使用 -trace_debug_events
参数以便我们排查 Jackalope 运行过程中的问题,随后可以看到
自定义异常导致一些错误日志 Debugger: Exception e06d7363 at address ......
:
最终 Jackalope 会报错退出:
[!] WARNING: Process exit during target function
[!] WARNING: Input sample resulted in a hang
[-] PROGRAM ABORT : No interesting input files
Location : Fuzzer::SynchronizeAndGetJob(), C:\Users\john\Desktop\Jackalope\fuzzer.cpp:630
那么添加 -patch_return_addresses
参数即可处理以上由自定义异常引发的问题:
# Example
$ fuzzer.exe -in in -out out -instrument_module test.exe -target_module test.exe -target_method fuzz -patch_return_addresses -cmp_coverage -trace_debug_events -- test.exe -f @@
0x06 覆盖率
Jackalope 使用 -dump_coverage
可以生成覆盖率文件,如下:
$ fuzzer.exe -in in -out out -t 1000 -instrument_module test.exe -target_module test.exe -target_method fuzz -cmp_coverage -dump_coverage -- test.exe -f @@
运行一段时间后,可在 out
目录下看到覆盖率文件 coverage.txt
,使用 IDA 加载 test.exe 文件,并使用 lighthouse 插件加载 coverage.txt
,可以查看覆盖率情况如下:
0x07 样本预处理
在 WinAFL 中我们使用 afl-fuzz.exe
进行 fuzz,如果输入文件夹中提供的种子文件存在问题,导致目标程序 crash 时,WinAFL 会停止运行并给予提示;但是 Jackalope 的处理机制不同,即便种子文件导致目标程序 crash,但只要有任一种子文件能够让目标程序正常运行,Jackalope 都会正常运行,并基于正常的种子文件进行变异和 fuzz。
这可能导致我们使用 Jackalope 时无法按照样本种子产生预期的覆盖率,所以在实际进行 fuzz 前,最好对样本种子进行校验,编写如下 powershell 脚本:
Get-ChildItem ".\input\" |
Foreach-Object {
$result = "BAD"
.\test.exe $_.FullName
if ($LASTEXITCODE -eq 0) {
$result = "GOOD"
Copy-Item $_.FullName -Destination ".\good\"
} else {
$result = "BAD"
Copy-Item $_.FullName -Destination ".\bad\"
}
Write-Host $_.FullName $result
}
根据我们编写的目标程序,程序正常运行时的退出码(exit code)为 0,为其他时表示发生异常错误。
除此之外,Jackalope 也提供对语料库最小化的操作,使用 -dry_run
参数启动 fuzz,Jackalope 在加载处理完所有的样本文件后直接退出,随后便可以在 [out]/samples
目录下看到通过覆盖率筛选后的样本文件,后续 fuzz 便可以用该文件夹的内容作为输入。
0x08 References
https://github.com/googleprojectzero/Jackalope
https://github.com/googleprojectzero/TinyInst
https://github.com/gaasedelen/lighthouse
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/2070/