經典 Fuzzer 工具 AFL 模糊測試指南

AFL(American Fuzzy Lop)是由安全研究員Michał Zalewski 開發的一款基於覆蓋引導(Coverage-guided)的模糊測試工具。通過記錄輸入樣本的代碼覆蓋率,不斷對輸入進行變異,從而達到更高的代碼覆蓋率。AFL 採用新型的編譯時插樁和遺傳算法自動發現新的測試用例,這些用例會觸發目標二進制文件中的新內部狀態。這大大改善了模糊測試的代碼覆蓋範圍。1

  1. 從源碼編譯程序時進行插樁,以記錄代碼覆蓋率(Code Coverage);
  2. 選擇一些輸入文件,作爲初始測試集加入輸入隊列(queue);
  3. 將隊列中的文件按一定的策略進行變異”;
  4. 如果經過變異文件更新了覆蓋範圍,則將其保留添加到隊列中;
  5. 上述過程會一直循環進行,期間觸發了crash的文件會被記錄下來。

在這裏插入圖片描述


0x10 安裝 afl

以下三個版本任選其一(推薦第二個,筆者三個都試過,在普通 afl 模式下,三個都沒有問題。但是涉及到更深層次的用法,多多少少都有些 bug;第二個是相對較好的一個版本)

AFLplusplus,引進了更強的編譯算法,如果是源碼插樁的方式,更適合使用 AFLplusplus。進入到已經下載好的項目,安裝 afl,命令如下

make	# 編譯 afl
sudo make install

這是編譯成功的截圖
在這裏插入圖片描述

0x20 構建被測程序(白盒測試)

用 AFL 編譯目標源碼,其目的在於插樁,讓編譯得到的程序,反饋路徑覆蓋。AFL 自帶定製版本的 gcc 和 clang 編譯器,建議選擇 LLVM 的 clang 編譯器,可以加快 fuzz 的速度

0x21 使用 afl-gcc 進行源碼插樁

對於單個文件,直接使用 afl-gcc 代替 gcc 即可

afl-gcc test.c -o test

對於完整的項目,需要將編譯器指定爲 afl-gcc,然後再進行編譯

./configure CC="afl-gcc" CXX="afl-g++"	# 或者直接修改 Makefie 文件,將編譯器改位 afl-gcc
make

如果需要 fuzz 共享庫,可以通過設置 LD_LIBRARY_PATH 讓程序加載經過 AFL 插樁的 .so 文件,不過最簡單的方法是靜態構建,通過以下方式實現

./configure --disable-shared CC="afl-gcc" CXX="afl-g++"

0x22 使用 LLVM 模式進行源碼插樁

AFL 還支持使用 LLVM 模式,可以獲得更快的Fuzzing速度,而且具有更多的選項,LLVM 的前端表現形式是 clang 編譯器,因此需要自行安裝 clang,之後就可以編譯了

afl-clang test.c -o test

0x23 出現 error while loading shared libraries 錯誤 2

構建完成之後,可能出現如下錯誤。這是因爲編譯器默認只會使用 /lib 和 /usr/lib 這兩個目錄下的庫文件,通過源碼編譯的方式安裝程序,如果不指定--prefix,會將庫安裝在/usr/local/lib目錄下;當運行程序需要鏈接動態庫時,提示找不到相關的.so庫,會報錯。也就是說,/usr/local/lib目錄不在系統默認的庫搜索目錄中,需要將目錄加進去。
在這裏插入圖片描述
解決方法

  1. 打開/etc/ld.so.conf文件
  2. 加入動態庫文件所在的目錄:執行vi /etc/ld.so.conf,在"include ld.so.conf.d/*.conf"下方增加"/usr/local/lib"
  3. 保存後,在命令行終端執行:/sbin/ldconfig -v;其作用是將文件 /etc/ld.so.conf 列出的路徑下的庫文件緩存到 /etc/ld.so.cache 以供使用,因此當安裝完一些庫文件,或者修改 etc/ld.so.conf增 加了庫的新搜索路徑,需要運行一下 ldconfig,使所有的庫文件都被緩存到文件 /etc/ld.so.cache 中,如果沒做,可能會找不到剛安裝的庫。

0x30 選擇語料庫

其實就是給被測程序喂入合適的測試用例,afl 會根據這些原始種子,變異生成大量的用例。理想條件下,提供的語料庫(即原始的測試用例)能夠讓程序執行不同的路徑,這樣才能達到代碼覆蓋最大化。

0x31 選擇用例

這裏,借用 Freebuf 提供的資料,給出一些開源的語料庫

事實上很多程序也會自帶一些案例,也可以作爲測試用例。


0x32 精簡語料庫

找到語料庫之後,最好能夠進行修剪,合併重複用例,裁剪體積。afl 推薦的每個用例體積小於 1KB,不然會影響 fuzz 的效率。

去重

afl-cmin 是 afl 提供的一個十分有用的工具,可以精簡語料庫,去掉可能重複的測試用例,針對一些複雜的語料庫十分有用,可大大減少無用的 fuzz 用例。

afl-cmin -i input_dir -o output_dir -- /path/to/tested/program [params]

更多的時候,我們是從文件中獲取輸入,因此,往往使用 @@ 替代 params(參數),即

afl-cmin -i input_dir -o output_dir -- /path/to/tested/program @@

縮小體積

afl-tmin 可以縮短文件體積,因爲 afl 要求測試用例的大小最好小於 1KB,因此最好將精簡後的用例進一步縮小體積。afl-tmin 有兩種工作模式,instrumented modecrash mode。默認的工作方式是instrumented mode

afl-tmin -i input_file -o output_file -- /path/to/tested/program [params] @@

由於 afl-cmin 一次性只能精簡單個文件,如果用例特別多,需要手動花費很長時間,其實一條簡單的 shell 腳本即可完成

for i in *; do afl-tmin -i $i -o tmin-$i -- ~/path/to/tested/program [params] @@; done;

0x40 fuzzing

在對程序正式進行 fuzz 之前,可以使用 afl-map 跟蹤單個用例的執行路徑,它會打印出程序的輸出和 tuples

afl-showmap -m none -o /dev/null

正式執行 fuzz 測試的命令如下

afl-fuzz -m none -i in -o out target_binary @@

0x50 黑盒 fuzz

以上 fuzz 過程,依賴於我們有程序的源碼,並且在編譯過程中進行了插樁,但很多時候,我們並沒有源碼,這時候就要靠 afl 提供的 qemu_mode 模式了。原版本的 afl qemu 模式由於版本過老,已不能正常運行,推薦使用 github 上的 AFLplusplus 或者 afl-unicornAFLplusplus 更容易安裝,而 afl-unicorn 針對 qemu 模式更加友好。

無論是下載的哪個版本的 afl,根目錄下都會有 qemu_mode 文件夾,進入此目錄,運行以下腳本,如果沒有出錯,就代表 qemu_mode 成功了

cd qemu_mode
sudo ./build_qemu_support.sh

如果出錯,請訪問:深入分析 afl / qemu-mode(qemu模式) / afl-unicorn 編譯及安裝存在的問題以及相應的解決方案

如果要對不同架構的二進制文件進行黑盒 fuzz,需要在編譯 qemu 腳本前,指定相應的架構,舉個例子,要在 x86 架構下,fuzz arm 架構的程序,需要運行如下命令

CPU_TARGET=arm ./build_qemu_support.sh

編譯成功後,即可進行黑盒 fuzz

afl-fuzz -Q -m none -i in -o out target_binary @@

0x60 實戰分析

0x61 源碼分析

編寫個 demo,舉例來說。這裏爲了體現 afl 的插樁功能,特意多寫了幾個 if 分支,在分支深處,會發生棧溢出

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

int main(int argc, char const *argv[])
{
	if(argc != 2)
	{
		printf("null args!\n");
		return -1;
	}

	/* Get file state */
	struct stat fstat;
	if(stat(argv[1], &fstat))
	{
		printf("Failed ^_^\n");
		return -1;
	}

	/* Open file */
	FILE * fd = NULL;
	fd = open(argv[1], O_RDONLY);
	if(fd == -1)
	{
		printf("open file failed!\n");
		return -1;
	}

	/* Select */
	char buf[15];
	if(read(fd, buf, 2) == -1)
	{
		printf("read failed!");
		return -1;
	}

	if(buf[0] == 'a' && buf[1] == 'b')
	{
		if(read(fd, buf, 4) != -1)
		{
			if(buf[2] == 's')
			{
				read(fd, buf, fstat.st_size - 6);
				printf("%s\n", buf);
			}
		}
	}

	return 0;
}

0x62 源碼插樁

使用 afl 的 LLVM 模式編譯,即對源代碼進行插樁

afl-clang afl_demo.c -o afl_demo

每一個 if 語句代表每一個條件分支,在 IDA 的反彙編窗口現實的就是一個基本塊。一個 if 語句就是一個新分支,如下所示,我們用 afl-clang 編譯後的反彙編代碼,可以看到,在每個 if 語句中,都已經自動插樁,_afl_maybe_log 是插樁函數,用來反饋路徑
在這裏插入圖片描述

0x63 生成語料庫

新建 in 目錄,在目錄下新建文件,寫入種子,變異算法會根據此種子變異生成各種測試用例,餵給程序,比如寫入以下信息(根據代碼,當第一個字符爲 a,第二個字符爲 b,第五個字符 爲 s,可能會發生溢出)

abttdcccccccccccccccccccccccccaaaaaaaaaaaaaaaaaaaaaaadddddddddddddddddddddddddddd

0x64 開始 fuzzing

root@lys-virtual-machine:~/Documents/test# afl-fuzz -m none -i in -o out ./afl_demo @@
afl-fuzz++2.60d based on afl by Michal Zalewski and a big online community
[+] afl++ is maintained by Marc "van Hauser" Heuse, Heiko "hexcoder" Eißfeldt and Andrea Fioraldi
[+] afl++ is open source, get it at https://github.com/vanhauser-thc/AFLplusplus
[+] Power schedules from github.com/mboehme/aflfast
[+] Python Mutator and llvm_mode whitelisting from github.com/choller/afl
[+] afl-tmin fork server patch from github.com/nccgroup/TriforceAFL
[+] MOpt Mutator from github.com/puppet-meteor/MOpt-AFL
[*] Getting to work...
[+] Using exploration-based constant power schedule (EXPLORE)
[+] You have 8 CPU cores and 2 runnable tasks (utilization: 25%).
[+] Try parallel jobs - see /usr/local/share/doc/afl/parallel_fuzzing.md.
[*] Checking CPU core loadout...
[+] Found a free CPU core, try binding to #0.
[*] Checking core_pattern...
[!] WARNING: Could not check CPU scaling governor
[*] Setting up output directories...
[+] Output directory exists but deemed OK to reuse.
[*] Deleting old session data...
[+] Output dir cleanup successful.
[*] Scanning 'in'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,time:0,orig:testcases.txt'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
    len = 81, map size = 9, exec speed = 199 us
[+] All test cases processed.

[+] Here are some useful stats:

    Test case count : 1 favored, 0 variable, 1 total
       Bitmap range : 9 to 9 bits (average: 9.00 bits)
        Exec timing : 199 to 199 us (average: 199 us)

[*] No -t option specified, so I'll use exec timeout of 20 ms.
[+] All set and ready to roll!

fuzz 窗口
在這裏插入圖片描述
可以看到,已經有 crash 出現,當 cycles done 爲綠色時,表示可以停止 fuzz 了。

0x65 結果分析

我們分析一下結果,根據 fuzz 時輸入的命令,我們的結果是輸出在 out 目錄下

root@lys-virtual-machine:~/Documents/test/out# tree
.
├── cmdline
├── crashes
│   ├── id:000000,sig:11,src:000001,time:600493+000003,op:splice,rep:64
│   ├── id:000001,sig:11,src:000003,time:600898,op:havoc,rep:64
│   └── README.txt
├── fuzz_bitmap
├── fuzzer_stats
├── hangs
├── plot_data
└── queue
    ├── id:000000,time:0,orig:testcases.txt
    ├── id:000001,src:000000,time:3,op:flip1,pos:0,+cov
    ├── id:000002,src:000000,time:6,op:flip1,pos:1,+cov
    └── id:000003,src:000000,time:3406,op:havoc,rep:2,+cov

3 directories, 11 files
  • crashes:導致目標接收致命 signal 而崩潰的獨特測試用例
  • fuzzer_stats:afl-fuzz 的運行狀態
  • hangs:導致目標超時的獨特測試用例
  • plot_data:用於 afl-plot 繪圖
  • queue:存放所有具有獨特執行路徑的測試用例

afl-plot 可以繪製更加直觀的結果,利用的就是 fuzzer 生成的 plot_data 文件。當然,要使用 afl-plot ,需要先安裝 apt-get install gnuplot

root@lys-virtual-machine:~/Documents/test# afl-plot out result/
progress plotting utility for afl-fuzz by Michal Zalewski

[*] Generating plots...
[*] Generating index.html...
[+] All done - enjoy your charts!

這樣就可以在 result 目錄下,生成 html 和圖片文件,如下所示
在這裏插入圖片描述

  • 第一幅圖:路徑覆蓋變化,pending fav 數量變爲零並且 total paths 數量基本上沒有再增長時,說明 fuzzer 有新發現的可能性就很小了
  • 崩潰和超時的變化
  • 執行速度的變化

要想重新找到 bug,直接輸入 crash 目錄下的測試用例即可。

0x70 總結

AFL 作爲一款優秀的 fuzz 工具,通過源碼插樁的方式,計算代碼覆蓋率,再以此爲基礎,對語料庫(種子文件)不斷進行變異,從而達到增大代碼覆蓋率的效果。本文主要講解其一般用法,其效率最高的也是 LLVM 模式下的源碼插樁,結合具體實例,進行 fuzz 測試。對 C/C++ 代碼編譯成的二進制文件,雖然 AFL 也提供基於 qemu 的無源碼黑盒 fuzz,但是效率低,發現漏洞的可能性小。所以最好採用 AFL 的一般模式,即基於源碼插樁的 fuzz 測試。

在沒有源碼的情況下,如果想直接使用二進制插樁,需要安裝 qemu-mode 或者 unicorn 模式,安裝可能會出現很多問題,歡迎訪問另外一篇博客:深入分析 afl / qemu-mode(qemu模式) / afl-unicorn 編譯及安裝存在的問題以及相應的解決方案


  1. https://www.freebuf.com/articles/system/191543.html ↩︎

  2. https://www.cnblogs.com/codingmengmeng/p/7456539.html ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章