從研究者的視角看Fuzzing技術發展30年

  • 2020 年 2 月 14 日
  • 筆記

源起

1988年,在威斯康星大學Barton Miller教授的電腦實驗課上(http://pages.cs.wisc.edu/~bart/fuzz/CS736-Projects-f1988.pdf),首次提出Fuzz生成器(Fuzz Generator)的概念,用於測試Unix程式的健壯性,即用隨機數據來測試程式直至崩潰。因此,Barton Miller教授也被多數人尊稱為"模糊測試之父"。但是,當時更多是為了驗證程式碼品質和程式的穩定性,而非專門用於挖掘安全漏洞,真正用於軟體安全漏洞挖掘的開端要從下面兩件事說起。

從學術界到工業界的證明

2001年,芬蘭奧盧大學公布PROTOS測試集項目的研究成果(https://rd.springer.com/content/pdf/10.1007%2F978-0-387-35413-2_16.pdf),首次將Fuzzing技術應用在網路協議的安全測試當中,他們針對不同的網路協議構造出不同的測試用例集,這些在其官網(https://www.ee.oulu.fi/research/ouspg/Protos )上依然可以下載到。2002年,PROTOS逐漸成熟,Microsoft開始為該項目提供資金支援。於是,2003年項目組成立了Codenomicon公司,開始將Fuzzing技術應用於商業產品,也確實發現了不少安全問題。因此,PROTOS項目可以說是Fuzzing技術發展歷程中的一次重要里程碑。可能大家對Codenomicon公司不太了解,但說到"心臟滴血"漏洞,應該無人不知無人不曉。沒錯,它就是Codenomicon公司發現的。

2002年,在BlackHat USA黑客大會上,來自Immunity安全公司(PS:還有人記得Immunity Debugger嗎?)的Dave Aitel發表議題"An Introduction to SPIKE, the Fuzzer Creation Kit",至此著名的Fuzzer工具SPIKE公布了,它是基於塊模板定義的網路協議測試工具,優點是支援定義可變長度數據塊的能力,除生成隨機數據外,它還提供一些現成的邊界值生成,以提供觸發崩潰的概率。SPIKE的誕生,使得廣大用戶能夠依據自身需求訂製網路協議Fuzzer,這對Fuzzing技術的普及起到巨大的推動作用。早些年,筆者也曾寫過一篇SPIKE的教程"基於SPIKE的網路協議Fuzzing技術(http://riusksk.me/2011/12/30/spike-fuzz/)",但現在其實基本不用了。

從PROTOS到SPIKE的誕生,代表著學術界與工業界對Fuzzing技術在商業與安全實戰領域的應用提供了有力的證明。

文件Fuzzing技術的興起

2004年,Peach模糊測試框架的發布,標誌著文件Fuzzing時代的到來。最初Peach是用Python開發的,後來在2007年被收購後改用C#重寫,並分為社區版和付費版。Peach支援文件格式、網路協議、ActivieX控制項等多種形式,通過編寫pit文件(xml格式)來定義數據格式,每次開始寫的時候其實挺費勁的,後來有人提供自動將010editor格式解析器(仿C語言的bt文件)轉換為pit,在一定程度上可以緩解勞動力。筆者第一次通過文件Fuzzing挖到漏洞也是藉助Peach實現的。

直至今日,Peach依然還有人在用,更有人將Peach與AFL打通,在Github上發布aflsmart的開源項目。

文件Fuzzing應該是當前Fuzzing應用中最為普遍的形式,即使是網路協議等其它目標的Fuzzing,也是可以轉換為文件Fuzzing的。比如OpenSSL網路協議Fuzzing,通過源碼打Log的方式先收集網路數據為本地文件,再調用其API寫個hareness用AFL或libfuzzer進行本地測試,就順利地將網路協議Fuzzing轉換為文件Fuzzing。

語法模板Fuzzing:

打開攻擊瀏覽器的大門

2008年,Mozilla安全團隊發布了jsfunfuzz和DOMfuzz,基於JS語法模板來生成測試用例,以挖掘瀏覽器漏洞,後來兩款工具合稱funfuzz( https://github.com/MozillaSecurity/funfuzz ),以開源的形式對外公開。這款工具在當時確實挖到了不少瀏覽器的漏洞,但其語法模板的可擴展性並不友好,只能在程式碼上作修改,這點不如dharma(https://github.com/MozillaSecurity/dharma),以及後來Project Zero發布的Domato (https://github.com/googleprojectzero/domato)。這種基於語法模板的Fuzzing方式,挖完一波後,就要求保持模板的更新才能持續產出,同時要理解測試目標在JS程式碼上的觸發邏輯,比如JIT可通過for循環來觸發程式碼優化,Dom UAF可通過創建Dom元素,並調用相關元素的方法來觸發刪除和引用,以探測是否存在UAF的可能。整體上依賴於對語法和目標原理的理解,才能構造出好的語法模板。

在funfuzz之後,業界也出現了好多款優秀的JS語法Fuzzing工具,比如grinder、nduja、crossfuzz等等。當年PC流行時代,用grinder來Fuzzing Windows IE瀏覽器的人應該比較多。

瀏覽器一直是網路攻擊中最受關注和最常用的入口,過去如是,現今依然。因為系統自帶,且用戶使用率高,又是遠程訪問的最佳途徑。渲染引擎和JS引擎一直是瀏覽器主要攻擊面,主要以html、js、vbs作為解析語言,因此對這些語言的語法Fuzzing就自然而然的產生了。除此之外,如今WebSQL也開始備受關注,比如Chrome上的sqlite模組,SQL語法的fuzzing也隨之而來。

除了瀏覽器,pdf的JS和flash的as語法解析,也一度作為攻擊Adobe Reader和Adobe Flash的入口。

符號執行:學術與工業之爭

2008年,基於LLVM的符號執行引擎KLEE發布後,引領了一波程式分析新姿勢的潮流。後來,符號執行被應用於Fuzzing中,經常被用來打CTF比賽,用來找key、解混淆、fuzzing等用途。比如,將AFL與angr結合的driller (https://github.com/shellphish/driller),還被用在了CGC(Cyber Grand Challenge)自動網路攻防競賽上,但這種比賽都是特定場景下的比賽,不能完全代表真實的軟體世界;還有將AFL與KLEE結合的kleefl (https://github.com/julieeen/kleefl ),這款工具知道的人應該不多。

符號執行在學術界中應用得比較多,工業界相對少一些,這是現狀。將符號執行應用在Fuzzing中,通過約束求解新路徑的條件值,以增加程式碼覆蓋率,可以一定程度上彌補暴力變異的不足。符號執行主要的挑戰在於路徑爆炸問題,約束求解能力的局限性,以及性能消耗問題,比如記憶體和時間消耗過大。符號執行與約束求解對於小型應用比較有效果,也常被用於CTF比賽,在CTF中使用最廣的當屬angr框架。但是,基於當前的業界情況,符號執行仍然比較難以應用於大型軟體中。符號執行在Fuzzing中的應用並沒有真正帶來新的技術浪潮,真正的技術浪潮始於程式碼覆蓋引導技術的引入。

程式碼覆蓋引導技術:

Fuzzing技術的分水嶺

2013年底,afl-fuzz(http://lcamtuf.coredump.cx/afl/ )發布了,首次採用源碼編譯插樁和QEMU模式來實現程式碼覆蓋引導Fuzzing的方式,這絕對是Fuzzing技術發展歷程中最重要的一次里程碑,也是技術分水嶺,它開啟了Fuzzing技術的新篇章。剛發布的時候,afl並沒有那麼火,主要是在2014和2015年期間,被很多人使用後挖到不少主流開源軟體的0day,並在Twitter上宣傳,使得更多人關注到並使用,這證明了程式碼覆蓋引導技術在Fuzzing實戰中的價值。

隨後,基於afl二次開發的fuzzer如雨後春筍般湧現出來,比如winafl、libfuzzer、AFLFast、Vuzzer等等,而且針對各種語言的版本出相繼出現,比如go、python、js、ruby等等。一些已知名的Fuzzer也迅速跟進,比如syzkaller內核Fuzzer,它原本是基於API調用模板的,後來也引入了程式碼覆蓋引導能力。同時,業界都在試圖將其移植到各種平台上(比如windows、android、IOT平台等等),並實現支援閉源程式的程式碼覆蓋引導能力,這一直是近幾年來Fuzzing技術研究的熱點方向,比如動靜態插樁、虛擬機模擬執行、硬體特性等等。無論是工業界大會(BlackHat、OffensiveCon、CCC等等),還是學術界四大高峰會,關於Fuzzing的議題也越來越多,相信這種趨勢會持續下去。

系統函數調用模板Fuzzing一度成為攻擊內核的常用手段

2015年Google開源了syzkaller,一款用於Fuzzing Linux內核的工具,漏洞產出特別高。現在依然很多人用它來挖各系統平台的內核漏洞,包括Android、macOS、Windows等主流系統平台。syzkaller通過定義系統函數調用模板來實現,在模板中定義系統調用函數參數類型,並解決函數調用的順序依賴和值依賴問題。Project Zero官方部落格就曾寫過一篇利用syzkaller fuzz socket挖掘Linux內核漏洞的文章,標題為「Exploiting the Linux kernel via packet sockets(syzkaller usage)」 https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html ),詳細講述了如何編寫模板,以及syzkaller的使用方式。

Windows平台也常被通過構建GUI API調用模板來Fuzzing系統內核,macOS平台內核Fuzzing就常拿IOKit函數開刀,都是基於這種系統函數調用模板的Fuzzing方式實現的。

2016年Google提出"結構感知型Fuzzing」(Structure-Aware Fuzzing ),並基於libfuzzer與protobuf實現了libprotobuf-mutator(https://github.com/google/libprotobuf-mutator),其實現思路與syskaller相似,它彌補了peach的無覆蓋引導的問題,也彌補了afl和libfuzzer對於複雜輸入類型的低效變異問題。正如前面提到的,也有人將afl與peach整合成aflsmart,以此實現類似功能。現在Project Zero也用libprotobuf-mutator來fuzzing iOS內核,詳見"SockPuppet: A Walkthrough of a Kernel Exploit for iOS 12.4"(https://googleprojectzero.blogspot.com/2019/12/sockpuppet-walkthrough-of-kernel.html)。

結構感知型Fuzzing並不是什麼新技術,跟Peach的實現思路是一樣的,只是對輸入數據類型作模板定義,以提高變異的準確率。只是當前大家更傾向於將結構感知與覆蓋引導等多種技術優勢整合一塊,基於系統函數模板用於Fuzzing系統內核,相信這種方式未來仍會被經常使用。

助力開源生態安全建設

在工業界中,最知名的Fuzzing平台當屬Google的clusterfuzz( https://github.com/google/clusterfuzz ),運行在25000+台機器上,發現過16000+個Chrome bug,11000+開源項目bug,這個平台整合了OSS-Fuzz(https://github.com/google/oss-fuzz ),既支援libfuzzer和AFL的程式碼覆蓋引導Fuzzing,也支援黑盒Fuzzing。OSS-Fuzz和clusterfuzz分別在2016年和2019年開源對外,業界同行可以協同開發,對於提交fuzzer後挖到新漏洞的,Google會美刀獎勵提交者,對於主流開源項目的0Day,Google也是有獎勵機制的。同時,Google開發了ASan、MSan、TSan、UBSan、LSan等多種編譯時插樁工具用來幫助檢測漏洞,有些漏洞只在開啟相關Sanitizer之後才會觸發異常,跟在Windows下開啟頁堆的方式類似,可以幫助更有效地發現崩潰場景,在發現和分析漏洞上提供幫助。

Google對開源生態安全建設的貢獻,是各大互聯網公司不能比的。他們的貢獻不僅幫助提高各大主流開源項目的安全性,也降低了Fuzzing的技術成本,在資金和技術上對開源生態安全的建設提供了大力支援。

語法樹變異成為語法解析引擎漏洞挖掘的新方向

2012年,USENIX安全高峰會上發布一篇論文"Fuzzing with code fragments",研究者開發了一款叫LangFuzz的工具,他們從firefox、webkit、chromium等開源的瀏覽器項目以及網路上去收集js測試樣本,然後用ANTLR其進行AST語法樹分析,再將樣本拆分成非終止語法的程式碼片斷,放入程式碼池中,最後再基於程式碼池的程式碼片斷對輸入樣本作交叉變異,主要取同類型的程式碼片斷作替換或插入,再運行生成的變異樣本進行測試。

基於LangFuzz的思路,後面又有人開源了IFuzzer,並發表相關論文公開,在LangFuzz的基礎上增加遺傳演算法,對輸入樣本進行評估,篩選出優秀的個體進行組裝以產生新樣本。不過這個工具並沒有那麼完善,也未見到比較好的實際漏洞產出。

2018年,Project Zero的Samuel Groß發布一款叫fuzzilli的JS語法Fuzzer工具,它整合了語法變異、模板生成、覆蓋引導等多種技術,使用自定義中間語言用於語法變異,再將變異後的中間語言轉換成JS程式碼。fuzzilli在3大主流JS引擎的測試中,戰果頗豐,發現了不少漏洞,也因此被業界同行拿去作二次開發,又發現了其它新的漏洞。

2019年,有2篇學術論文發布,他們都引用了論文"Fuzzing with code fragments"中的思想,它們分別是"CodeAlchemist: Semantics-Aware Code Generation to Find Vulnerabilities in JavaScript Engines""Superion: Grammar-Aware Greybox Fuzzing",CodeAlchemist將輸入樣本進行語法樹分析和數據流分析,為拆分出來的程式碼片段設置前置和後置的約束條件,前置條件代表一些引用的變數需要先定義,後置條件代表程式碼片段的輸出結果,通過兩者來解決一些未定義變數引用的問題。Superion是將語法樹變異規則置入AFL中實現的,藉助AFL篩選變異後的輸入樣本,而且支援多種語言,也是採用ANTLR作語法樹分析,其在語法擴展上比較友好。兩款工具均在最新JS解析引擎上發現過若干0day漏洞,並且均已在Github上開源。

除了傳統的模板Fuzzing,語法變異(無論是AST,還是自定義中間語言)也是一項值得探索的方向。

人工智慧在Fuzzing中的應用仍亟待探索

2018年是人工智慧元年,很多領域都在探索AI的應用,漏洞挖掘領域亦然。之前筆者閱讀過一些AI應用在Fuzzing中的相關議題,主要集中在測試樣本生成的訓練上,利用已知漏洞的樣本或正常樣本作訓練,然後重新生成測試樣本進行測試。可惜從效果上來看,是一種高投入低產出的事情,有些還不如直接暴力變異來得高效高產。但這也不是說,AI沒有價值,只是它在這方面的發展時間較短,很多東西仍亟待探索,還有待時間的年輪來證明。