AFL學習(一)對於AFL插樁的理解

weixin_43820701發表於2020-11-08

前言

最近剛剛接觸AFL(american fuzzy lop),打算先通過閱讀AFL原始碼來進行學習,在讀原始碼之前我看了看AFL技術白皮書(Technical “whitepaper” for afl-fuzz),本文主要寫一下我目前對於AFL插樁的粗淺理解,如有錯誤,懇請指正。

參考資料:
白皮書原文

一、程式碼覆蓋率及其相關概念

在介紹AFL插樁之前,先簡單介紹一下相關的基本概念

1.程式碼覆蓋率
程式碼覆蓋率是一種度量程式碼的覆蓋程度的方式,也就是指原始碼中的某行程式碼是否已執行。其計量方式很多,但無論是GCC的GCOV還是LLVM的SanitizerCoverage,都提供函式(function)、基本塊(basic-block)、邊界(edge)三種級別的覆蓋率檢測,更具體的細節可以參考LLVM的官方文件。

2.基本塊(Basic Block)
縮寫為BB,指一組順序執行的指令,BB中第一條指令被執行後,後續的指令也會被全部執行,每個BB中所有指令的執行次數是相同的,也就是說一個BB必須滿足以下特徵:

只有一個入口點,BB中的指令不是任何跳轉指令的目標。

只有一個退出點,只有最後一條指令使執行流程轉移到另一個BB

3.邊(edge)
AFL的技術白皮書中提到fuzzer通過插樁程式碼捕獲邊(edge)覆蓋率。那麼什麼是edge呢?我們可以將程式看成一個控制流圖(CFG),圖的每個節點表示一個基本塊,而edge就被用來表示在基本塊之間的跳轉。知道了每個基本塊和跳轉的執行次數,就可以知道程式中的每個語句和分支的執行次數,從而獲得比記錄BB更細粒度的覆蓋率資訊。

4.元組(tuple)
具體到AFL的實現中,使用二元組(branch_src, branch_dst)來記錄當前基本塊 + 前一基本塊 的資訊,從而獲取目標的執行流程和程式碼覆蓋情況。

二、AFL插樁

什麼是插樁?
在AFL編譯檔案時候afl-gcc會在規定位置插入樁程式碼,可以理解為一個個的探針(但是沒有暫停功能),在後續fuzz的過程中會根據這些樁程式碼進行路徑探索,測試等。

AFL通過插樁的形式注入到被編譯的程式中,實現對分支(branch、edge)覆蓋率的捕獲,以及分支節點計數。程式碼大致如下:

cur_location = <COMPILE_TIME_RANDOM>;
shared_mem[cur_location ^ prev_location]++;//將當前塊和前一塊異或儲存到shared_mem[]
prev_location = cur_location >> 1;//cur_location右移1位區分從當前塊到當前塊的轉跳

程式碼十分簡單,我們來分析一下

第一行就是用一個隨機數cur_location 標記當前的塊。
之後將當前塊和前一塊異或儲存到shared_mem[],其中shared_mem[]陣列是一個被呼叫者傳入插樁二進位制的64Kb SHM大小的區域。
最後一行將cur_location右移一位作為prev_location,這樣就完成了對兩個塊的路徑的標記。

這裡就會有幾個問題

1.為什麼要將cur_location 和 prev_location進行異或處理,如果進行其他處理效果會怎樣?
得到的答案是可能作者覺得這樣處理得到的結果還不錯,這裡暫時還沒想明白。

2.為什麼要將cur_location進行除以二之後再賦給prev_location?
這裡是為了區分兩個塊之間不同方向的路徑,設想直接將cur_location作為下一個塊的pre_location,那麼這將很難鑑別A ^ B和B ^ A,這樣顯然不是我們想要的結果。

此外,在分支較多的情況下,產生的資料會發生衝突,下圖就是白皮書中貼出的資料。

在這裡插入圖片描述

相關文章