【AFL(五)】文件變異策略


前言:

在AFL的fuzzing過程中,維護了一個 testcase 隊列 queue ,每次把隊列里的文件取出來之后,對其進行變異,下面就先粗略講一下各個階段的變異是怎樣的。

  • bitflip:        按位翻轉,每次都是比特位級別的操作,從 1bit 到 32bit ,從文件頭到文件尾,會產生一些有意思的額外重要數據信息;
  • arithmetic: 與位翻轉不同的是,從 8bit 級別開始,而且每次進行的是加減操作,而不是翻轉;
  • interest:    把一些有意思的東西“interesting values”對文件內容進行替換;
  • dictionary: 用戶提供的字典里有token,用來替換要進行變異的文件內容,如果用戶沒提供就使用 bitflip 自動生成的 token;
  • havoc:       進行很大程度的雜亂破壞,規則很多,基本上換完就是面目全非的新文件了;
  • splice:       通過將兩個文件按一定規則進行拼接,得到一個效果不同的新文件;

注: bitflip、arithmetic、interest、dictionary 是 deterministic fuzzing 過程,屬於dumb mode(-d) 和主 fuzzer(-M) 會進行的操作; havoc、splice 與前面不同是存在隨機性,是所有fuzz都會進行的變異操作。文件變異是具有啟發性判斷的,應注意“避免浪費,減少消耗”的原則,即之前變異應該盡可能產生更大的效果,比如 eff_map 數組的設計;同時減少不必要的資源消耗,變異可能沒啥好效果的話要及時止損。

師兄之前說是可以開始看afl-fuzz.c文件,然后看下來很多點都是纏在一起的,除了插樁編譯部分和fuzzing過程可以稍微分開,其實很多頭文件也是需要看的。完整版筆記見我的git地址(不斷更新):afl-fuzz.c 閱讀筆記,隨着深入會把一些理解的比較多的部分抽離出來單獨寫一寫。在閱讀源碼過程中對文件變異有了新的整體的認識,故有此文。本文分兩部分:文件變異方法詳解 和 變異策略


文件變異方法詳解

1.bitflip,位反轉,顧名思義按位進行翻轉,0變1,1變0。

  • STAGE_FLIP1 每次翻轉一位(1 bit),按一位步長從頭開始。
  • STAGE_FLIP2 每次翻轉相鄰兩位(2 bit),按一位步長從頭開始。
  • STAGE_FLIP4 每次翻轉相鄰四位(4 bit),按一位步長從頭開始。
  • STAGE_FLIP8 每次翻轉相鄰八位(8 bit),按八位步長從頭開始,也就是說,每次對一個byte做翻轉變化。
  • STAGE_FLIP16每次翻轉相鄰十六位(16 bit),按八位步長從頭開始,每次對一個word做翻轉變化。
  • STAGE_FLIP32每次翻轉相鄰三十二位(32 bit),按八位步長從頭開始,每次對一個dword做翻轉變化。

變異的具體實現部分在大約 5135 行的   #define FLIP_BIT(_ar, _b) do {***}   有詳細的代碼實現。

<1.1 token - 自動檢測>

源碼中有一段關於這部分的注釋,意思是說在進行為翻轉的時候,程序會隨時注意翻轉之后的變化。比如說,對於一段 xxxxxxxxIHDRxxxxxxxx 的文件字符串,當改變 IHDR 任意一個都會導致奇怪的變化,這個時候,程序就會認為 IHDR 是一個可以讓fuzzer很激動的“神仙值”--token。

/*
While flipping the least significant bit in every byte, pull of an extra trick to detect possible syntax tokens. In essence, the idea is that if you have a binary blob like this: xxxxxxxxIHDRxxxxxxxx ...and changing the leading and trailing bytes causes variable or no changes in program flow, but touching any character in the "IHDR" string always produces the same, distinctive path, it's highly likely that "IHDR" is an atomically-checked magic value of special significance to the fuzzed format. We do this here, rather than as a separate stage, because it's a nice way to keep the operation approximately "free" (i.e., no extra execs). Empirically, performing the check when flipping the least significant bit is advantageous, compared to doing it at the time of more disruptive changes, where the program flow may be affected in more violent ways. The caveat is that we won't generate dictionaries in the -d mode or -S mode - but that's probably a fair trade-off. This won't work particularly well with paths that exhibit variable behavior, but fails gracefully, so we'll carry out the checks anyway.
*/

其實token的長度和數量都是可以控制的,在 config.h 中有定義,但是因為是在頭文件宏定義的,修改之后需要重新編譯使用。

/* Maximum number of auto-extracted dictionary tokens to actually use in fuzzing (first value), and to keep in memory as candidates. The latter should be much higher than the former. */
 #define USE_AUTO_EXTRAS 50
 #define MAX_AUTO_EXTRAS (USE_AUTO_EXTRAS * 10)

<1.2 effector map - 生成>

在這里值得一提的是 effector map,在看源碼數據變異這一部分的時候,一定會注意的是在 bitflip 8/8 的時候遇到一個叫 eff_map 的數組,這個數組的大小是 EFF_ALEN(len) (也就是【?多大還不清楚?】),數組元素只有 0/1 兩種值,很明顯是標記的意思,到底是標記什么呢?
要想明白 effector map 的原理需要了解三個點:

> 為什么是 8/8 的時候出現?個人理解:因為這里設置 eff_map 是為了之后的啟發式判斷,而后面的文件數據變異都是 8/8 起步的,所以這里在 8/8 處進行判斷也就合情合理了。還有一點點個人猜測:這里是 8bit 對應着 1byte,應該是byte級別下的啟發判斷的效率最高。

> 標記是干什么用的?根據上面的分析,就很好理解了,標記好的數組可以為之后的變異服務,相當於提前“踩雷(踩掉無效byte的雷)”,相當於進行了啟發式的判斷。無效為0,有效為1。

> 達到了怎樣的效果?要知道判斷的時間開銷,對不停循環的fuzzing過程來說是致命的,所以 eff_map 利用在這一次8/8的判斷中,通過不大的空間開銷,換取了可觀的時間開銷。(暫時是這樣分析的,具體是否真的節約很多,不得而知)

2.arithmetic,算術,實際操作就是加加減減。

bitflip 結束之后,就進入 arithmetic 階段,目標大小和階段與 bitflip 非常類似:

  • arith 8/8,每次8bit進行加減運算,8bit步長從頭開始,即對每個byte進行整數加減變異;
  • arith 16/8,每次16bit進行加減運算,8bit步長從頭開始,即對每個word進行整數加減變異;
  • arith 32/8,每次32bit進行加減運算,8bit步長從頭開始,即對每個dword進行整數加減變異;

其中對於加減變異的上限,在 config.h 中有所定義:

/* Maximum offset for integer addition / subtraction stages: */
#define ARITH_MAX 35

注:跟bitflip相同的,如果需要修改此值,在頭文件中修改完之后,要進行編譯才會生效。

在這里對整數目標進行+1,+2,+3...+35,-1,-2,-3...-35的變異。由於整數存在大端序和小端序兩種表示,AFL會對這兩種表示方式都進行變異。
前面也提到過AFL設計的巧妙之處,AFL盡力不浪費每一個變異,也會盡力讓變異不冗余,從而達到快速高效的目標。AFL會跳過某些arithmetic變異:

  1. 在 eff_map 數組中對byte進行了 0/1 標記,如果一個整數的所有 bytes 都被判為無效,那么就認為整數無效,跳過此數的變異;
  2. 如果加減某數之后效果與之前某bitflip效果相同,認為此次變異在上一階段已經執行過,此次不再執行;

3.interest,把一些“有意思”的特殊內容替換到原文件中。

interest的三個步驟跟arithmetic相似:

  • interest 8/8,每次8bit進行加減運算,8bit步長從頭開始,即對每個byte進行替換;
  • interest 16/8,每次16bit進行加減運算,8bit步長從頭開始,即對每個word進行替換;
  • interest 32/8,每次32bit進行加減運算,8bit步長從頭開始,即對每個dword進行替換;

用於替換的叫做 interesting values ,是AFL預設的特殊數:

/* Interesting values, as per config.h */
static s8 interesting_8[] = { INTERESTING_8 }; 
static s16 interesting_16[] = { INTERESTING_8,INTERESTING_16 };
static s32 interesting_32[] = { INTERESTING_8, INTERESTING_16, INTERESTING_32 };

其中 interesting values 在 config.h 中的設定:

/* List of interesting values to use in fuzzing. */
#define INTERESTING_8 \
  -128,          /* Overflow signed 8-bit when decremented  */ \
  -1,            /*                                         */ \
   0,            /*                                         */ \
   1,            /*                                         */ \
   16,           /* One-off with common buffer size         */ \
   32,           /* One-off with common buffer size         */ \
   64,           /* One-off with common buffer size         */ \
   100,          /* One-off with common buffer size         */ \
   127           /* Overflow signed 8-bit when incremented  */

#define INTERESTING_16 \
  -32768,        /* Overflow signed 16-bit when decremented */ \
  -129,          /* Overflow signed 8-bit                   */ \
   128,          /* Overflow signed 8-bit                   */ \
   255,          /* Overflow unsig 8-bit when incremented   */ \
   256,          /* Overflow unsig 8-bit                    */ \
   512,          /* One-off with common buffer size         */ \
   1000,         /* One-off with common buffer size         */ \
   1024,         /* One-off with common buffer size         */ \
   4096,         /* One-off with common buffer size         */ \
   32767         /* Overflow signed 16-bit when incremented */

#define INTERESTING_32 \
  -2147483648LL, /* Overflow signed 32-bit when decremented */ \
  -100663046,    /* Large negative number (endian-agnostic) */ \
  -32769,        /* Overflow signed 16-bit                  */ \
   32768,        /* Overflow signed 16-bit                  */ \
   65535,        /* Overflow unsig 16-bit when incremented  */ \
   65536,        /* Overflow unsig 16 bit                   */ \
   100663045,    /* Large positive number (endian-agnostic) */ \
   2147483647    /* Overflow signed 32-bit when incremented */

可以看到,基本是些會造成溢出的數值。與前面的思想相同的,本着“避免浪費,減少消耗”的原則,eff_map數組中已經判定無效的就此輪跳過;如果 interesting value 達到的效果跟 bitflip 或者 arithmetic 效果相同,也被認為是重復消耗,跳過。

4.distionary,字典,會把自動生成或者用戶提供的token替換、插入到原文件中。

此階段已經是確定性變異  deterministic fuzzing  的結尾:

  • user extras (over),從頭開始,將用戶提供的tokens依次替換到原文件中
  • user extras (insert),從頭開始,將用戶提供的tokens依次插入到原文件中
  • auto extras (over),從頭開始,將自動檢測的tokens依次替換到原文件中

其中 “用戶提供的tokens” 是一開始通過 -x 選項指定的,如果沒有則跳過對應的子階段;“自動檢測的tokens” 是第一個階段 bitflip 生成的。

5.havoc,“大破壞”,對原文件進行大量變異。

havoc 意味着隨機的開始,與后面的 splice 是任何模式下都要進行的變異,具體來說 havoc 包含了多輪變異,每一輪都是組合拳:

  • 隨機選取某個bit進行翻轉
  • 隨機選取某個byte,將其設置為隨機的interesting value
  • 隨機選取某個word,並隨機選取大、小端序,將其設置為隨機的interesting value
  • 隨機選取某個dword,並隨機選取大、小端序,將其設置為隨機的interesting value
  • 隨機選取某個byte,對其減去一個隨機數
  • 隨機選取某個byte,對其加上一個隨機數
  • 隨機選取某個word,並隨機選取大、小端序,對其減去一個隨機數
  • 隨機選取某個word,並隨機選取大、小端序,對其加上一個隨機數
  • 隨機選取某個dword,並隨機選取大、小端序,對其減去一個隨機數
  • 隨機選取某個dword,並隨機選取大、小端序,對其加上一個隨機數
  • 隨機選取某個byte,將其設置為隨機數
  • 隨機刪除一段bytes
  • 隨機選取一個位置,插入一段隨機長度的內容,其中75%的概率是插入原文中隨機位置的內容,25%的概率是插入一段隨機選取的數
  • 隨機選取一個位置,替換為一段隨機長度的內容,其中75%的概率是替換成原文中隨機位置的內容,25%的概率是替換成一段隨機選取的數
  • 隨機選取一個位置,用隨機選取的token(用戶提供的或自動生成的)替換
  • 隨機選取一個位置,用隨機選取的token(用戶提供的或自動生成的)插入

之后AFL會生成一個隨機數,作為變異組合的數量,每次從上面隨機選取作用於當前文件。

6.splice,“拼接”,兩個文件拼接到一起得到一個新文件。

具體來說,AFL會在文件隊列中隨機選擇一個文件與當前文件進行對比,如果差別不大就重新再選;如果差異明顯,就隨機選取位置兩個文件都一切兩半。最后將當前文件的頭與隨機文件的尾拼接起來得到新文件【為什么不是當前的尾拼接隨機文件的頭【??】】。當然了本着“減少消耗”的原則拼接后的文件應該與上一個文件對比,如果未發生變化應該過濾掉。


變異策略 - 循環往復的Cycle

一波變異結束后的文件,會在隊列結束后下一輪中繼續變異下去。AFL狀態欄右上角的  cycles done  意味着完成的循環數,每次循環是對整個隊列的再一次變異,不過只有第一次 cycle 才會進行  deterministic fuzzing ,之后循環的只有隨機性變異了。

那么一次變異的整體看下來是一個什么步驟呢?【有許多地方不太理解,待補充】

fuzz_one//進行一次


參考 - Reference:

【1】文件變異一覽:http://rk700.github.io/2018/01/04/afl-mutations/

【2】afl-fuzz.c詳解:https://bbs.pediy.com/thread-254705.htm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM