深圳seo優(yōu)化服務(wù)商seo引擎搜索入口
一、AFL簡介
AFL(American Fuzzy Lop)號(hào)稱是當(dāng)前最高級(jí)的Fuzzing 測試工具之一 ,是由安全研究員Micha? Zalewski(@lcamtuf)開發(fā)的一款 基于覆蓋引導(dǎo)(Coverage-guided)的 模糊測試工具,它 通過記錄輸入樣本的代碼覆蓋率,從而調(diào)整輸入樣本以提高覆蓋率,增加發(fā)現(xiàn)漏洞的概率。
AFL 采用新型的 編譯時(shí)插樁和 遺傳算法自動(dòng)發(fā)現(xiàn)新的測試用例,這些用例會(huì)觸發(fā)目標(biāo) 二進(jìn)制文件中的新內(nèi)部狀態(tài)。這大大改善了模糊測試的代碼覆蓋范圍。
AFL既可以對(duì)源碼進(jìn)行編譯時(shí)插樁,也可以使用AFL的QEMU mode對(duì)二進(jìn)制文件進(jìn)行插樁,但是前者的效率相對(duì)來說要高很多。
AFL的 優(yōu)點(diǎn)是可以輕松部署,配置相對(duì)簡單,測試效率相對(duì)較高。 原生的AFL僅適配于C/C++程序的測試,不過目前已經(jīng)衍生出很多分支,用于適配其他語言的模糊測試,如針對(duì)JAVA程序的Kelinci等 。
其 工作流程大致如下:
?? ?①從源碼編譯程序時(shí)進(jìn)行插樁,以記錄代碼覆蓋率(Code Coverage);
?? ?②選擇一些輸入文件,作為初始測試集加入輸入隊(duì)列(queue);
?? ?③將隊(duì)列中的文件按一定的策略進(jìn)行“突變”;
?? ?④如果經(jīng)過變異文件更新了覆蓋范圍,則將其保留添加到隊(duì)列中;
?? ?⑤上述過程會(huì)一直循環(huán)進(jìn)行,期間觸發(fā)了crash的文件會(huì)被記錄下來。

AFL變異的方式
AFL是采用遺傳算法,基于變異生成的測試用例,變異的主要類型有下面這幾種:
bitflip:按位翻轉(zhuǎn),1變?yōu)?,0變?yōu)? arithmetic:整數(shù)加/減算術(shù)運(yùn)算 interest:把一些特殊內(nèi)容替換到原文件中。所謂的特殊內(nèi)容是AFL預(yù)設(shè)的一些比較特殊的數(shù),比如可能造成溢出的數(shù)。 dictionary:把自動(dòng)生成或用戶提供的token替換/插入到原文件中 havoc:“大破壞”,是前面幾種變異的組合 splice:“連接”,此階段會(huì)將兩個(gè)文件拼接起來得到一個(gè)新的文件
二、AFL安裝和使用
利用AFL進(jìn)行模糊測試的大概 過程:①插樁編譯;②設(shè)置種子測試用例;③開始fuzz 。模糊測試對(duì)于軟件測試的幫助很大,其不受限于被測系統(tǒng)的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)和復(fù)雜程度。
2.1、安裝
AFL 自帶定制版本的 gcc 和 clang 編譯器,建議選擇 LLVM 的 clang 編譯器,可以加快 fuzz 的速度。
下載AFL源碼( GitHub - google/AFL: american fuzzy lop - a security-oriented fuzzer),解壓后,編譯安裝:
AFL使用afl-gcc編譯 :
make
sudo make install? #? 安裝到系統(tǒng)目錄
AFL使用afl-clang編譯 :
cd?llvm_mode
make
cd ..
sudo make install?
查看路徑可以看到afl安裝的文件:

作用分別為
- afl-gcc 和afl-g++ 分別對(duì)應(yīng)的是gcc 和g++ 的封裝
- afl-clang 和afl-clang++ 分別對(duì)應(yīng)clang 的c 和c++ 編譯器封裝à。
- afl-fuzz 是AFL 的主體,用于對(duì)目標(biāo)程序進(jìn)行fuzz。
- afl-analyze 可以對(duì)用例進(jìn)行分析,通過分析給定的用例,看能否發(fā)現(xiàn)用例中有意義的字段。
- afl-qemu-trace 用于qemu-mode ,默認(rèn)不安裝 ,需要手工執(zhí)行qemu-mode 的編譯腳本進(jìn)行編譯,后面會(huì)介紹。
- afl-plot 生成測試任務(wù)的狀態(tài)圖
- afl-tmin 和afl-cmin 對(duì)用例進(jìn)行簡化
- afl-whatsup 用于查看fuzz 任務(wù)的狀態(tài)
- afl-gotcpu 用于查看當(dāng)前CPU 狀態(tài)
- afl-showmap 用于對(duì)單個(gè)用例進(jìn)行執(zhí)行路徑跟蹤
afl-fuzz指令:

下面我們進(jìn)行一個(gè)簡單的AFL實(shí)驗(yàn)了解其使用流程。
2.2、準(zhǔn)備被測程序
首先創(chuàng)建一個(gè)簡單的test.c源文件,里面放入我們要測的c程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h> int AFLTest(char *str)
{int len = strlen(str);if(str[0] == 'A' && len == 6){raise(SIGSEGV);//如果輸入的字符串的首字符為A并且長度為6,則異常退出}else if(str[0] == 'F' && len == 16){raise(SIGSEGV);//如果輸入的字符串的首字符為F并且長度為16,則異常退出}else if(str[0] == 'L' && len == 66){raise(SIGSEGV);//如果輸入的字符串的首字符為F并且長度為66,則異常退出}else{printf("it is good!\n");}return 0;
}int main(int argc, char *argv[])
{char buf[100]={0};gets(buf);//存在棧溢出漏洞printf(buf);//存在格式化字符串漏洞AFLTest(buf);return 0;
}
2.3、使用 afl-gcc?插樁編譯
(編譯插樁是指在代碼編譯期間修改或新增代碼)
用 AFL 編譯目標(biāo)源碼,其目的在于插樁,讓編譯得到的程序,反饋路徑覆蓋。AFL 自帶定制版本的 gcc 和 clang 編譯器,建議選擇 LLVM 的 clang 編譯器,可以加快 fuzz 的速度。
編譯過程和普通gcc編譯也是一樣,除了使用的命令需要帶上afl-前綴,因此
afl-gcc -g -o test? test.c?? #?針對(duì)c文件, -g選項(xiàng)為使用gdb調(diào)試所需,如果不調(diào)試就不需加此選項(xiàng)。 在linux下編譯鏈接程序,如果不加-o參數(shù),生成的binary代碼的名字都是默認(rèn)的a.out

可以看到在編譯過程中,編譯器已經(jīng)提示存在漏洞,不理會(huì),用AFL去測試 。
編譯后會(huì)有插樁符號(hào),使用下面的命令可以驗(yàn)證這一點(diǎn): readelf -s ./test | grep afl

注:
針對(duì)c++文件,則使用 afl-g++ -g -o test? test.cpp
注意在編譯項(xiàng)目時(shí),通常有Makefile,這是就需要在Makefile中添加內(nèi)容: gcc/g++重新編譯程序的方法是:
CC=/path/to/afl/afl-gcc ./configure
make clean all
對(duì)于一個(gè)C++程序,要設(shè)置:
CXX=/path/to/afl/afl-g++.
afl-clang和afl-clang++的使用方法類似。
2.4、準(zhǔn)備種子語料庫
2.4.1、搜集種子語料庫
作為模糊測試,AFL需要提供初始的種子輸入, 變異算法會(huì)根據(jù)此種子變異生成各種測試用例,喂給程序。但實(shí)際上,你完全可以提供任何無意義的輸入作為種子,模糊測試也一般能達(dá)到效果,只不過效率會(huì)低一些而已,是否提供有意義種子?提供多少?無外乎在種子獲取難度和測試的效率要求之間進(jìn)行權(quán)衡而已。
這里,借用 Freebuf 提供的資料,給出一些開源的語料庫
- libav sample s
- ffmpeg samples
- fuzzdata
- moonshine
- afl generated image test sets
- fuzzer-test-suite
事實(shí)上很多程序也會(huì)自帶一些案例,也可以作為測試用例。
在這里我們生成一好一壞兩個(gè)種子語料庫:
mkdir good-seeds bad-seeds
echo '1+1' > good-seeds/any-seed
echo 'this is a bad seed' > bad-seeds/any-seed
2.4.2、精簡語料庫
找到語料庫之后,最好能夠進(jìn)行修剪, 合并重復(fù)用例,裁剪體積。 afl 推薦的每個(gè)用例體積小于 1KB,不然會(huì)影響 fuzz 的效率。
2.4.2.1、去重
afl-cmin?可以精簡語料庫,去掉可能重復(fù)的測試用例,可大大減少無用的 fuzz 用例
afl-cmin -i input_dir -o output_dir -- /path/to/tested/program [params]

更多的時(shí)候,我們是從文件中獲取輸入,因此,往往使用 @@ 替代 params(參數(shù)),即
afl-cmin -i input_dir -o output_dir -- /path/to/tested/program @@
2.4.2.2、裁剪體積
afl-tmin? 可以縮短文件體積,因?yàn)?/span> afl 要求測試用例的大小最好小于 1KB ,因此最好將精簡后的用例進(jìn)一步縮小體積。afl-tmin 有兩種工作模式, instrumented mode 和 crash mode 。默認(rèn)的工作方式是instrumented mode
afl-tmin -i input_file -o output_file -- /path/to/tested/program [params] @@
由于 afl-cmin 一次性只能精簡單個(gè)文件,如果用例特別多,需要手動(dòng)花費(fèi)很長時(shí)間,其實(shí)一條簡單的 shell 腳本即可完成
for i in *; do afl-tmin -i $i -o tmin-$i -- ~/path/to/tested/program [params] @@; done;
2.5、開始測試
在執(zhí)行afl-fuzz前,如果系統(tǒng)配置為將核心轉(zhuǎn)儲(chǔ)文件(core)通知發(fā)送到外部程序,將導(dǎo)致將崩潰信息發(fā)送到Fuzzer之間的延遲增大,進(jìn)而可能將崩潰被誤報(bào)為超時(shí),所以我們得臨時(shí)修改core_pattern文件:? echo core >/proc/sys/kernel/core_pattern
對(duì)于可以直接從stdin讀取輸入的目標(biāo)程序來說,執(zhí)行命令, afl-fuzz? -i good-seeds/ -o good-outputs -- ./test
對(duì)從文件讀取輸入的目標(biāo)程序來說,要用“@@”: afl-fuzz -i ./in/ -o ./out/ -Q ./readelf -a @@? ? # 開始fuzz, @@表示從in 文件夾中找elf作為輸入 ,實(shí)際上是在執(zhí)行readelf -a 文件名

ctrl-C結(jié)束fuzz,共找到了6個(gè)crash,可以看到當(dāng)前目錄下已經(jīng)多出了good-outputs目錄,這是本次模糊測試的結(jié)果。
process?timing:展示的是fuzzer的運(yùn)行時(shí)間,也可以看到最近一次發(fā)現(xiàn)新路徑的時(shí)間,最近一次崩潰的時(shí)間和最近一次掛起的時(shí)間。

overall result:運(yùn)行的總周期數(shù),總路徑數(shù),崩潰次數(shù)和掛起次數(shù)

cycle?progress:fuzzer正在處理的測試樣例的編號(hào)和由于超時(shí)放棄的的輸入數(shù)量,從而得知fuzzer處理了多少樣例,隊(duì)列中還剩多少

map coverage:
第一行顯示的是已經(jīng)命中了多少分支元組與位圖可以容納的數(shù)量的比例。左邊的是當(dāng)前輸入,右邊的是整個(gè)輸入語料庫的值。
第二行顯示的是二進(jìn)制文件中元組命中計(jì)數(shù)的變化,如果對(duì)于所有嘗試的輸入,每個(gè)采用的分支都采用固定的次數(shù)。讀數(shù)顯示為1.00, 當(dāng)設(shè)法觸發(fā)每個(gè)分支的其他命中計(jì)數(shù)時(shí),讀數(shù)向8.00移動(dòng)(8位映射命中每一位),但可能永遠(yuǎn)不會(huì)到達(dá)極限。
這些值可以用于比較不同的模糊測試對(duì)于同一檢測二進(jìn)制文件的覆蓋范圍。

stage progress:顯示了正在測試的策略(AFL變異方式),進(jìn)度,總執(zhí)行次數(shù)和執(zhí)行速度。一般正常的執(zhí)行速度應(yīng)該在500以上。

findings in depth:
這一部分的數(shù)據(jù)對(duì)一般的使用者用處不大,包括基于應(yīng)用到代碼的最小化算法獲得的fuzzer最喜歡的路徑數(shù)和真正獲取更好的邊緣覆蓋率的測試用例數(shù),還有超時(shí)和崩潰的計(jì)數(shù)器,注意這里的timeout和hang有所不同,timeout計(jì)數(shù)器包括了所有超過超時(shí)的測試用例,即使它們沒有超過超時(shí)足夠的幅度而分類為hangs(掛起)

fuzzing strategy yields:用于追蹤各種fuzzing策略所獲得的路徑和嘗試執(zhí)行的次數(shù)的比例,用于驗(yàn)證各種方法的有效性。

path geometry:
第一個(gè)數(shù)據(jù)指的是fuzzing過程中達(dá)到的路徑深度,通常用戶提供的測試用例視為1級(jí),傳統(tǒng)模糊測試從中獲得的測試用例視為2級(jí),通過它們作為后續(xù)模糊測試回合的輸入得到的結(jié)果為3級(jí),以此類推。
下一個(gè)數(shù)據(jù)pending指的是還有多少輸入數(shù)據(jù)沒有經(jīng)過任何測試。pend fav時(shí)指fuzzer在這個(gè)隊(duì)列中真正想到達(dá)的條目。接下來的是這個(gè)fuzzing部分找到的新路徑數(shù)量和在進(jìn)行并行化fuzzing時(shí)從其他fuzzer實(shí)例導(dǎo)入的路徑數(shù)量 。
最后一個(gè)數(shù)據(jù)可以衡量觀測到痕跡的一致性,如果程序?qū)ο嗤妮斎胧冀K表現(xiàn)相同,則為100%,若數(shù)值較低但仍然顯示為紫色,測試過程不太可能受到負(fù)面影響,而顯示紅色說明出現(xiàn)了問題,afl可能難以區(qū)分調(diào)整輸入文件的有意義的效果和幻象效果

2.6、fuzz結(jié)果分析

crashes:導(dǎo)致目標(biāo)接收致命signal而崩潰的獨(dú)特測試用例
queue:存放所有具有獨(dú)特執(zhí)行路徑的測試用例。
AFL輸出文件:
- crashes/README.txt:保存了目標(biāo)執(zhí)行這些crashes文件的命令行參數(shù)。
- hangs:導(dǎo)致目標(biāo)超時(shí)的獨(dú)特測試用例。
- fuzzer_stats:afl-fuzz的運(yùn)行狀態(tài)。
- plot_data:用于afl-plot繪圖。
查看第1個(gè)crash,發(fā)現(xiàn)符合棧溢出漏洞的crash情況(最長輸入100字符):

查看第2個(gè)crash:發(fā)現(xiàn)符合首字符為A且字符串長度為6的異常退出情況:

查看第3個(gè)crash:發(fā)現(xiàn)符合首字符為F且字符串長度為16的異常退出情況:

查看第4個(gè)crash,發(fā)現(xiàn)符合棧溢出漏洞的crash情況(最長輸入100字符):

查看第5個(gè)crash,發(fā)現(xiàn)符合棧溢出漏洞的crash情況(最長輸入100字符):

查看第6個(gè)crash:發(fā)現(xiàn)符合輸入的字符串的首字符為L并且長度為66的異常退出情況:

AFL編譯后的是一個(gè)普通的可執(zhí)行文件,可以直接在命令行使用 ./test啟動(dòng)運(yùn)行,然后此時(shí)你需要在終端進(jìn)行輸入,然后又會(huì)在終端獲得輸出:


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

三、并行fuzz測試
在使用afl-fuzz時(shí),每個(gè)進(jìn)程只會(huì)占用一個(gè)CPU核心。如果我們的機(jī)器是多核處理器,我們可以通過進(jìn)行分布式fuzz來提高fuzz速度。
首先先看自己有多少內(nèi)核: afl-gotcpu

以上可以看出可以運(yùn)行6~8個(gè)實(shí)例。
首先指定主實(shí)例 -M 用于主實(shí)例,將 -S 添加到所有從屬實(shí)例。注意, 這里-o跟的參數(shù)一定要保持一致,這個(gè)是用來同步各個(gè)fuzz進(jìn)程的。
- 主實(shí)例:?? afl-fuzz? -M master? -i? good-seeds/? ?-o good-outputs?? -m none? --?? ./test
- 從實(shí)例1 : afl-fuzz? -S slave1? ?-i? good-seeds/? ?-o good-outputs?? -m none? --?? ./test
- 從實(shí)例2: afl-fuzz? -S slave2? ?-i? good-seeds/? ?-o good-outputs?? -m none? --?? ./test
若分布式意外退出可以使用以下命令繼續(xù)fuzz任務(wù):
- 主實(shí)例:?? afl-fuzz? -M master?? -i- ? -o good-outputs? -m none? --?? ./test
- 從實(shí)例1 : afl-fuzz? -S slave1??? -i- ? -o good-outputs?? -m none? --?? ./test
- 從實(shí)例2: afl-fuzz? -S slave2?? -i- ? -o good-outputs? -m none? --?? ./test
運(yùn)行后會(huì)在good_outputs文件夾會(huì)多出三個(gè)不同的文件夾master、slave1、slave2:

四、黑盒 fuzz(無源碼AFL測試)
以上 fuzz 過程,依賴于我們有程序的源碼,并且在編譯過程中進(jìn)行了插樁,但很多時(shí)候,我們并 沒有源碼,這時(shí)候就要靠 afl 提供的 qemu_mode 模式了( 只要在之前的命令的基礎(chǔ)上加上-Q的參數(shù)即可? )。原版本的 afl qemu 模式由于版本過老,已不能正常運(yùn)行,推薦使用 github 上的 ? AFLplusplus? 或者? afl-unicorn。AFLplusplus 更容易安裝,而 afl-unicorn 針對(duì) qemu 模式更加友好。
4.1、qemu_mode模式安裝
無論是下載的哪個(gè)版本的 afl,根目錄下都會(huì)有 qemu_mode 文件夾,進(jìn)入此目錄,運(yùn)行以下腳本,如果沒有出錯(cuò),就代表 qemu_mode 成功了:
cd qemu_mode
sudo ./build_qemu_support.sh
cd ..
make install

安裝注意事項(xiàng):
- 修改build_qemu_support.sh文件中的 QEMU_URL=" https://download.qemu.org/qemu- ${VERSION}.tar.xz",不然提示404
- 參考 我的AFL入門之路 - 知乎 (zhihu.com) ,更改一些配置:
-
4.2、示例
>>> 繼續(xù)使用上面簡單c代碼進(jìn)行測試,但 這次采用gcc進(jìn)行編譯,而不是afl-gcc。將test.c編譯為test2:

執(zhí)行命令: afl-fuzz -Q -i good-seeds/ -o good-outputs??--? ./test2

可以看出:同樣的程序,在qemu 模式下比在源碼編譯插樁的模式下會(huì)慢很多。(通過觀察stage progress下的exec speed)
>>> 一個(gè)Fuzz實(shí)例
測試readelf。 由于readelf的輸入其實(shí)就是elf文件,因此需要在in目錄下放一個(gè)輸入elf。 按照流程創(chuàng)建文件夾和測試用的elf。?
cd AFL/testcases/mytest
mkdir in out
cd in
cp ../../others/elf/small_exec.elf??.? ? # afl目錄中自帶一些常用文件的testcase
cd ..
cp /usr/bin/readelf .? ? # 將readelf復(fù)制到mytest 目錄下
afl-fuzz -i ./in/ -o ./out/ -Q ./readelf -a @@? ? #? 開始fuzz,@@表示從in文件夾中找elf作為輸入,實(shí)際上是在執(zhí)行readelf -a 文件名

五、參考
google/AFL: american fuzzy lop - a security-oriented fuzzer (github.com)
入門AFL | I'm dev (i-m.dev)
我的AFL入門之路 - 知乎 (zhihu.com)
AFL實(shí)戰(zhàn)_Elwood Ying的博客-CSDN博客
經(jīng)典 Fuzzer 工具 AFL 模糊測試指南_swift fuzzer工具_(dá)江下楓的博客-CSDN博客
模糊測試技術(shù)線上分享_嗶哩嗶哩_bilibili