Winafl中基於插樁的覆蓋率反饋原理
最近winafl增加支援對Intel PT的支援的,但是隻支援x64,且覆蓋率計算不全,比如條件跳轉等,所以它現在還是不如直接用插樁去hook的方式來得準確完整,這裡主要想分析也是基於 DynamoRIO插樁的覆蓋率反饋原理。
之前曾有人在《初識 Fuzzing 工具 WinAFL》(https://paper.seebug.org/323/#32)中“3.2.2 插樁模組”一節中簡單分析過其插樁原理,但沒有找到我想要的答案,因此只好自動動手分析下原始碼。
比如,我想知道:
-
通過迴圈呼叫fuzzing的目標函式來提高速度,但DynamoRIO的覆蓋率資訊是如何同步給fuzzer主程序的?
-
具體是如何實現暫存器環境的記錄與恢復,從而實現目標函式的不斷迴圈?
-
覆蓋率資訊是如何記錄與分析的?
覆蓋率資訊記錄與分析原理
第3個問題發現已經有人分析過afl,可以參見這裡《AFL內部實現細節小記》(http://rk700.github.io/2017/12/28/afl-internals/),簡單總結下:
-
AFL在編譯原始碼時,為每個程式碼生成一個隨機數,代表位置地址;
-
在二元組中記錄分支跳轉的源地址與目標地址,將兩者異或的結果為該分支的key,儲存每個分支的執行次數,用1位元組來儲存;
-
儲存分支的執行次數實際上是一張大小為64K的雜湊表,位於共享記憶體中,方便target程序與fuzzer程序之間共享,對應的虛擬碼如 下:
-
fuzzer程序通過buckets雜湊桶來歸類這些分支執行次數,如下結構定義,左邊為執行次數,右邊為記錄值trace_bits:
-
對於是否觸發新路徑,主要通過計算各分支的trace_bits的hash值(演算法:u32 cksum **=** hash32(trace_bits, MAP_SIZE常量, HASH_CONST常量);)是否發生變化來實現的
覆蓋資訊的傳遞原理
-
先在fuzzer程序中先建立命名管道,其中fuzzer_id為隨機值:
2.建立drrun程序去執行目標程式並Hook,在childpid_(%fuzzer_id%).txt的檔案中記錄子程序id,即目標程序ID,然後等待管道連線,並通過讀取上述txt檔案以獲取目標程序id,主要用來後面超時中斷程序的:
3. 在插樁模組winafl.dll中開啟前面建立的命名管道,然後通過管道與fuzzer主程序進行互動:
4. 當插樁模組winafl.dll監測到程式首次執行至目標函式入口時,pre_fuzz_handler函式會被執行,然後通過管道寫入'P'命令,代表開始進入目標函式,afl-fuzz.exe程序收到命令後,會向目標程序寫入管道命令'F',並監測超時時間和迴圈呼叫次數。afl-fuzz.exe與目標程序正是通過讀寫管道命令來互動的,主要有'F'(退出目標函式)、'P'(進入目標函式)、'K'(超時中斷程序)、'C'(崩潰)、'Q'(退出程序)。覆蓋資訊通過檔案對映方法(記憶體共享)寫入winafl_data.afl_area:
篡改目標函式迴圈呼叫的原理
此步的關鍵就在於進入目標函式前呼叫的pre_fuzz_handler函式,以及函式退出後呼叫的post_fuzz_handler函式。
進入pre_fuzz_handler函式時,winafl.dll會先獲取以下資訊
其中記憶體上下文資訊支援各平臺的暫存器記錄:
接下來就是獲取和設定fuzzed的目標函式引數:
當目標函式退出後,執行post_fuzz_handler函式,會恢復棧頂指標和pc地址,以此實現目標函式的迴圈呼叫:
總結
總結下整個winafl執行流程:
-
afl-fuzz.exe通過建立命名管道與記憶體對映來實現與目標程序互動,其中管道用來發送和接收命令相互操作對方程序,記憶體對映主要用來記錄覆蓋率資訊;
-
覆蓋率記錄主要通過drmgr_register_bb_instrumentation_event去設定BB執行的回撥函式,通過instrument_bb_coverage或者instrument_edge_coverage來記錄覆蓋率情況,如果發現新的執行路徑,就將樣本放入佇列目錄中,用於後續檔案變異,以提高程式碼覆蓋率;
-
目標程序執行到目標函式後,會呼叫pre_fuzz_handler來儲存上下文資訊,包括暫存器和執行引數;
-
目標函式退出後,會呼叫post_fuzz_handler函式,記錄恢復上下文資訊,以執行回原目標函式,又回到第2步;
-
目錄函式執行次數達到指定迴圈呼叫次數時,會中斷程序退出。