前言
fuzzing技術在漏洞挖掘領域是一個無法繞開的話題,無恆實驗室也一直在使用fuzzing技術發現產品的問題。雖然fuzzing不是萬能的,但是沒有它是萬萬不能的。說它不是萬能的其實也是相對的說法,理想狀態下,例如在可接受的時間範圍內,計算資源足夠豐富且系統複雜度足夠低的情況下,fuzzing就能夠給你任何想要的結果。這就好像是讓一臺計算機去隨機的print一些文字,只要時間足夠長,隨機字元產生的效率足夠快,那麼早晚有一天會print出一部《三體》出來。
然而理論是豐滿的,現實是骨感的,雖然人類社會的計算資源和效率在不斷增長,但是軟體系統的複雜度卻以更快的幅度在增長著,以往透過近乎於dumb fuzz就能找到漏洞的情況幾乎絕跡了。所以fuzzing技術必然要朝著更高覆蓋率的樣本生成、更高效的程式碼路徑移動演算法(變異)、更合理的計算資源分配排程等方向去發展。
不敢妄言未來fuzzing技術能發展到什麼程度,這要靠學術界和工業界的共同努力,但是如果我們有一天見到了一個AI在面對大部分未知系統的時候都能自己solve出bug,那麼毫無疑問,它一定用到了fuzzing!情懷的部分就到這裡,下面來腳踏實地的實踐一下安卓的一些native binary如何去fuzz。
技術背景:fuzzing二進位制目前有很多流派,但都大同小異,目的都是以最快的速度產生樣本覆蓋更多的code path,顯然在這個過程中以code coverage作為整個fuzzer的驅動導向是最科學的,也就是覆蓋率引導的灰盒模糊測試技術CGF(Coverage-based Greybox Fuzzing),這裡有必要對這個最為核心的技術背景做些介紹。
統計coverage資訊的方法通常有以下幾種:
1、Compiler Instrumentation:如果存在原始碼的情況下,這種方式做程式碼覆蓋率統計和fuzz是最可靠且高效的,例如利用LLVM、GCC等,但可惜很多情況下拿不到原始碼。
https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.htmlhttps://llvm.org/docs/CommandGuide/llvm-cov.html
2、Execute Simulation:例如基於QEMU、Unicorn模式的AFL就會在QEMU準備翻譯執行基本塊之前插入覆蓋率統計的程式碼,缺點是執行效率有點低。
https://github.com/edgarigl/qemu-etracehttps://andreafioraldi.github.io/articles/2019/07/20/aflpp-qemu-compcov.html
3、Runtime Trace:這種形式的程式碼路徑覆蓋比較靈活,實現形式也比較多樣化,也是本文所用到的主要方法。較為常見的方式例如使用frida動態插樁、除錯啟動、或者直接改造手機ROM等方式都可以實現,缺點是由於架構複雜穩定性很難保證,很多時候還沒等target crash,fuzzer自己先crash了。文章後面會詳細一些的介紹用到的frida stalker工具。
https://frida.re/docs/stalker/
4、Binary Rewrite:這種方法主要是針對二進位制disassemble的每個基本語句塊進行插樁,如果做的比較理想,效果可能僅次於Compiler Instrumentation,但缺點是它太難了,如果僅僅是個比較簡單的binary問題倒還不大,但如果是個複雜度很高的系統實現起來就會困難重重,例如有些binary自帶VM的情況、binary存在runtime rewrite自身的機制、binary使用了一些CPU的特殊Architecture Feature、各種binary重定位問題等等,這些處理不好都會讓rewriten binary無法順利執行。
https://github.com/GJDuck/e9patchhttps://github.com/utds3lab/multiversehttps://github.com/talos-vulndev/afl-dyninst
5、Hardware Trace:這可能成為未來binary fuzzing的主流方向,硬體對軟體天然就處在上帝視角中,這裡要明確一下我所說的Hardware Trace這個範疇,並不是真的需要搞個專用於fuzzing的硬體,這裡主要是說利用硬體與作業系統之間的那一層的能力去完成fuzz的目的,這對fuzz作業系統自身尤其有效,例如利用hypervisor、硬體偵錯程式等的能力,當然也不排除有一天會有人搞出個FPGA甚至ASIC來跑fuzz,哈哈,那簡直太硬核了!
https://github.com/gamozolabs/applepiehttps://the-elves.github.io/Ajinkya-GSoC19-Submission/
目標選擇:關於如何選擇fuzzing目標這點,主要從安卓so庫的安全風險角度分享一下我的經驗和思路:
1、有攻擊面:這是最先要確認的一個點,雖然理論上說任何程式都有攻擊面,但是有大有小,有多有少,我們傾向於選擇攻擊面大的目標,這樣才更有價值,例如有些so庫可能會直接接收使用者的外部資料進行處理,例如影片播放器、圖片解析引擎、js解析引擎等等,這些so庫如果出現漏洞,大機率上比較容易直接利用。
2、高頻應用:更高頻被用到的so庫也是值得重點考量的,越是高頻被用到,就越易於攻擊利用,風險也就越大,例如有些工具util性質的so庫,可能會被好多其他庫呼叫,出問題的機率很大。
3、複雜度高:理論上說,漏洞的產生的機率與系統複雜度成正比,越是複雜就越是容易出問題。
4、消減措施缺失:某些so庫在編譯過程中可能沒有考慮到安全性,沒有開啟安全編譯選項,這就會導致其上的漏洞很容易被利用,風險也很大。
樣本生成:確認了目標以後,就要開始考慮目標的業務邏輯了,越是能清晰的瞭解測試目標,就越是能準確的構造出好的樣本,提升fuzz的效率。這個過程就好像導彈在擊中目標之前的制導過程,需要明確擊中目標所要經過的各個路徑和需要繞過哪些障礙等。落實到業務上就是需要了解目標會接收什麼樣的輸入資料、對資料處理的過程是怎樣的、是否需要互動、是同步還是非同步等等,越準確清晰越好。例如,現在要fuzz的目標是一個影片解碼引擎的H265解碼演算法,那麼就要考慮如何去基於H265的編碼演算法生成一些影片樣本,如果樣本使用其他一些MPEG、AVS、WMV等的編碼,那可能fuzz到天荒地老也未必能找出一個H265解碼器的問題。
“精確制導”之後我們也需要去做一些類似“火力覆蓋”的事情,因為生成一個單一樣本很難做到最大化的code coverage,所以我們需要在目標接收資料範圍內做一些多樣性的變化,產生一個樣本集,透過大量多樣性的樣本達到一個比較滿意的程式碼覆蓋率。
本文使用ffmpeg庫對H265的影片樣本進行生成,示例部分程式碼邏輯。
對原始幀做一些隨機變化:
影片引數也進行些隨機多樣化的設定:
還可以選擇性的在影片流封裝前搞點事情:
這樣生成出來的影片檔案其實已經經過一定程度的變異,甚至可以直接fuzz出一些crash了。
覆蓋率引導:樣本集有了,我們需要進行一些裁剪工作,主要根據樣本對被測目標的覆蓋率進行篩選,選出能夠最大化coverage的最小集合,然後再對這個最小樣本集進行變異,這樣可以避免生成一些重複性的測試cases。
技術背景中提到過的stalker是這個環節的核心,stalker是frida系列神器的程式碼tracer,可以做到函式級、基本塊級、指令集的程式碼tracing。不過這個工具之前一直對arm支援的不夠好,目前frida 14.0的版本也僅支援arm64。
stalker的主要原理是dynamic recompilation,這裡我們簡單介紹一些stalker中的專有名詞概念:
Blocks:基本塊,與編譯原理中的概念相同,不再贅述。
Probes:Probes就是一個基本塊的樁點,如果你用過interceptor的話,他們很類似。
Trust Threshold:這個概念稍微有點繞,它實際主要是為了最佳化編譯執行效率,但是設定它卻和一些帶有self-modifying功能的程式有關,例如某些加過殼或者做了anti-disassembly的binary在runtime期間會對自身的程式碼進行rewrite,這個過程stalker就必須要對程式碼重新進行dynamic recompilation,這將會是個非常耗時且麻煩的過程。所以stalker為blocks設定了一個閾值N,當blocks執行過N次之後,這個block將會被標記為可信的,以後就不會再對它進行修改了。
瞭解了這些概念後,繼續來看一下stalker在dynamic recompilation過程中的基本操作。stalker會申請一塊記憶體,以基本塊為單位寫入instrumented後的block copy並插入Probes,重定位位置相關的指令例如ADR Address of label at a PC-relative offset.,對於函式呼叫,儲存lr相關的上下文資訊,建立這個塊的索引並且進行count計數,達到Trust Threshold的設定閾值的就不再進行重新編譯,接下來執行一個基本塊然後繼續開始下一個。這個過程限於篇幅只能說個大概,實際的處理過程比較複雜,感興趣的同學可以自行去讀一下stalker的程式碼加深理解。
透過stalker的能力,我們可以在trace target的過程中清晰的拿到coverage,後續透過coverage來最佳化樣本集,引導變異過程等就都很輕鬆了。例如我們可以透過建立bitmap的方式記錄覆蓋狀態,根據coverage的高低來對樣本進行篩選,還可以透過coverage來確定每次樣本變異後的的效果等。最後截幾小段關鍵程式碼示例一下:
結束語:fuzzing技術作為漏洞挖掘的經典手段,一直受到安全從業人員的喜愛,無恆實驗室也一直致力於使用fuzzing技術發現產品的安全缺陷,提升產品質量。在這個過程中我們發現了大量安全性及穩定性問題,但是路漫漫其修遠兮,未來無恆實驗室會繼續在fuzzing的智慧化、高效化、精準化等方向持續投入研究,並且向業界貢獻成果。
關於無恆實驗室:無恆實驗室是由位元組跳動資深安全研究人員組成的專業攻防研究實驗室,實驗室成員具備極強的實戰攻防能力,研究領域覆蓋滲透測試、APP安全、隱私保護、IoT安全、無線安全、漏洞挖掘等多個方向。實驗室成員為位元組跳動各項業務保駕護航的同時,不斷鑽研攻防技術與思路,發表多篇高質量論文和演講,發現大量影響面廣的0day漏洞。無恆實驗室希望以最為穩妥和負責的方式降低網路安全問題對企業的影響,同時,透過實驗室的技術沉澱、產品研發,致力於保障位元組跳動旗下業務與產品的使用者安全,讓世界更加美好更加安全!