黑盒 fuzz 工具 Jackalope 的使用
2023-5-16 11:15:0 Author: paper.seebug.org(查看原文) 阅读量:21 收藏

作者:[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++的桌面开发」即可: 1.配置Visual Studio

随后配置 Python3 环境,注意勾选自动添加环境变量: 2.配置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

执行如下: 3.编译Jackalope

编译成功后,可在 [src]/build/Release/ 下看到二进制文件 fuzzer.exe4.fuzzer二进制文件

0x03 fuzz test

Jackalope 源码中还提供了 test.cpp 测试代码,会自动编译生成 [src]/build/Release/test.exe,我们使用该二进制文件演示 Jackalope 的使用。

test.cpp 源码中提供了 -f/-m 两个命令行参数,用于区分直接读取文件加载数据还是使用内存映射的方式加载数据,其核心代码如下: 5.test核心函数fuzz

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

执行如下: 6.fuzz运行并获得crash

运行一段时间后我们便收获了 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 速度大幅提高: 7.持久模式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 ......8.自定义异常引发fuzz错误

最终 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,可以查看覆盖率情况如下: 9.lighthouse查看覆盖率

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


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/2070/



文章来源: https://paper.seebug.org/2070/
如有侵权请联系:admin#unsafe.sh