協議模糊測試工具AFLNet詳解丨工具分析
在「FuzzWiki」全新升級后,我們增加了工具分析梳理板塊,期望通過持續的研究,分領域、分場景對現有知名模糊測試工具進行系統梳理和介紹。在該板塊中,我們推出“看這一篇就夠了”專題,通過一篇一工具的方式,逐步完成對現今大部分知名模糊測試工具的介紹,構建模糊測試工具知識圖譜。今天,就為大家帶來該專題的第一篇文章——《協議模糊測試工具AFLNet詳解》。
各位如對內容有疑問歡迎留言共同探討,同時,大家也可以通過留言的方式,將想要了解和學習的工具發送給我們,我們會結合讀者的學習和閱讀需求,優先發布相關工具的介紹哦!
如果需要系統地了解現有協議模糊測試工具情況,可以移步我們之前發布的文章《協議模糊測試》。
接下來,讓我們一起開始對AFLNet的了解和學習吧!
一、 協議模糊測試簡介
在互聯網飛速發展的今天,許多本地應用都是在B/S模式下轉化為網絡服務:服務器部署在網絡上,而客戶端應用程序通過網絡協議與服務器通信。協議中的安全問題可能會產生比本地應用更嚴重的損害,如拒絕服務、信息泄露、遠程代碼執行等。2017年影響全球的Wanna Cry勒索病毒事件,就是因為Windows系統中的SMB協議漏洞導致的。網絡協議的安全性測試成為一個重要的問題。與基于文件的模糊測試相比,協議模糊測試面臨著更多的挑戰,主要體現在:
網絡服務可能會定義它們自己特有的通信協議,有時我們也稱其為“專有協議”或“私有協議”,而這些協議標準往往不公開。此外,即使是有文檔規范的協議,其具體實現也不一定能嚴格遵循諸如RFC文檔之類的規范。
協議模糊器需要與被測目標建立通信連接,然后將生成的符合協議規范的測試用例發送給被測目標。建立通信的過程和發送測試用例的過程會帶來額外的開銷,因此協議模糊測試吞吐量往往低于本地文件模糊測試的吞吐量。
協議模糊測試的被測目標通常具有很大的狀態空間,有時需要精心構造特定順序的消息序列才能到達某一狀態。早期的協議模糊測試工具常采用基于生成的方式進行黑盒模糊測試,這種方式依賴于協議格式的先驗知識來生成有效的模糊測試用例,并且由于黑盒的特性,很難探索深層次的漏洞。
由于AFLNet是第一個使用狀態機的灰盒模糊器,能很好的對程序內部進行探索,所以本文主要對AFLNet這個工具進行詳細解讀。
二、 工具基本原理概要
AFLNet是一款基于AFL 的灰盒協議模糊測試工具,采用了代碼覆蓋率反饋、種子分割變異以及狀態反饋等技術。AFLNet使用client發向server的數據包作為種子,無需掌握協議前置知識就能使用。墨爾本大學研究員Van-Thuan Pham在2020年,發表在ICST會議上的AFLNet論文中所介紹并開源的一款工具。
項目地址
github.com/aflnet/aflnet
論文原文地址
https://mboehme.github.io/paper/ICST20.AFLNet.pdf
2.1 SAMPLE
服務器的行為及其漏洞取決于一段時間內交換的一系列消息,這些消息決定了服務器的狀態。眾所周知的有狀態協議的例子包括加密協議,如TLS,文件傳輸和消息傳遞協議,如FTP、SMB和SMTP,以及多媒體協議,如SIP和RSTP。根據會話中以前的消息,所有這些協議在給定時間可以接收哪些消息,以及可以執行哪些操作方面都是有選擇性的。
下面是客戶機和LightFTP[11]之間根據文件傳輸協議(FTP)進行的消息交換,LightFTP是一個實現FTP的服務器,也是我們評估的主題之一。客戶端發送的消息序列以紅色突出顯示。FTP指定客戶端必須首先在服務器上進行自身身份驗證。只有在成功驗證后,客戶端才能發出其他命令。對于來自客戶端的每個請求消息,FTP服務器用包含狀態代碼的響應消息進行回復(例如,230[登錄成功]或430[無效用戶/通過])。響應中的狀態代碼確保客戶端請求得到確認,并將當前服務器狀態通知客戶端。

2.2 Implement
AFLNET是在AFL的基礎上拓展實現,通過socket(C Socket APIs)的方法實現網絡通信來方便與服務器交互。AFLNET作為客戶端,擁有發送請求和接收響應兩個Channels,待測服務器則作為服務端。AFLNET接收Channel除了代碼覆蓋率反饋外,還增加了狀態反饋機制。為保證同步性,在發送請求之間添加了時延,不然服務器會丟棄發送對上一個報文的響應之前到達的數據包。AFLNET的輸入是包含捕獲的網絡流量的pcap文件,可以使用Sniffer(如tcpdump)來嗅探網絡流量,用數據包分析器(wireshark)提取相關的消息交換。

由圖所示總體由5個部件組成,分別是報文解析器、狀態學習機、目標狀態選擇器、序列選擇器、序列變異器。
Request Sequence Parser 報文解析器
該組件用來生成消息序列的初始語料庫。AFLNET使用特定于協議的消息結構的信息,以正確的順序從pcap中提取單個請求,首先過濾掉pcap文件中的響應,以獲得客戶端請求數據包的跟蹤。然后,它解析過濾后的跟蹤,以識別跟蹤中每條報文的開始和結束。
State Machine Learner 狀態學習機
該組件接收服務器響應并將新發現的狀態和transitions(轉變?)添加到協議狀態機IPSM。AFLNET讀取響應報文并提取協議指定的狀態碼,確定當前的執行狀態。下圖中每個新的節點代表一個新的狀態,根據響應中是否有新的狀態碼,將其添加到圖中。這種形式僅用于輸出可視化狀態圖。

而在代碼中的引導機制的實現,則是使用哈希表的形式,如下圖所示,以一個特定的狀態序列來標記。所出現了新的狀態序列哈希值則認為當前的測試用例是Interesting的。

Target State Selector 目標狀態選擇器
AFLNET使用了幾種啟發式算法,這些算法可以從學習到的IPSM中可用的統計數據計算出來,以幫助目標狀態選擇器選擇下一個狀態。一開始是隨機的在IPSM中所有狀態中選取,fuzz一段時間之后,就開始根據已有的數據決定那些狀態fuzz權重更大,如選取那些更少被遍歷的狀態。
Sequence Selector 序列選擇器
目標狀態s被選擇后,該組件從種子庫哈希表中隨機選取能夠遍歷到狀態s的報文序列。同AFL一樣,AFLNET將種子庫做為一個隊列實體,隊列實體結構保存了種子的相關信息。此外,AFLNET維護了一個狀態庫,該狀態庫由兩部分組成:1、一個狀態實體列表,保存相關狀態信息的一個數據結構;2、一個哈希表,該表將狀態標識符映射到執行與狀態標識符對應狀態的隊列列表。該組件利用哈希表隨機選取能夠遍歷到狀態s的報文序列。
Sequence Mutator 序列變異器
使用AFL的fuzz_one函數中的變異,同時結合協議感知的變異。這個模塊在序列選取器選取報文序列后,把報文對話M拆分成三個部分,M1是報文中遍歷到狀態s的報文前綴,他保證在M1后能到達狀態s。M2是變異部分,M2包含可在M1之后執行的所有消息,但仍處在狀態s中。在M2實施AFL的字節翻轉、插入、刪除等變異操作。變異之后生成報文序列。如果生成的序列M`能夠得到新的響應狀態碼或者帶來程序新的覆蓋率就認為是感興趣的,然后存入我們的種子庫。
三、 工具使用介紹
本文只列出簡單使用方法,想了解詳細指導步驟可參照官方倉庫文檔
afl-fuzz 為主程序, afl-fuzz --help 可查看全部選項,主要選項如下:
-N netinfo: 待測服務器信息 (如 tcp://127.0.0.1/
8554)
-P protocol: 指定待測協議 (如 RTSP, FTP, DTLS12, DNS, DICOM, SMTP, SSH, TLS, DAAP-HTTP,SIP, MODBUS)
-D usec: (可選) 等待服務器初始化時間 (毫秒級)
-K : (可選) 在服務器處理完所有請求后,發送SIGTERM信號結束服務器進程
-E : (可選) 開啟狀態感知模式(啟動狀態機)
-R : (可選) 開啟region級別的變異操作
-c script : (可選) 服務器清理腳本名稱或完整路徑
-q algo: (可選) 指定狀態選擇算法 (如 1. RANDOM_SELECTION, 2. ROUND_ROBIN, 3. FAVOR)
-s algo: (可選) 種子選擇算法 (如 1. RANDOM_SELECTION, 2. ROUND_ROBIN, 3. FAVOR)
其余選項與AFL相似
示例命令:
afl-fuzz -d -i in -o out -N <server info> -x <dictionary file> -P <protocol> -D 10000 -q 3 -s 3 -E -K -R <executable binary and its arguments (e.g., port number)>
四、 源碼分析
4.1 源碼概述
AFLNet代碼主要實現:
01
socket通信發送測試用例
02
一套與代碼覆蓋率并行的狀態機引導機制
03
增加了消息序列級別的變異策略
AFLNet在AFL基礎上主要對afl-fuzz.c文件進行了修改,實現了一套socket通信,在fuzzer與被測服務器之間實現消息收發。增加了aflnet.c和aflnet.h兩個主要文件,同時引入了klib和graphviz兩個庫文件。理解這部分內容需要讀者有一點AFL源碼的基礎。
AFLNet內置了一些支持的協議,可以在aflnet.h內查看。若要新增協議,需要在aflnet.h和aflnet.c內自行實現對應的狀態提取代碼。
4.2 變量分析
AFLNet新增的一些比較重要數據結構
queue_entry新增成員
region_t *regions; /* Regions keeping information of message(s) sent to the server under test 種子的region指針 */
u32 region_count; /* Total number of regions in this seed 種子的region個數 */
u32 index; /* Index of this queue entry in the whole queue 該種子在種子隊列中的下標 */
u32 generating_state_id; /* ID of the start at which the new seed was generated 新種子產生的起始狀態ID */
u8 is_initial_seed; /* Is this an initial seed 是否是初始種子 */
u32 unique_state_count; /* Unique number of states traversed by this queue entry 該種子覆蓋的唯一狀態個數*/
regions
AFLNET根據不同協議定制extract_requests函數來對種子文件進行拆分,拆分結果保存在q->regions中,每個region代表客戶端發送給服務器的一條請求。region_t結構體存儲了該region的一些相關信息,構造region在read_testcases()函數中進行。
typedef struct {
int start_byte; /* The start byte, negative if unknown. */
int end_byte; /* The last byte, negative if unknown. */
char modifiable; /* The modifiable flag. */
unsigned int *state_sequence; /* The annotation keeping the state feedback. */
unsigned int state_count; /* Number of states stored in state_sequence. */
} region_t;
message
message結構,通過函數實現message到region、message到file的轉換。用于構造kl_messages鏈表,在
perform_dry_run()中的construct_kl_messages()使用klib庫的klist進行構造,后遍歷該種子的每一個region塊,將每一個region添加到kl_messages鏈表中。
typedef s truct {
char *mdata; /* Buffer keeping the message data */
int msize; /* Message size */
} message_t;
state
使用state_info_t對每個狀態做了詳細描述
typedef struct {
u32 id; /* state id */
u8 is_covered; /* has this state been covered 該狀態是否被覆蓋*/
u32 paths; /* total number of paths exercising this state */
u32 paths_discovered; /* total number of new paths that have been discovered when this state is targeted/selected 選擇該狀態下,總共發現的新路徑數量*/
u32 selected_times; /* total number of times this state has been targeted/selected 此狀態的選擇次數*/
u32 fuzzs; /* Total number of fuzzs (i.e., inputs generated) */
u32 score; /* current score of the state */
u32 selected_seed_index; /* the recently selected seed index */
void **seeds; /* keeps all seeds reaching this state -- can be casted to struct queue_entry* 保存所有可以到達該狀態的種子文件*/
u32 seeds_count; /* total number of seeds, it must be equal the size of the seeds array 上述數組中的所有種子的數量*/
} state_info_t;
其他一些全局變量
was_fuzzed_map:一個二維數組,記錄狀態與queue_entry的關系,數組中值-1表示狀態不可到達,1表示fuzzed,0表示non-fuzzed。主要用與target_state的選擇。行數表示狀態數量,列數表示隊列實體數量。
ipsm:使用graphviz構造的狀態機指針,這個狀態機實際上不影響fuzzing過程中的決策,只是用來可視化狀態機。
khs_ipsm_paths、khms_states:兩者都是哈希集合,前者用來記錄去重之后的狀態序列,其key值為狀態序列的哈希值,用來判斷一個狀態序列是否Interesting;后者則用來記錄狀態。
M2_prev、M2_next:這一點對應論文中的變異策略,M2是感興趣的序列包,前者是M2前一個包,后者是M2后一個包。
response_buf、response_buf_size、response_bytes:這組變量用來記錄接收服務器相應包相關信息。
4.3 網絡通信
架空原生AFL的write_to_testcase()函數,改用網絡傳輸向服務器傳輸數據包。
send_over_network()
run_target()函數中調用send_over_network()函數,主要通過socket編程實現。
net_recv()是recv()函數的一個封裝,調用循環將接收的數據放入response_buf,同時記錄response_buf_size。超時或全部接受完返回0,出錯返回1。
net_send()是send()函數的封裝,調用循環確保將緩沖區的數據發送完,返回已發送的字節數
清空response_buf,先設置套接字及其選項,設置超時,接著配置服務器地址結構體(sockaddr_in)變量serv_addr,connect()連接服務器。
先調用net_recv()函數讀取早期的響應內容
遍歷kl_messages,依次發送鏈表內容,分配response_bytes存儲每個message的響應包大小,檢索服務器響應
進入HANDLE_RESPONSES階段,對響應進行處理后,關閉套接字,關閉服務器子進程
4.4 狀態機構建
setup_ipsm()初始化狀態機,初始化graphviz圖和khs_ipsm_paths、khms_states兩個哈希表
若收到服務器的狀態序列為interesting,則保存該kl_message。遍歷該response的狀態序列來更新狀態機ipsm,將狀態序列的狀態和轉移關系添加到graphviz圖,同時更新兩個哈希表。
4.5 狀態選擇
choose_target_state()從state_ids[]選擇target_state_id
每次發現新的狀態都會加入state_ids[]數組,且擴大was_fuzzed_map
有三種狀態選擇算法
01
RANDOM_SELECTION:隨機選擇狀態
02
ROUND_ROBIN:輪詢選擇
03
FAVOR:輪詢幾輪獲取足夠信息開始啟用FAVOR
state->score = ceil(1000 * pow(2, -log10(log10(state->fuzzs + 1) * state->selected_times + 1)) * pow(2, log(state->paths_discovered + 1)));
4.6 種子篩選
was_fuzzed_map二維數組記錄了狀態與隊列實體間的關系,AFLNET在篩選種子的時候結合了以下條件
/* AFLNet takes into account more information to make this decision */
if ((top_rated[i]->generating_state_id == target_state_id ||
top_rated[i]->is_initial_seed) &&
(was_fuzzed_map[get_state_index(target_state_id)][top_rated[i]->index] == 0))
pending_favored++;
通過target_state_id來選擇對應隊列實體中比較好的種子文件
4.7 序列級別變異
AFLNET變異在fuzz_one()中做了些許改動
如果開啟了state_aware_mode,則根據target_state_id來選定M2;否則隨機選取M2
之后構造該實例的kl_messages,并切割成M1, M2, M3三部分,將M2作為變異目標復制到out_buf進行變異
確定性變異與原生AFL基本相同
HAVOC階段增加了case17~20四個region級別的變異
case17: 用隨機種子中的隨機區域替換當前區域
case18: 將隨機區域從隨機種子插入到當前區域的開頭
case19: 從隨機種子中插入一個隨機區域到當前區域的末尾
case 20: 復制當前區域
五、 結語
協議模糊測試的發展以AFLNET工具為分界線。在AFLNET提出之前,主流的協議模糊測試工具采用黑盒的方法,由于黑盒測試不能得到SUT的反饋,模糊測試很難有效探索協議的狀態空間;在AFLNET提出之后,基于覆蓋的有狀態灰盒模糊測試成為協議模糊測試的主流方法。但AFLNet也存在較大的局限性,socket通信吞吐量較低,且服務器程序啟動有初始化時間,所以相比文件類的模糊測試效率要大打折扣。