作者:0x7F@知道創宇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 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/2070/