您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)Fuzz結(jié)果分析和代碼覆蓋率指的是什么,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
下面介紹何時(shí)結(jié)束測試過程,以及測試完成后又需要做些什么。并開始逐步介紹一些AFL相關(guān)原理,以下是文中主要討論的問題:
1.何時(shí)結(jié)束Fuzzing工作
2.afl-fuzz生成了哪些文件
3.如何對產(chǎn)生的crash進(jìn)行驗(yàn)證和分類
4.用什么來評估Fuzzing的結(jié)果
5.代碼覆蓋率及相關(guān)概念
6.AFL是如何記錄代碼覆蓋率的
因?yàn)閍fl-fuzz永遠(yuǎn)不會停止,所以何時(shí)停止測試很多時(shí)候就是依靠afl-fuzz提供的狀態(tài)來決定的。除了前面提到過的通過狀態(tài)窗口、afl-whatsup查看afl-fuzz狀態(tài)外,這里再補(bǔ)充幾種方法。
afl-stat是afl-utils這套工具AFL輔助工具中的一個(gè)(這套工具中還有其他更好用的程序,后面用到時(shí)會做介紹),該工具類似于afl-whatsup的輸出結(jié)果。
使用前需要一個(gè)配置文件,設(shè)置每個(gè)afl-fuzz實(shí)例的輸出目錄:
{ "fuzz_dirs": [ "/root/syncdir/SESSION000", "/root/syncdir/SESSION001", ... "/root/syncdir/SESSION00x" ] }
然后指定配置文件運(yùn)行即可:
$ afl-stats -c afl-stats.conf [SESSION000 on fuzzer1] Alive: 1/1 Execs: 64 m Speed: 0.3 x/s Pend: 6588/249 Crashes: 101 [SESSION001 on fuzzer1] Alive: 1/1 Execs: 105 m Speed: 576.6 x/s Pend: 417/0 Crashes: 291 ...
afl-whatsup是依靠讀afl-fuzz輸出目錄中的fuzzer_stats文件來顯示狀態(tài)的,每次查看都要需要手動執(zhí)行,十分麻煩。因此可以對其進(jìn)行修改,讓其實(shí)時(shí)顯示fuzzer的狀態(tài)。方法也很簡答,基本思路就是在所有代碼外面加個(gè)循環(huán)就好,還可以根據(jù)自己的喜好做些調(diào)整:
前面提到的都是基于命令行的工具,如果還想要更直觀的結(jié)果,可以用afl-plot繪制各種狀態(tài)指標(biāo)的直觀變化趨勢。
#安裝依賴工具gnuplot $ apt-get install gnuplot $ afl-plot afl_state_dir graph_output_dir
以測試libtiff的情況為例,進(jìn)入afl-plot輸出目錄,打開index.html,會看到下面三張圖:
首先是路徑覆蓋的變化,當(dāng)pending fav的數(shù)量變?yōu)榱悴⑶?strong>total paths數(shù)量基本上沒有再增長時(shí),說明fuzzer有新發(fā)現(xiàn)的可能性就很小了。
接著是崩潰和超時(shí)的變化
最后是執(zhí)行速度的變化,這里要注意的是,如果隨著時(shí)間的推移,執(zhí)行速度越來越慢,有一種可能是因?yàn)閒uzzer耗盡一些共享資源。
筆者在查閱資料的過程中,還發(fā)現(xiàn)了pythia這個(gè)AFL的擴(kuò)展項(xiàng)目,雖然不知道效果如何,但這里還是順便提一提。其特色在于可以估算發(fā)現(xiàn)新crash和path概率,其運(yùn)行界面相比原版的AFL多出了下面幾個(gè)字段:
correctness: 在沒有發(fā)現(xiàn)crash時(shí),發(fā)現(xiàn)一個(gè)導(dǎo)致crash輸入的概率。
fuzzability: 表示在該程序中發(fā)現(xiàn)新路徑的難度,該數(shù)值越高代表程序越容易Fuzz。
current paths: 顯示當(dāng)前發(fā)現(xiàn)的路徑數(shù)。
path coverag: 路徑覆蓋率。
檢查afl-fuzz工作狀態(tài)的目的是為何時(shí)停止測試提供依據(jù),通常來說符合下面幾種情況時(shí)就可以停掉了。
(1)狀態(tài)窗口中"cycles done"字段顏色變?yōu)榫G色該字段的顏色可以作為何時(shí)停止測試的參考,隨著周期數(shù)不斷增大,其顏色也會由洋紅色,逐步變?yōu)辄S色、藍(lán)色、綠色。當(dāng)其變?yōu)榫G色時(shí),繼續(xù)Fuzzing下去也很難有新的發(fā)現(xiàn)了,這時(shí)便可以通過Ctrl-C停止afl-fuzz。
(2)距上一次發(fā)現(xiàn)新路徑(或者崩潰)已經(jīng)過去很長時(shí)間了,至于具體多少時(shí)間還是需要自己把握,比如長達(dá)一個(gè)星期或者更久估計(jì)大家也都沒啥耐心了吧。
(3)目標(biāo)程序的代碼幾乎被測試用例完全覆蓋,這種情況好像很少見,但是對于某些小型程序應(yīng)該還是可能的,至于如何計(jì)算覆蓋率將在下面介紹。
(4)上面提到的pythia提供的各種數(shù)據(jù)中,一旦path covera達(dá)到99%(通常來說不太可能),如果不期望再跑出更多crash的話就可以中止fuzz了,因?yàn)楹芏郼rash可能是因?yàn)橄嗤脑驅(qū)е碌?;還有一點(diǎn)就是correctness的值達(dá)到1e-08,根據(jù)pythia開發(fā)者的說法,這時(shí)從上次發(fā)現(xiàn)path/uniq crash到下一次發(fā)現(xiàn)之間大約需要1億次執(zhí)行,這一點(diǎn)也可以作為衡量依據(jù)。
afl-fuzz的輸出目錄中存在很多文件,有時(shí)想要寫一個(gè)輔助工具可能就要用到其中的文件。下面以多個(gè)fuzz實(shí)例并行測試時(shí)的同步目錄為例:
$ tree -L 3 . ├── fuzzer1 │ ├── crashes │ │ ├── id:000000,sig:06,src:000019+000074,op:splice,rep:2 │ │ ├── ... │ │ ├── id:000002,sig:06,src:000038+000125,op:splice,rep:4 │ │ └── README.txt │ ├── fuzz_bitmap │ ├── fuzzer_stats │ ├── hangs │ │ └── id:000000,src:000007,op:flip1,pos:55595 │ ├── plot_data │ └── queue │ ├── id:000000,orig:1.png │ ├── .... │ └── id:000101,sync:fuzzer10,src:000102 └── fuzzer2 ├── crashes ├── ...
queue:存放所有具有獨(dú)特執(zhí)行路徑的測試用例。
crashes:導(dǎo)致目標(biāo)接收致命signal而崩潰的獨(dú)特測試用例。
crashes/README.txt:保存了目標(biāo)執(zhí)行這些crash文件的命令行參數(shù)。
hangs:導(dǎo)致目標(biāo)超時(shí)的獨(dú)特測試用例。
fuzzer_stats:afl-fuzz的運(yùn)行狀態(tài)。
plot_data:用于afl-plot繪圖。
到了這里,我們可能已經(jīng)跑出了一大堆的crashes,那么接下來的步驟,自然是確定造成這些crashes的bug是否可以利用,怎么利用?這是另一個(gè)重要方面。當(dāng)然,個(gè)人覺得這比前面提到的內(nèi)容都要困難得多,這需要對常見的二進(jìn)制漏洞類型、操作系統(tǒng)的安全機(jī)制、代碼審計(jì)和調(diào)試等內(nèi)容都有一定深度的了解。但如果只是對crash做簡單的分析和分類,那么下面介紹的幾種方法都可以給我們提供一些幫助。
這是afl-fuzz的一種運(yùn)行模式,也稱為peruvian rabbit mode,用于確定bug的可利用性,具體細(xì)節(jié)可以參考lcamtuf的博客。
$ afl-fuzz -m none -C -i poc -o peruvian-were-rabbit_out -- ~/src/LuPng/a.out @@ out.png
舉個(gè)例子,當(dāng)你發(fā)現(xiàn)目標(biāo)程序嘗試寫入\跳轉(zhuǎn)到一個(gè)明顯來自輸入文件的內(nèi)存地址,那么就可以猜測這個(gè)bug應(yīng)該是可以利用的;然而遇到例如NULL pointer dereferences這樣的漏洞就沒那么容易判斷了。
將一個(gè)導(dǎo)致crash測試用例作為afl-fuzz的輸入,使用-C選項(xiàng)開啟crash exploration模式后,可以快速地產(chǎn)生很多和輸入crash相關(guān)、但稍有些不同的crashes,從而判斷能夠控制某塊內(nèi)存地址的長度。這里筆者在實(shí)踐中沒有找到適合的例子,但在一篇文章中發(fā)現(xiàn)了一個(gè)很不錯的例子——tcpdump棧溢出漏洞,crash exploration模式從一個(gè)crash產(chǎn)生了42個(gè)新的crash,并讀取不同大小的相鄰內(nèi)存。
AFL源碼的experimental目錄中有一個(gè)名為triage_crashes.sh的腳本,可以幫助我們觸發(fā)收集到的crashes。例如下面的例子中,11代表了SIGSEGV信號,有可能是因?yàn)榫彌_區(qū)溢出導(dǎo)致進(jìn)程引用了無效的內(nèi)存;06代表了SIGABRT信號,可能是執(zhí)行了abort\assert函數(shù)或double free導(dǎo)致,這些結(jié)果可以作為簡單的參考。
$ ~/afl-2.52b/experimental/crash_triage/triage_crashes.sh fuzz_out ~/src/LuPng/a.out @@ out.png 2>&1 | grep SIGNAL +++ ID 000000, SIGNAL 11 +++ +++ ID 000001, SIGNAL 06 +++ +++ ID 000002, SIGNAL 06 +++ +++ ID 000003, SIGNAL 06 +++ +++ ID 000004, SIGNAL 11 +++ +++ ID 000005, SIGNAL 11 +++ +++ ID 000006, SIGNAL 11 +++ ...
當(dāng)然上面的兩種方式都過于雞肋了,如果你想得到更細(xì)致的crashes分類結(jié)果,以及導(dǎo)致crashes的具體原因,那么crashwalk就是不錯的選擇之一。這個(gè)工具基于gdb的exploitable插件,安裝也相對簡單,在ubuntu上,只需要如下幾步即可:
$ apt-get install gdb golang $ mkdir tools $ cd tools $ git clone https://github.com/jfoote/exploitable.git $ mkdir go $ export GOPATH=~/tools/go $ export CW_EXPLOITABLE=~/tools/exploitable/exploitable/exploitable.py $ go get -u github.com/bnagy/crashwalk/cmd/...
crashwalk支持AFL/Manual兩種模式。前者通過讀取crashes/README.txt文件獲得目標(biāo)的執(zhí)行命令(前面第三節(jié)中提到的),后者則可以手動指定一些參數(shù)。兩種使用方式如下:
#Manual Mode $ ~/tools/go/bin/cwtriage -root syncdir/fuzzer1/crashes/ -match id -- ~/parse @@ #AFL Mode $ ~/tools/go/bin/cwtriage -root syncdir -afl
兩種模式的輸出結(jié)果都一樣,如上圖所示。這個(gè)工具比前面幾種方法要詳細(xì)多了,但當(dāng)有大量crashes時(shí)結(jié)果顯得還是十分混亂。
最后重磅推薦的工具便是afl-collect,它也是afl-utils套件中的一個(gè)工具,同樣也是基于exploitable來檢查crashes的可利用性。它可以自動刪除無效的crash樣本、刪除重復(fù)樣本以及自動化樣本分類。使用起來命令稍微長一點(diǎn),如下所示:
$ afl-collect -j 8 -d crashes.db -e gdb_script ./afl_sync_dir ./collection_dir -- /path/to/target --target-opts
但是結(jié)果就像下面這樣非常直觀:
代碼覆蓋率是模糊測試中一個(gè)極其重要的概念,使用代碼覆蓋率可以評估和改進(jìn)測試過程,執(zhí)行到的代碼越多,找到bug的可能性就越大,畢竟,在覆蓋的代碼中并不能100%發(fā)現(xiàn)bug,在未覆蓋的代碼中卻是100%找不到任何bug的,所以本節(jié)中就將詳細(xì)介紹代碼覆蓋率的相關(guān)概念。
代碼覆蓋率是一種度量代碼的覆蓋程度的方式,也就是指源代碼中的某行代碼是否已執(zhí)行;對二進(jìn)制程序,還可將此概念理解為匯編代碼中的某條指令是否已執(zhí)行。其計(jì)量方式很多,但無論是GCC的GCOV還是LLVM的SanitizerCoverage,都提供函數(shù)(function)、基本塊(basic-block)、邊界(edge)三種級別的覆蓋率檢測,更具體的細(xì)節(jié)可以參考LLVM的官方文檔。
縮寫為BB,指一組順序執(zhí)行的指令,BB中第一條指令被執(zhí)行后,后續(xù)的指令也會被全部執(zhí)行,每個(gè)BB中所有指令的執(zhí)行次數(shù)是相同的,也就是說一個(gè)BB必須滿足以下特征:
只有一個(gè)入口點(diǎn),BB中的指令不是任何跳轉(zhuǎn)指令的目標(biāo)。
只有一個(gè)退出點(diǎn),只有最后一條指令使執(zhí)行流程轉(zhuǎn)移到另一個(gè)BB
將上面的程序拖進(jìn)IDA,可以看到同樣被劃分出了4個(gè)基本塊:
AFL的技術(shù)白皮書中提到fuzzer通過插樁代碼捕獲邊(edge)覆蓋率。那么什么是edge呢?我們可以將程序看成一個(gè)控制流圖(CFG),圖的每個(gè)節(jié)點(diǎn)表示一個(gè)基本塊,而edge就被用來表示在基本塊之間的轉(zhuǎn)跳。知道了每個(gè)基本塊和跳轉(zhuǎn)的執(zhí)行次數(shù),就可以知道程序中的每個(gè)語句和分支的執(zhí)行次數(shù),從而獲得比記錄BB更細(xì)粒度的覆蓋率信息。
具體到AFL的實(shí)現(xiàn)中,使用二元組(branch_src, branch_dst)來記錄當(dāng)前基本塊 + 前一基本塊 的信息,從而獲取目標(biāo)的執(zhí)行流程和代碼覆蓋情況,偽代碼如下:
cur_location = <COMPILE_TIME_RANDOM>;//用一個(gè)隨機(jī)數(shù)標(biāo)記當(dāng)前基本塊 shared_mem[cur_location ^ prev_location]++;//將當(dāng)前塊和前一塊異或保存到shared_mem[] prev_location = cur_location >> 1;//cur_location右移1位區(qū)分從當(dāng)前塊到當(dāng)前塊的轉(zhuǎn)跳
實(shí)際插入的匯編代碼,如下圖所示,首先保存各種寄存器的值并設(shè)置ecx/rcx,然后調(diào)用__afl_maybe_log
,這個(gè)方法的內(nèi)容相當(dāng)復(fù)雜,這里就不展開講了,但其主要功能就和上面的偽代碼相似,用于記錄覆蓋率,放入一塊共享內(nèi)存中。
了解了代碼覆蓋率相關(guān)的概念后,接下來看看如何計(jì)算我們的測試用例對前面測試目標(biāo)的代碼覆蓋率。
這里需要用到的工具之一是GCOV,它隨gcc一起發(fā)布,所以不需要再單獨(dú)安裝,和afl-gcc插樁編譯的原理一樣,gcc編譯時(shí)生成插樁的程序,用于在執(zhí)行時(shí)生成代碼覆蓋率信息。
另外一個(gè)工具是LCOV,它是GCOV的圖形前端,可以收集多個(gè)源文件的gcov數(shù)據(jù),并創(chuàng)建包含使用覆蓋率信息注釋的源代碼HTML頁面。
最后一個(gè)工具是afl-cov,可以快速幫助我們調(diào)用前面兩個(gè)工具處理來自afl-fuzz測試用例的代碼覆蓋率結(jié)果。在ubuntu中可以使用apt-get install afl-cov
安裝afl-cov,但這個(gè)版本似乎不支持分支覆蓋率統(tǒng)計(jì),所以還是從Github下載最新版本為好,下載完無需安裝直接運(yùn)行目錄中的Python腳本即可使用:
$ apt-get install lcov $ git clone https://github.com/mrash/afl-cov.git $ ./afl-cov/afl-cov -V afl-cov-0.6.2
還是以Fuzz libtiff為例,計(jì)算Fuzzing過程的代碼覆蓋率流程如下:
第一步,使用gcov重新編譯源碼,在CFLAGS中添加"-fprofile-arcs"
和"-ftest-coverage"
選項(xiàng),可以在--prefix
中重新指定一個(gè)新的目錄以免覆蓋之前alf插樁的二進(jìn)制文件。
$ make clean $ ./configure --prefix=/root/tiff-4.0.10/build-cov CC="gcc" CXX="g++" CFLAGS="-fprofile-arcs -ftest-coverage" --disable-shared $ make $ make install
第二步,執(zhí)行afl-cov。其中-d選項(xiàng)指定afl-fuzz輸出目錄;—live用于處理一個(gè)還在實(shí)時(shí)更新的AFL目錄,當(dāng)afl-fuzz停止時(shí),afl-cov將退出;--enable-branch-coverage用于開啟邊緣覆蓋率(分支覆蓋率)統(tǒng)計(jì);-c用于指定源碼目錄;最后一個(gè)-e選項(xiàng)用來設(shè)置要執(zhí)行的程序和參數(shù),其中的AFL_FILE和afl中的"@@"類似,會被替換為測試用例,LD_LIBRARY_PATH則用來指定程序的庫文件。
$ cd ~/tiff-4.0.10 $ afl-cov -d ~/syncdir --live --enable-branch-coverage -c . -e "cat AFL_FILE | LD_LIBRARY_PATH=./build-cov/lib ./build-cov/bin/tiff2pdf AFL_FILE"
成功執(zhí)行的結(jié)果如下所示:
我們可以通過—live選擇,在fuzzer運(yùn)行的同時(shí)計(jì)算覆蓋率,也可以在測試結(jié)束以后再進(jìn)行計(jì)算,最后會得到一個(gè)像下面這樣的html文件。它既提供了概述頁面,顯示各個(gè)目錄的覆蓋率;也可以在點(diǎn)擊進(jìn)入某個(gè)目錄查看某個(gè)具體文件的覆蓋率。
點(diǎn)擊進(jìn)入每個(gè)文件,還有更詳細(xì)的數(shù)據(jù)。每行代碼前的數(shù)字代表這行代碼被執(zhí)行的次數(shù),沒有執(zhí)行過的代碼會被紅色標(biāo)注出來。
關(guān)于Fuzz結(jié)果分析和代碼覆蓋率指的是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。