作者: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++的桌面開發」即可: 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 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/2070/