? ? 以使用afl-gcc進行插樁后的AFL運行為例。我們來學(xué)習(xí)AFL的運行。main函數(shù)中首先識別參數(shù)opt?=?getopt(argc,?argv,?"+i:o:f:m:t:T:d:n:C:B:S:M:x:Q")。然后在setup_shm()這個函數(shù)中初始化MAP_SIZE(2^16)大小的map用來父進程子進程間的信息共享。然后perform_dry_run()這個函數(shù)把初始的種子跑一遍,觀察是否有問題。
先補充一些知識:
fd文件(File?descriptor)
????所有都可以抽象成文件,比如普通的文件、目錄、塊設(shè)備、字符設(shè)備、socket、管道等等。當通過一些系統(tǒng)調(diào)用(如open/socket等),會返回一個fd(就是一個數(shù)字)給你,然后根據(jù)這個fd對應(yīng)的文件進行操作,比如讀、寫。linux默認對每個進程最大能打開的fd的個數(shù)是1024(軟限制是1024,硬限制4096)。
管道PIPE
管道初始化函數(shù) int?pipe(int?pipefd[2]);?成功:0;失?。?1
fd[0]?→?r;?fd[1]?→?w,就像0對應(yīng)標準輸入,1對應(yīng)標準輸出一樣。向管道文件讀寫數(shù)據(jù)其實是在讀寫內(nèi)核緩沖區(qū)。在后面AFL的forkserer中父進程和子進程交互就使用了st_pipe[2],ctl_pipe[2]。
這里介紹幾個很重要的數(shù)據(jù)結(jié)構(gòu)和函數(shù):
cull_queue()
? ? 種子的數(shù)據(jù)結(jié)構(gòu)為:
struct?queue_entry?{
? ??u8*?fname;??????????????????????????/*?File?name?for?the?test?case??????*/
? ??u32?len;????????????????????????????/*?Input?length?????????????????????*/
? ??u8??cal_failed,?????????????????????/*?Calibration?failed???????????????*/
? ? trim_done,??????????????????????/*?Trimmed??????????????????????????*/
? ? was_fuzzed,?????????????????????/*?Had?any?fuzzing?done?yet?????????*/
? ? passed_det,?????????????????????/*?Deterministic?stages?passed??????*/
? ? has_new_cov,????????????????????/*?Triggers?new?coverage????????????*/
? ? var_behavior,???????????????????/*?Variable?behavior????????????????*/
? ? favored,????????????????????????/*?Currently?favored????????????????*/
? ? fs_redundant;???????????????????/*?Marked?as?redundant?in?the?fs????*/
? ? u32?bitmap_size,????????????????????/*?Number?of?bits?set?in?bitmap?????*/
? ? exec_cksum;?????????????????????/*?Checksum?of?the?execution?trace??*/
? ? u64?exec_us,????????????????????????/*?Execution?time?(us)??????????????*/
? ? handicap,???????????????????????/*?Number?of?queue?cycles?behind????*/
? ? depth;??????????????????????????/*?Path?depth???????????????????????*/
? ? u8*?trace_mini;?????????????????????/*?Trace?bytes,?if?kept?????????????*/
? ? u32?tc_ref;?????????????????????????/*?Trace?bytes?ref?count????????????*/
? ? struct?queue_entry?*next,???????????/*?Next?element,?if?any?????????????*/
?????????????????????*next_100;???????/*?100?elements?ahead???????????????*/
};
????我們知道種子是一系列的,每次通過程序運行該種子的時間、是否產(chǎn)生新路徑等信息對種子進行打分排序,對一些沒有貢獻的種子,pass_det這一項就置為表示循環(huán)fuzz的時候?qū)⑻^該種子。
run__target()
????不管是在一開始的perform_dry_run()還是后面測試過程中一直循環(huán)的fuzz_one(),這個是運行插樁程序的函數(shù)。在里面先是查詢有沒有forkserver這么一個子進程,如果沒有就建立一個forkserver,同時建立好管道。
????在init_forkserver這個環(huán)節(jié),會生成兩個管道ctl_pipe,?st_pipe,分別綁定fd?198,fd?199.然后開始運行程序并且進程間通信。一旦init了child進程就不會退出,每次有新的測試樣例,就直接運行,運行的時候child進程fork一個grandchild進程,這個進程一直運行_afl_store()這個函數(shù)對shm的bitmap進行修改。
????如果是第一次運行(一般在perform_dry_run()這個函數(shù)中),就會創(chuàng)建一個子進程。子進程會一直用testcase這個緩沖區(qū)中的種子運行程序,通過管道把執(zhí)行路徑交互給父進程。交互過程如下:

? ? 每次運行run_target()函數(shù)都會在ctl_pipe寫入值,喚醒卡死在read等待的子進程,子進程就可以fork一個孫進程,孫進程執(zhí)行程序,把所有程序塊中插樁的部分執(zhí)行一遍,把結(jié)果寫入共享的內(nèi)存區(qū),就實現(xiàn)了執(zhí)行路徑的記錄。
cur_location?=?<COMPILE_TIME_RANDOM>;
shared_mem[cur_location?^?prev_location]++;?
prev_location?=?cur_location?>>?1;
????這里的cur_location和prev_location都是程序塊的id,這里使用[cur_location?^?prev_location]意為記錄邊,因為如果一個程序有a、b、c三個基本塊,a->b->c 和a->c->b是不同的執(zhí)行路徑,但是只用基本塊記錄的話都是a、b、c,用邊記錄的話可以跟好比對運行路徑。
? ? 關(guān)于種子變異,這個是AFL的確定性變異(位翻轉(zhuǎn)、拼接等)和非確定性變異組合而成,在afl-fuzz.c中可以更好學(xué)習(xí),就不在這里贅述。
? ? 就先寫到這里,大家對AFL還有什么疑問,可以留言,我們可以寫四五六等。