天天看點

炒雞棒的模糊測試技術

本文講的是<b>炒雞棒的模糊測試技術</b>,安全軟體的重點是能使系統更加安全。在開發軟體時,絕對不想引入新的故障點,或者增加軟體運作系統的攻擊面。是以我們自然會認真對待安全的編碼實踐和軟體品質。在這篇文章中,我們想解釋一下我們在内部使用的用來發現漏洞和缺陷的模糊測試技術,以便在這些漏洞發生在客戶那裡以及我們親愛的bug賞金獵人之前找到它們。

一種已經被漏洞賞金獵人證明是非常有效的發現軟體安全漏洞的一種稱為fuzzing的技術,這種技術需要在目标程式中注入意外或畸形的資料,以便導緻輸入錯誤處理,例如可利用的記憶體損壞。為了建立模糊測試用例,一個典型的模糊測試器将會改變現有的樣本輸入,或者根據定義的文法或規則集生成測試用例。一種更有效的模糊方法是覆寫引導模糊測試,程式執行路徑被用于為測試用例生成更有效的輸入資料。覆寫引導模糊測試會嘗試最大化程式的代碼覆寫率,以便測試程式中存在的每個代碼分支。随着一些覆寫引導模糊工具的開源,如American Fuzzy Lop (AFL),LLVM libFuzzer和HonggFuzz,使用覆寫引導模糊測試技術從未如此簡單。你不再需要掌握深奧的技術,或者花費無數個小時編寫測試用例生成器規則,或者是收集覆寫目标所有功能的輸入樣本。在最簡單的情況下,你可以使用不同的編譯器編譯現有的工具,或者分離出你想要的模糊測試功能,隻需編寫幾行代碼,然後編譯并運作fuzzer。fuzzer将每秒執行數千甚至數萬個測試用例,并從目标中的觸發行為中收集一組有趣的結果。

如果你想要開始使用覆寫指導自己的模糊測試,下面會提供幾個示例,描述如何使用我們内部所喜歡的兩個Fuzzer:AFX和LLVM libFuzzer來建構一個被廣泛用于XML解析的工具庫——libxml2的模糊測試工具。

用AFL進行模糊測試

将AFL用于實際的模糊測試的例子很簡單。在Ubuntu 16.04 Linux上,你可以通過系統的xmllint實用程式和AFL,并執行下面的七個指令來進行libxml2的模糊測試。

首先我們來安裝AFL并擷取libxml2-utils的源代碼。

接下來,我們對libxml2進行配置和建構,配置的時候使用AFL編譯器并編譯xmllint實用程式。

最後,我們為AFL建立一個包含“&lt;a&gt; &lt;/a&gt;”的示例檔案,然後開始并運作afl-fuzz。

炒雞棒的模糊測試技術

使用LLVM libFuzzer進行模糊化

我們現在使用LLVM libFuzzer來對libxml2進行模糊測試。要開始模糊測試,你首先需要引入一個目标函數LLVMFuzzerTestOneInput,它從libFuzzer接收模糊測試輸入緩沖區。代碼看起來像下面這樣。

針對libxml2的模糊測試,Google的fuzzer測試套件提供了一個很好的模糊測試示例函數。

在編譯我們的目标函數之前,我們需要使用clang和-fsanitize-coverage = trace-pc-guard來編譯所有依賴關系,以啟用SanitizerCoverage覆寫跟蹤。 為了啟用AddressSanitizer(ASAN)和UndefinedBehaviorSanitizer(UBSAN),捕獲許多可能難以找到的錯誤,還需要使用-fsanitize = address,這是一個很不錯的主意  。

第二步是編譯我們的目标函數,使用相同的标志,并将其與libFuzzer運作時和我們之前編譯的libxml2進行連結。

現在我們準備好運作我們的fuzzer了。

炒雞棒的模糊測試技術

建立語料庫

如果你的目标是快速的執行模糊測試,比如每秒執行數百甚至數千次,那麼你可以嘗試生成一個基礎語料庫。即使使用更複雜的格式,如AFL作者MichałZalewski對JPEG檔案進行模糊測試,使用覆寫引導模糊測試也可以做到這一點,但是為了節省時間,你應該獲得盡可能小的應用程式的典型檔案。檔案越小,模糊測試越快。

當生成語料庫時,AFL沒有給出任何補充标記。隻需要給出一個小的樣本輸入,例如“&lt;a&gt; &lt;/a&gt;”作為XML示例,并像通常那樣運作AFL。

使用libFuzzer可以有更多的标志來進行實驗。例如,對于XML,你可能需要嘗試使用“ -only_ascii = 1 ”。對于大多數格式的一個很好的技術是執行多個時間較短的運作,同時增加我們的Fuzzer的每一輪的最大樣本量,然後合并所有結果以形成輸出的語料庫。

使用這種方法,我們首先需要收集最大長度為4位元組的有趣的輸入,接下來運作分析4位元組的輸入,并将其用作8位元組輸入的基礎等等。這樣我們就可以用更小的輸入來發現“簡單”的覆寫範圍,當我們移動到較大的檔案時,我們就有了一個更好的初始設定。

為了獲得這個技術的一些數字,我們用示例腳本進行了三次運作。

炒雞棒的模糊測試技術

平均來說,運作語料庫生成腳本在我們的筆記本電腦上花了大約18分鐘。LibFuzzer在疊代結束時仍然經常發現新的coverage,其中-max_len大于8位元組,這表明,對于這些長度,使用libFuzzer花費的時間也比較長。

為了比較,我們還采用了libFuzzer的預設設定,并運作了三次,大概用了18分鐘。

炒雞棒的模糊測試技術

從這些結果我們看到,我們運作的語料庫生成腳本平均執行了更多的測試用例,生成了一組更大的檔案,觸發了比使用預設值生成的集合更多的覆寫和功能。這是由于libFuzzer使用預設設定生成的測試用例的大小導緻的。以前的libFuzzer使用的是64位元組的預設的-max_len,但是在編寫libFuzzer時,剛剛更新了一個預設的-max_len為4096個位元組。在實踐中,由腳本生成的樣本集已經非常有效地起作用了,但是在長時間連續模糊測試中,與預設設定相比,效果不同,并沒有收集到資料。

生成語料庫是一個令人印象深刻的壯舉,但是如果我們将這些結果與W3C XML測試套件的覆寫範圍進行比較,我們看到,将不同來源的示例檔案包含在你的初始語料庫中也是一個好主意,在你弄清目标之前,會得到更好的覆寫。

将我們生成的語料庫合并到W3C測試套件中将代碼塊覆寫率增加到18727,是以并不是那麼多,但是我們仍然獲得了83972個功能,進而增加了這些測試用例的總吞吐量。這兩個改進最有可能是由于小樣本觸發了W3C測試套件未涵蓋的錯誤條件。

“修剪”你的語料庫

在将目标模糊測試一段時間後,最終會出現一大堆模糊測試檔案。這些檔案中的很多檔案是不必要的,将它們“修剪”成更小的集合可以為你提供與目标相同的代碼覆寫。為了實作這一點,這兩個項目都提供了語料庫最小化工具。

AFL為你提供了可用于最小化語料庫的afl-cmin shell腳本。對于上一個示例,為了最小化在./out目錄中生成的語料庫,你可以将生成的最小化的檔案集放在./output_corpus目錄中。

AFL還提供了另一個工具afl-tmin,可用于最小化單個檔案,同時可以保持前面看到的相同的覆寫率。請注意,在一大堆檔案上運作afl-tmin可能需要很長時間,是以在嘗試afl-tmin之前,首先要使用afl-cmin進行幾次疊代。

LibFuzzer沒有提供外部“修剪”工具 – 它具有内置的稱為merge的語料庫最小化功能。

LibFuzzer 的merge更容易使用,因為它是從任意數量的輸入目錄遞歸地查找檔案。libFuzzer merge中的另一個不錯的功能是  -max_len标志。使用-max_len = X, libFuzzer将僅使用每個樣本檔案的前X個位元組,是以你可以收集随機樣本,而無需關心其大小。沒有max_len标志,libFuzzer在執行合并時使用的預設最大長度為1048576位元組。

使用libFuzzer merge,你可以使用與生成語料庫相同的技術。

通過這種“修剪”政策,libFuzzer将首先收集每個輸入樣本中觸發2個位元組塊的新覆寫,然後将這些樣本合并為4個位元組的塊,依此類推,直到所有不同長度的塊中都具有優化集合。

簡單的 merge 并不總是可以幫助你解決性能問題。有時,你的fuzzer可能會遇到非常慢的代碼路徑,導緻收集的樣本開始衰減你的模糊測試吞吐量。如果你不介意犧牲幾個代碼塊來執行性能,則可以輕松的使用libFuzzer來從語料庫中删除運作太慢的樣本。當libFuzzer以檔案清單作為參數而不是檔案夾運作時,它将單獨執行每個檔案,并列印出每個檔案的執行時間。

使用awk的代碼片段,此功能可以列印出花費太長時間運作的檔案的名稱,在我們的示例中為100毫秒,然後我們可以删除這些檔案。

并行運作兩個fuzzer

現在你有一個很好的基礎語料庫了,你知道如何維護它,你可以啟動一些連續的模糊測試運作執行個體。你可以單獨運作你最喜歡的fuzzer,或單獨運作兩個fuzzer,但如果你有足夠的硬體可用,你也可以在同一語料庫中同時輕松運作多個fuzzer。這樣,你可以結合兩個fuzzer的最佳優勢,而fuzzer可以分享他們各自發現的所有新覆寫。

很容易就可以實作一個簡單的腳本,同時運作兩個fuzzer,同時每小時重新啟動fuzzer來重新整理樣本語料庫。

因為示例腳本每次疊代隻運作一個小時,是以AFL使用“快速和髒模式”以跳過所有确定性步驟。即使一個大檔案可能會導緻AFL在确定性步驟上花費幾個小時甚至幾天時間,是以在按預算時間運作的話運作AFL更可靠。确定性步驟可以手動運作,也可以在将新樣本複制到“ ./libfuzzer_output ”的另一個執行個體上自動運作。

字典

你現在有你的語料庫,并你快樂地進行模糊測試和修剪。那麼接下來你從哪裡去呢?

AFL和libFuzzer都支援使用者提供的字典。這些字典應包含關鍵字或其他有趣的位元組模式,這對于fuzzer來說很難确定。有關一些有用的例子,請檢視Google libFuzzer的  XML字典和關于AFL詞典的這篇 部落格文章。

AFL和libFuzzer都在執行期間收集字典。當執行确定性的模糊測試步驟時,AFL收集字典,而libFuzzer的方法是進行插樁。

當運作libFuzzer的時候或測試用例限制時,libFuzzer将在退出時輸出一個推薦的字典。此功能可用于收集有趣的字典條目,但建議對所有自動收集的條目執行手動合理性檢查。libFuzzer會在發現新的覆寫範圍時建構這些字典條目,是以這些條目通常建立在最終關鍵字上。

我們測試了三個運作時長為10分鐘的字典:沒有字典,第一次運作的推薦字典和Google的libFuzzer XML字典。這三個測試的結果可以從下表中看出。

炒雞棒的模糊測試技術

令人驚訝的是,沒有字典的運作結果與第一次運作的推薦字典的測試結果沒有顯著的差異,但是使用“真實”字典,在運作期間發現的覆寫量就發生了巨大變化。

字典真的可以改變模糊測試的效果,至少在短時間内是這樣的,是以他們值得去做。Shortcuts,像libFuzzer推薦的字典,很有幫助,但你仍然需要額外的手動操作來利用字典中的潛力。

模糊測試實驗

我們的目标是在幾台筆記本電腦上做一個在周末長時間運作的模糊測試。我們運作了兩個AFL和libFuzzer的執行個體,對上面的例子進行模糊測試。第一個執行個體是沒有任何語料庫的,第二個是W3C XML Test Suite的修剪語料庫。然後可以通過執行所有四組的最小化語料庫的運作來比較結果。這些fuzzer的結果不是直接可以比較的,因為兩個fuzzer都使用不同的儀器來檢測執行的代碼路徑和特征。libFuzzer測量兩件事情,用于評估新的樣本覆寫率,塊覆寫率,被通路的隔離代碼塊和特征 覆寫,這是不同代碼路徑特征(如代碼塊和命中次數之間的轉換)的組合。AFL不對觀察到的覆寫率提供直接計數,但在我們的比較中我們使用總體覆寫圖密度。地圖密度表示我們所擊中的多少個分支元組,與覆寫地圖可以容納多少個元組成比例。

我們的第一次運作并沒有按預期的那樣進行。2天7小時後,我們發現了大檔案使用确定性模糊測試的缺點。我們的  afl-cmin最小化語料庫包含了一些超過100kB的樣本,導緻AFL在加工之後減慢了運作速度,僅次于第一輪的38%。AFL需要幾天的時間才能通過單個檔案,我們在樣本集中有四個,是以我們決定在我們删除超過10kB的樣本後重新啟動執行個體。可悲的是,星期天晚上11點,“備份第一”不是我們頭腦中的第一件事,AFL的資料被意外覆寫,是以沒有第一個比較的結果。我們設法在中止之前儲存AFL UI。

炒雞棒的模糊測試技術

模糊測試兩天的完整結果可以從下面的圖表中找到。

炒雞棒的模糊測試技術

我們實際上從來沒有試圖把這些fuzzer互相對抗。即使在我們的實驗中,這兩個fuzzer結果都是驚人的。從W3C樣本開始,由libFuzzer測量的發現覆寫率之間的差異隻有1.4%。同時這兩個fuzzer都發現了幾乎相同的覆寫。當我們合并了四個運作的所有收集的檔案和原始的W3C樣本時,組合覆寫率僅比libFuzzer單獨發現的覆寫率高出1.5%。另一個值得注意的是,即使在2天之後,沒有初始樣本,libFuzzer或AFL都沒有發現比以前的示範更多的覆寫率,在10分鐘内反複産生了一個語料庫。

我們還使用W3C樣本在libFuzzer模糊測試運作期間生成了覆寫發現的圖表。

炒雞棒的模糊測試技術

我應該使用哪一個?

正如我們上面的詳細說明一樣,AFL使用起來非常簡單,可以幾乎無需安裝即可開始使用。AFL負責發現錯誤處理以及與崩潰類似的東西。但是,如果你沒有可用的指令行工具,如xmllint,并且需要編寫一些代碼來啟用模糊測試,通常使用libFuzzer來獲得卓越的性能。

與AFL相比,libFuzzer内置了資料清洗功能的支援,例如AddressSanitizer和UndefinedBehaviorSanitizer,可以幫助你在測試過程中發現微妙的錯誤。AFL對清洗功能有一些支援,但根據你的目标,可能會有一些嚴重的副作用。AFL的文檔中建議在沒有清洗功能的情況下運作模糊,并且使用清洗功能建構分開的運作輸出隊列,但沒有實際的資料可用來确定該技術是否可以捕獲與ASAN啟用的模糊測試相同的問題。有關AFL和ASAN的更多資訊,你可以從AFL的源碼中找到docs/notes_for_asan.txt。

然而,在許多情況下,運作兩個fuzzer是有意義的,因為它們的模糊測試,碰撞檢測和覆寫政策略有不同。

如果你最終使用了libFuzzer,那麼你應該參考一下Google編寫的非常不錯的 libFuzzer教程。

原文釋出時間為:2017年7月4日

本文作者:絲綢之路

本文來自雲栖社群合作夥伴嘶吼,了解相關資訊可以關注嘶吼網站。

<a href="http://www.4hou.com/mobile/6015.html" target="_blank">原文連結</a>

繼續閱讀