標籤:目錄結構 文檔 running 注入 dump 主程 事件 off strong
本文為 椒圖科技 授權嘶吼發布,如若轉載,請註明來源於嘶吼: http://www.4hou.com/technology/2800.html
導語:Winafl是一個檔案格式及協議漏洞的半自動探索工具,可以協助我們發現各種使用特定格式檔案的應用軟體漏洞,如檔案編輯軟體、圖片查看軟體、視頻播放軟體等。
Winafl是Linux下的智能模糊測試神器afl-fuzz的Windows版本。afl-fuzz從2013年發布到現在,發現了很多真實的漏洞,被廣大網路安全從業人員所使用,現在,Windows版本也同樣問世,我們同樣可以利用它對Windows下的軟體進行測試並嘗試發現漏洞。
Winafl是一個檔案格式及協議漏洞的半自動探索工具,可以協助我們發現各種使用特定格式檔案的應用軟體漏洞,如檔案編輯軟體(doc、xls、pdf等)、圖片查看軟(bmp、jpg、png等)、視頻播放軟體(avi、mp4、mpg等)等。
一、編譯(直接下載即可,裡麵包含編譯過的檔案)
Winafl的編譯一般情況下不會出現什麼問題,過程如下:
可以從https://github.com/ivanfratric/winafl直接下載所有代碼的打包檔案,也可以使用git for Windows從網站Clone所有代碼。下載的代碼本身包含編譯好的版本,包括32位和64位,你也可以自己編譯,這樣方便理解、修改和使用Winafl。
1. 下載CMake(http://www.cmake.org),Winafl的編譯需要CMake,CMake是一個跨平台的編譯軟體。
2. 下載DynamoRIO(http://dynamorio.org/),一個跨平台開源的分析代碼動態instrumentation工具軟體。
3. 然後你需要一個編譯器,可以用Visual Studio C++,版本的問題是跟CMake有關的,下載最新的CMake,支援VS的2005-2015版本,但由於VS 2010前的版本並不跟C99完全相容,所以編譯的時候會出現stdint.h沒有找到的錯誤,可以從別的和C99相容的編譯器複製一個,比如MinGW、或者直接從網上搜尋下載一個。
安裝CMake,自動建立環境變數。
4. 建立一個目錄,如C:\test,解壓Winafl、DynamoRIO到C:\test目錄。
5. 啟動Visual Studio命令列編譯環境,如果要編譯32位版本進入32位環境、否則進入64位環境,編譯什麼版本取決於我們需要fuzzing的軟體版本。
6. 進入Winafl目錄,可以發現Bin32和Bin64目錄,這2個目錄是編譯好的Winafl,我們可以重新編譯
32位下鍵入如下命令完成編譯:
md b32cd b32cmake .. -DDynamoRIO_DIR=C:\test\DynamoRIO\cmake(DynamoRIO在你機器上的路徑)cmake --build . --config Release(也可以是Debug)
64位下鍵入如下命令完成編譯:
md b64cd b64cmake -G"Visual Studio 10 Win64" .. -DDynamoRIO_DIR=C:\test\DynamoRIO\cmake(DynamoRIO在你機器上的路徑)cmake --build . --config Release(也可以是Debug)
Winafl編譯完成可以使用了,編譯好的檔案在目前的目錄下的Release(或Debug)目錄。
二、使用
代碼編譯後產生的主要檔案有:
afl-fuzz.exe 主程式winafl.dll 注入進fuzzing程式的模組
afl-fuzz.exe命令列格式如下:
afl-fuzz [afl options] -- [instrumentation options] -- target_cmd_line
其中[afl options]常用的參數包括(這些參數由afl-fuzz.exe處理):
-i dir – 測試案例存放目錄-o dir – fuzzing過程和結果存放目錄-D dir – 二進位動態Instrumentation工具執行檔案路徑-t msec – 逾時設定-x dir – 字典檔案
…更多參數請查閱afc-fuzz.c的main函數。
[instrumentation options]常用的參數包括(這些參數由winafl.dll處理):
-coverage_module – fuzzing對象程式會調用到的模組-target_module – fuzzing對象模組,也就是-target_offset所在的模組-target_offset – fuzzing的位移地址,也就是會被instrumentation的位移-fuzz_iterations – 再fuzzing對象程式重新啟動一次內運行目標函數的最大迭代數-debug – debug模式
…更多參數請查閱winafl.c的options_init函數。
target_cmd_line參數就是你要fuzzing對象的啟動程式。
那麼,如果fuzzing winafl預設帶的例子,整個命令列如下:
afl-fuzz.exe -i in -o out -D C:\test\DynamoRIO\bin32 -t 20000 -- -coverage_module gdiplus.dll -coverage_module WindowsCodecs.dll -fuzz_iterations 5000 -target_module test_gdiplus.exe -target_offset 0x1260 -nargs 2 -- test_gdiplus.exe @@
-i in 測試案例存放在目前的目錄下的in目錄-o out 測試結果和過程輸出到目前的目錄下的out目錄-D c:\DynamoRIO-Windows-6.1.1-3\bin32 Instrumentation工具執行檔案路徑,可以採用相對路徑-t 20000 運行fuzzing對象檔案的逾時— 分割
-coverage_module gdiplus.dll -coverage_module WindowsCodecs.dll為fuzzing對象程式會調用的模組,也就是說你fuzzing的位移地址的函數會調用到這些模組裡面的函數,這個可以通過一些PE工具來查看fuzzing對象調用了那些模組,或者通過winafl.dll的-debug模式獲得,然後根據反組譯碼代碼判斷調用了那些模組,在無法自行判斷的情況下我們寫的跟-target_module一樣即可。
-target_module test_gdiplus.exe -target_offset所在的模組,也是fuzzing的對象模組
-target_offset 0x1260 fuzzing模組instrumentation的位移地址,如test_gdiplus.exe是一個很簡單的程式,所以對main函數進行fuzzing,而通過ida或類似的工具發現main函數相對於test_gdiplus.exe基地址的位移為0x1260
(注意:此處位移地址的計算方式,IDA開啟後代碼起始地址為0x401000,main函數地址為0x401260,但是exe基址為:Imagebase : 400000,因此位移為0x1260,不是減去代碼起始地址。
在有pdb的情況下,直接寫-target_method main.)
test_gdiplus.exe @@ fuzzing對象程式的啟動命令列
運行上面命令出現如下介面,afl-fuzz.exe開始工作。
三、流量分析
afl-fuzz.c的大體流程:
建立整個out目錄結構及相關檔案
處理測試案例並寫到out目錄
通過drrun.exe運行fuzzing對象程式並插入winafl.dll,使用變形後的測試案例進行測試,並返回結果。那麼具體out目錄結構如下:
out//crashes 使應用程式crash的用例,也就是Winafl存放最終結果的目錄/hangs 存放使應用程式掛起的用例,這個目錄的檔案同樣可能使應用程式crash/queue fuzzing的一些過程檔案/.cur_input 當前fuzzing的用例/fuzz_bitmap 被fuzzing函數的映射
3.1、測試案例檔案變形處理
afl-fuzz.exe通過-i參數指向的目錄讀取使用者提供的測試檔案,並通過各種變形儲存到out/.cur_input檔案,供被fuzzing的應用程式調用開啟,查看afl_fuzz.c可以發現其對測試案例都做過哪些變形。
afl-fuzz.c的主要函數是fuzz_one(char** argv),對測試檔案的變形操作也通過這個函數完成,在fuzz_one函數中會調用trim_case(char** argv, struct queue_entry* q, u8* in_buf)函數,先對測試案例進行裁剪,測試案例太大會影響測試速度,這是肯定的,所以理論上說測試案例越小那麼全部fuzzing完成的時間就越短,那麼怎麼裁剪才不會影響fuzzing的結果呢,afl-fuzz.c靠如下手段完成。
首先,Winafl會建立一個64KB的共用記憶體,用於儲存被fuzzing的應用程式的執行路徑,如:A -> B -> C -> D -> E (tuples: AB, BC, CD, DE) ,當程式路徑發生改變時,如變成A -> B -> D -> C -> E (tuples: AB, BD, DC, CE),Winafl會更新共用記憶體,發現新的執行路徑更有助於發現代碼的漏洞,因為大多數安全性漏洞經常是一些沒有預料到的狀態轉移,而不是因為沒有覆蓋那一塊代碼。
trim_case函數會用測試案例的總長度除以16,並刪除這個長度的資料交給被fuzzing的應用程式開啟並記錄執行路徑到共用記憶體中,執行完成後trim_case函數對這塊共用記憶體進行hash計算,如果這塊共用記憶體沒有發生變化,那麼說明沒有發現新的執行路徑,也同時說明被裁剪的這段資料對整個測試結果影響不大,於是對以後的測試案例都刪除掉這段內容,這樣,測試案例就會減小到一個合理的大小而並不會影響fuzzing結果。
在fuzz_one函數裡,接著就會對測試案例檔案做大量的變形操作,包括以下幾種類型的變形:bit flips、byte flips、arithmetics、known ints、dictionary、havoc。
在bit flips模式下,對測試案例檔案的內容進行按每1、2、4位的長度進行順序變形,在進行1、2、4位的長度變形時使用如下演算法:
#define FLIP_BIT(_ar, _b) do { u8* _arf = (u8*)(_ar); u32 _bf = (_b); _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); } while (0)
在byte flips模式下,對測試案例檔案的內容進行按每8、16、32位的長度進行順序,變形時使用語句:out_buf[stage_cur] ^= 0xFF;、*(u16*)(out_buf + i) ^= 0xFFFF;、*(u32*)(out_buf + i) ^= 0xFFFFFFFF;來進行變形,並把變形後的測試案例交給common_fuzz_stuff函數進行後續處理。
在arithmetics模式下,8位的運算運用變形語句:out_buf[i] = orig + j;、16位的運算運用變形語句:*(u16*)(out_buf + i) = orig – j;、32位的運算運用變形語句:*(u32*)(out_buf + i) = orig + j;。並把變形後的測試案例交給common_fuzz_stuff函數進行後續處理。
在known ints模式下、8位的運算運用變形語句:out_buf[i] = interesting_8[j];、16位的運算運用變形語句:*(u16*)(out_buf + i) = interesting_16[j];、32位的運算運用變形語句:*(u32*)(out_buf + i) = interesting_32[j];。並把變形後的測試案例交給common_fuzz_stuff函數進行後續處理。其中interesting相關定義如下:
static s8 interesting_8[] = { INTERESTING_8 };static s16 interesting_16[] = { INTERESTING_8, INTERESTING_16 };static s32 interesting_32[] = { INTERESTING_8, INTERESTING_16, INTERESTING_32 };
在dictionary模式下,允許使用者使用字典,這樣可以用字典檔案對測試案例的相關部分進行替換並可以對一些有格式的文本、協議進行fuzz,如html、js、php等。如果使用字典,參數是-x,後面可以是目錄或者是一個字典檔案,如:-x dict\xml.dict、-x dict,裝載字典的函數是load_extras,如果參數是一個檔案,那麼用load_extras_file開啟並格式化,用於對一些文字檔處理常式進行fuzz,如php、sql等、如果是一個目錄,那麼load_extras函數把所有檔案內容讀到extras數組,對二進位檔案處理常式進行fuzz,如doc、xls等,此時對字典檔案的大小限定為小於128B,但這個版本的load_extras函數寫的有問題,沒辦法使用,但自己可以簡單的修改下,如把load_extras_file(dir, &min_len, &max_len, dict_level);goto check_and_sort;這2條語句上移,不進行多餘的判斷,直接執行load_extras_file函數,至少能用字典跑一些帶格式的文字檔,文字格式設定檔案的一些字典可以參考testcases的例子。
在havoc模式下,通過如下運算式UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))計算出一個值,並通過switch…case進行不同的匹配變形。
3.2、通過DynamoRIO進行instrumentation
afl-fuzz對測試案例進行變形後,調用DynamoRIO的相關程式對被fuzz的目標程式進行instrumentation並把目標程式的參數設定為變形後的測試案例,監視目標程式的運行,如果目標程式crash,那麼複製變形後的測試案例到out\crashes目錄下,如果目標程式無響應,那麼複製變形後的測試案例到out\hangs目錄下。這裡負責運行DynamoRIO的函數是run_target函數。
run_target首先檢查目標進程是否存在,如果存在終止進程,並通過drconfig.exe -nudge_pid %d 0 1命令將一個參數為1的nudge事件送到用戶端回調,既目標fuzz程式的參數為1。然後通過drrun.exe -pidfile %s -no_follow_children -c winafl.dll %s — %s命令啟動目標程式,並注入winafl.dll到目標程式進程,之後的操作可以通過查看winafl.c檔案進行分析,winafl.c的入口函數為dr_client_main函數,先繼續一些必要的初始化工作後,首先通過options_init函數處理winafl的相關命令列參數,包括coverage_module、target_module、target_offset等,接著掛載各種回呼函數:event_exit,退出事件回調;onexception,異常回調;
instrument_bb_coverage,instrument_edge_coverage,兩種模式instrumentation的回調;event_module_load,event_module_unload模組裝載、卸載回調。在event_module_load函數中,可以看到winafl通過drwrap_wrap(to_wrap, pre_fuzz_handler, post_fuzz_handler);完成在給定位移(offset)的指向函數進入時運行pre_fuzz_handler函數,退出時運行post_fuzz_handler函數,監控位移指向函數的運行。
3.3、參數選擇
coverage_module可以有多個,target_module只有一個,而target_offset指定的位移是target_module相對於基地址的位移,coverage_module和target_module必須是目標被fuzz程式所調用的模組或其自身,確定的辦法可以通過-debug參數完成,如運行如下命令列:
C:\test\DynamoRIO\bin32\drrun.exe -c winafl.dll -debug -target_module test_gdiplus.exe -target_offset 0x1650 -fuzz_iterations 10 -nargs 2 — test_gdiplus.exe input.bmp
winafl會在目前的目錄下產生log檔案,檔案名稱類似afl.test_gdiplus.exe.13280.0000.proc.log,內容如所示:
可以看到很多行開始都是Module loaded,也就是說參數coverage_module和target_module指定的模組必須出現在這裡。
target_offset的設定簡單的命令列程式可以直接設定為程式的main函數,如上面例子的test_gdiplus.exe,複雜的帶介面的程式尋找位移可以如下操作:
1. 啟動ollydbg並載入目標程式,參數設定為測試案例文檔,按F9,目標程式停止在程式入口;
2. 在Command輸入框中建立斷點:bp CreateFileA、bp CreateFileW,一般情況下應用程式如果開啟檔案肯定會調用這2個API;
3. 按F9,觀察參數是否為輸入的測試案例文檔,不是繼續F9(函數調用太頻繁可以使用條件斷點 bp kernel32.CreateFileW, [UNICODE [ESP+4]]==”C:\\test\\1.mov”),如果是按F7進入,按Ctrl+F9執行到返回,記錄下EIP地址;
4. 繼續Ctrl+F9執行到返回,記錄下記錄下EIP地址,如此反覆多次,得到多個位移地址;
5. 用IDA反組譯碼目標程式,找到所有記錄的編譯地址所在函數的位移,這樣我們得到了一個執行路徑,把每個位移地址用上面提到的帶-debug參數的命令列測試,如果產生的log檔案中包含語句:Everything appears to be running normally,那麼說明這個位移地址是可以用的。接著,我們就可以用類似下面的命令列來對目標程式進行fuzz了。
afl-fuzz.exe -i in -o out -D C:\test\DynamoRIO\bin32 -t 20000 -- -coverage_module gdiplus.dll -coverage_module WindowsCodecs.dll -fuzz_iterations 5000 -target_module test_gdiplus.exe -target_offset 0x1650 -nargs 2 -- test_gdiplus.exe @@
當然,可以直接alt+k查看Call stack,但有時候想查看更深的調用可以這樣操作。這個簡單的辦法找到的位移地址極有可能不是最好的位移地址或者是無用的,而這是跟目標程式寫的方法和習慣有關係的,但無論如何確定的target_offset位移地址必須是目標應用程式開啟測試案例肯定會調用到的位移,否則fuzz程式無法繼續運行。
四、一個例子
真實的情況是通過winafl想短時間發現流行軟體的bug還是比較難的,甚至發現不了。我用winafl跑個很流行的文文書處理軟體跑了一天,一個crash沒有出現,可能時間不夠長,呵呵,還是先找個不流行的小眾軟體測試下好了。我下載了一些不流行的軟體來跑,於是winafl終於有了用武之地,每個軟體都跑了不太長的時間,就出現了很多crash。
跑了很多軟體後,出現了很多crash,但確定這些crash是否可利用還要花大量的時間,而這些軟體的價值都不是很大,只粗略的看了下,基本上都是讀異常,被利用的可能性很小,當然仔細分析的話還是可能存在被利用的可能,但由於上述的原因沒有仔細分析。其中有一個軟體的crash很像能利用,於是簡單的分析了下,最終認為也不能利用,下面把這個例子貼出來。
程式出錯位置:
異常的語句:rep movsd dword ptr es:[edi], dword ptr [esi],明顯是一個memcpy操作,檢查下esi是否可以控制,向上一層找:
esi就是從中黃色顯示的eax經過運算得來的,預設情況下這個值應該是50,當修改測試檔案相關位移的內容後,這個值是可以有限控制的,但看完esi後繼續看edi,發現完全無法控制,edi是函數內部malloc出來的,而複製的長度是固定的,也就是說基本上這個crash不太可能利用了。
無論如何,Winafl是一個開源的優秀半自動化fuzz測試載入器,相比較其他的公開fuzz測試載入器還是有很大的優勢的,那麼讓我們去發現漏洞吧。 ^_^
轉:智能模糊測試工具 Winafl 的使用與分析