本文系原創,轉載請說明出處:信安科研人
關注微信公眾號 信安科研人,獲取更多網安資訊
一、AFL++簡介
AFL++ 結合現今所有基於AFL框架改進方案,形成一個整體,即取其精華,去其糟粕,形成一款終極版AFL——AFL++。
下面以AFL基礎款框架的幾個部分,分別介紹AFL++結合了哪些改良部分。
縫合塊
AFL基礎款
AFL 是一款基於突變的、覆蓋率引導的FUZZer。 它改變一組測試用例以達到程序中以前未探索的代碼。覆蓋率發生變化時,觸發新覆蓋率的測試用例將保存為測試用例隊列的一部分。
1 基於覆蓋率指標的反饋
AFL的覆蓋率,計算在一次運行中,相應邊執行的次數,次數單位為2的冪(以緩解路徑爆炸)。如果一個用例輸入發現了至少一條邊,也就是創建一個新的桶來裝入新的邊的次數,那么這個用例就是interesting用例,並放入隊列。AFL用一個bitmap把這些裝有邊次數的桶整合起來,一個byte代表一條邊。
2 變異
AFL 的突變分為兩類:確定性突變和破壞性突變。 確定性階段包括對測試用例內容的單一確定性突變,例如位翻轉、加法、用一組常見有趣值(例如 -1、INT_MAX、...)中的整數替換等。 在破壞變異中,突變是隨機堆疊的,並且還包括對測試用例大小的更改(例如,添加或刪除部分輸入用例)。
3 fork 服務器
原理是每當AFL執行一個測試用例時,AFL都會將輸入用例寫入使用IPC機制控制的forkserver中的目標程序。也就是說,父進程fork出一個子進程執行輸入用例,父進程等待結果。這樣可以避免頻繁調用execve()函數。
但是,fork也會有性能瓶頸的問題,AFL提出persistent mode,該模式下不會為每個測試用例fork。 取而代之的是,可以將循環的方式添加到目標程序中,也就是每次迭代執行一個測試用例。
基於智能調度的加強版
現代覆蓋率引導的FUZZer可以實現不同的優先級算法來調度模糊測試工具隊列中的各種元素。 調度程序的目標通常是通過智能的測試用例選擇來提高整體覆蓋率和錯誤檢測。
1 AFLFast
AFLFast的特點是,更注重對低頻的路徑的探索,低頻的路徑是指,在模糊測試中測試用例很難或者是很少到達的路徑,AFLFast發明了一種方式,讓AFL基礎款的測試用例生成更注重程序中的低頻路徑。一共提出兩種問題:
- 為了注重低頻路徑,fuzzer 應該按什么順序挑選種子?
- 可以調整每個種子生成的輸入量嗎?
解決了這兩個問題基本上就能改變測試用例的生成方向。
2 MOpt
至於種子調度的橫向問題,MOPT引入了變異調度。 這項工作探索了使用自定義粒子群優化算法為變異算子賦予不同概率的可能性。這種優化提高了FUZZer 發現覆蓋范圍的能力。MOPT中的AFL 的補丁中,作者將模糊測試階段分為以下兩個模塊:Pilot模塊根據測試用例產生的效率分配可能性;Code 模塊對測試用例進行變異,並考慮用例在Pilot期間產生的可能性。
基於繞過障礙的加強版
一般來說,基於覆蓋率引導的FUZZer會遇到阻礙其探索代碼的障礙。典型的障礙如字符串和校驗和檢查這類的進行比較的代碼。
1 LAF-Intel
LAF-INTEL是一項旨在繞過艱難的多字節比較的工作,該研究將多字節拆分為多個單字節比較。 這樣,這些比較可以逐字節傳遞,基於覆蓋率引導的FUZZer接收每個字節的反饋。 具體的比較細節如下:
1、簡化>=(<=)操作器為比較鏈,即分解為 >(<)和 == 的兩種比較 ;
2、將有符號整數比較改為鏈式的只有符號比較和無符號比較;
3、將所有位寬為 64、32 或 16 位的無符號整數比較拆分為 8 位多重比較鏈;
2 RedQueen
基於 KAFL 的 REDQUEEN 探索是否能夠繞過硬比較和校驗和檢查,該FUZZer專注於一種稱為輸入到狀態 (Input-To-State,I2S) 的定義的比較,這是一種與至少一個操作數中的輸入直接相關的比較類型。作者表明,很多的校驗或者其他的數字比較屬於這種類型,並開發了一種技術來定位和繞過它們。
變異結構化輸入
FUZZer 的一個常見問題是它們可能會生成大部分無效輸入,從而使解析階段之后的程序狀態無法訪問。一個解決方案是使用輸入模型,有效地減少生成輸入的空間。 這可以讓基於反饋的FUZZer 探索程序中的更深層次的路徑。
AFLSmart
AFLSmart將輸入的測試用例結構化,以類似Peach Fuzzer的人工定義協議結構配置的方式構造輸入,並對結構化的輸入進行結構化的變異,例如,將IEC104協議分為幾個chunk,對chunk本身以及chunk之間之間進行變異。
縫合怪AFL++
簡單的說,一切基於AFL基礎款。
種子調度
AFL++這部分結合的是AFLFast的超強調度算法,調度形式包括 fast, coe, explore, quad, lin, exploit,這些調度對應的是以下參數的功能:
- 從隊列中選擇種子的次數;
- 種子覆蓋率相同生成的輸入的數量;
- 相同覆蓋率下的生成測試用例平均數;
默認的調度算法是exploit,AFL++在這些調度方案的基礎上又添加了mmopt和rare方案。Mmopt 調度增加了最新的種子用例的分數,以深入研究新發現的路徑;Rare 調度忽略了種子的運行時間,並且另外將重點放在邊緣很少被其他種子用例執行后覆蓋的種子上。
變異器
AFL++ 在原有的AFL基礎款的havoc和deteministic添加了很多變異。
1 自定義變異器API
AFL++將變異器API化,以方便調用,以支持學術研究:
自定義變異器允許相關的模糊測試的研究在 AFL++ 之上構建新的調度算法、變異算法等等,而無需像當前許多工具那樣fork 和修補 AFL。插件可以用 C ABI 兼容的語言編寫,甚至可以用 Python 進行原型設計。例如,使用當前的 API,AFLSMART 可以作為 AFL++ 插件完全重寫十次。
目前AFL++實現了以下功能:
- afl_custom_(de)init :初始化AFL++的偽隨機數種子生成器
- afl_custom_queue_get:其是一個回調函數,用於確定自定義的FUZZer是否應該FUZZ當前隊列的用例。
- afl_custom_fuzz:對給定的輸入執行自定義變異。
- afl_custom_havoc_mutation:對給定的輸入執行單個自定義變異。 這種突變與AFL的havoc階段的其他變異策略疊加在一起。
- afl_custom_post_process:在某些情況下,從自定義 mutator 返回的變異數據的格式不適合作為輸入到目標程序執行。例如,當使用 libprotobuf-mutator 時,返回的數據是對應於給定語法的 protobuf 格式,首先需要將其轉換為目標的純文本格式。 在這種情況下,或者要修復校驗和以及大小,用戶可以定義 afl_custom_post_process 函數。
- afl_custom_queue_new_entry:在將新測試用例添加到隊列后調用,這是一個存儲元數據的API。
支持用例修剪的API
修剪用例的目的是減少因為大量產生用例導致格式過於復雜,以至於不符合協議格式。
- afl_custom_init_trim:該API在每次修剪操作開始時被調用並接收初始緩沖區。它返回此次輸入上可能的迭代次數(例如,如果輸入有 n 個元素,其中一個應該被刪除,則返回 n-1)。 如果實現的修剪算法不允許確定(剩余)步驟的數量,那么它可以返回 1 表示可以執行進一步的修剪,這將在 afl_custom_post_trim 返回 0 時執行。
- afl_custom_trim:每次修剪操作都會調用 afl_custom_trim。 它可以記住當前狀態,因此可以保存每次迭代的重新分析的步驟。該API返回修剪后的輸入緩沖區,其返回的數據長度不得超過初始輸入數據。
- afl_custom_post_trim:該API在每次修剪操作后調用以通知修剪步驟是否成功。
2 Input-To-State 變異器
AFL++擴展了基於REDQUEEN的Input-To-State的變異器。首先是着色階段,該階段對輸入用例的每一個字節進行熵增,從而使得FUZZer在fuzz時對某個用例的某個字節的變異速度變緩慢。AFL++擴展着色階段,擴展了着色的區域,以及兩種執行條件。另一個擴展是每次比較下的概率fuzz,如果fuzzer在繞過程序檢查功能代碼的情況下沒能生成interesting用例,則下次將以較低的概率進行fuzz。
3 MOpt Mutator
AFL++ 實現了 MOPT 的 Core 和 Pilot 模式。 除此之外,AFL++ 更新后以便它可以與 Input-To-State mutator 結合使用,AFL++ 支持 MOPT 與標准突變模式的交錯。
插樁
1 LLVM
LLVM主要包含以下兩種插樁方式:
上下文敏感的邊緣覆蓋:edge覆蓋是將每個block被分配的ID與被調用者的唯一ID進行異或運算。
Ngram:在記錄edge時不考慮前一個塊和目標塊,而是考慮目標塊和前 N-1 個塊,其中 N 是 2 到 16 之間的數字。
2 GCC
除了包含舊的 afl-gcc 包裝器,AFL++ 還附帶了一個 GCC 插件。 它包括對延遲初始化和persistent 模式的支持,例如 AFL LLVM 模式。
3 QEMU
該模式針對二進制程序進行模糊測試。
4 Unicornafl
對於像固件這樣的二進制文件,AFL++ 結合了 Voss 模糊測試工具的afl-unicorn 分支,它為 Unicorn Engine 添加了 AFL 支持,稱為 unicornafl。
5 QBDI
AFL++ 可以通過使用 LLVM 的編譯器工具對 Android 庫進行模糊測試,但也可以對閉源庫進行測試。 它支持利用 QuarksLab 的 QBDI Dynamic Binary Instrumentation 框架,用於 Android 原生庫。
二、AFL++使用示例
安裝方式已經在上一篇文章EPF中說過了,這里只介紹怎樣去使用AFL++,以libxml2 庫的fuzz為例.
准備libxml2
下載libxml2:
git clone https://gitlab.gnome.org/GNOME/libxml2.git
cd libxml2
禁用共享庫並啟用ASan和UBSan:
./autogen.sh
./configure --enable-shared=no
export AFL_USE_UBSAN=1
export AFL_USE_ASAN=1
使用AFL++中的clang編譯,~/這里每個主機的環境不同,填上AFLplusplus的絕對地址就可,我的是 ~/Desktop/AFLplusplus,為了普適性這里都寫成如下目錄。
make CC=~/AFLplusplus/afl-clang-fast CXX=~/AFLplusplus/afl-clang-fast++ LD=~/AFLplusplus/afl-clang-fast
工作完成后,開始使用 xmllint 工具(依舊是這個目錄)作為工具對libxml2 進行模糊測試,並從測試文件夾中獲取一些測試用例作為初始種子。
工具xmllint的初始化
mkdir fuzz
cp xmllint fuzz/xmllint_cov
mkdir fuzz/in
cp test/*.xml fuzz/in/
cd fuzz
使用完后,in目錄如下:
fuzz目錄如下:
啟動afl++前,使用如下腳本配置全局環境與調用:
sudo ~/AFLplusplus/afl-system-config
啟用
輸入以下命令,開動!
注意 -m none。AFL++使用 AddressSanitizer 構建了它,它為影子內存映射了大量頁面,因此必須解除內存限制才能讓它運行起來。
XML 是一種高度結構化的輸入,因此 -d 是一個不錯的選擇。它啟用了 FidgetyAFL,這是一種跳過確定性階段(非常適合二進制格式)以支持隨機階段的模式。
可以使用持久模式加速模糊測試的過程,我的下一篇博文會詳細介紹persistent模式該怎么啟用。
~/AFLplusplus/afl-fuzz -i in/ -o out -m none -d -- ./xmllint_cov @@