獨角獸暑期訓練營 | 嵌入式韌體自動化漏洞掃描方法研究
該課題由獨角獸安全夏令營第二屆學員黃瑞同學完成
獨角獸暑期訓練營
360無線電安全研究院每年暑假都會面向在校學生舉辦一次暑期訓練營,申請者投遞簡歷並提交自己想做的課題介紹後,若入選,會在360技術專家的指導下完成課題。
本系列文章會發布今年5位學員在訓練營中的成果。文章相關程式碼後續會在 ofollow,noindex" target="_blank">訓練營github程式碼倉庫 釋出 。
引言
從骨幹路由器、交通訊號燈到家用調變解調器和智慧冰箱,嵌入式裝置在現代生活中的應用越來越多。嵌入式裝置韌體的數量也增長到難以計量。安全人員曾經在這類裝置上發現很多高危安全漏洞。相比挖掘新漏洞,在這些韌體中掃描出已經在其他裝置上發現或者是開原始碼中發現過的漏洞,也非常重要。
因此,我們需要一種有效的解決方案來搜尋韌體中的漏洞。 由於各種裝置供應商使用了各種不同的處理器架構和獨特的工具鏈,以及韌體的高度定製特性,在韌體搜尋漏洞程式碼片段極具挑戰性。
我們嘗試解決的問題是:檢測採用不同指令集的二進位制韌體中,是否使用過包含有特定漏洞的程式碼片段。我們閱讀了一些二進位制搜尋的學術論文,並實現了驗證平臺,針對這個目標做了一些改進,識別準確性取得了提高。
主要工作
本文總結了在閱讀分析兩篇靜態二進位制程式碼搜尋的文章後,對文章中的思路和演算法進行復現並評估效果,在效果並不理想的情況下進行了一點改進並再次評估的過程。
文章分別是:
- Yaniv David, Nimrod Partush, and Eran Yahav. 2018. FirmUp: Precise Static Detection of Common Vulnerabilities in Firmware. (ASPLOS ’18). ACM, New York, NY, USA, 392-404.
- Yaniv David, Nimrod Partush, and Eran Yahav. 2017. Similarity of binaries through re-optimization. SIGPLAN Not. 52, 6 (June 2017), 79-94.
問題定義
給定程式集F={T1, T2, …, Tn} 和一個查詢程式Q, Q中包含一個(有漏洞的)函式q,我們的目標是判斷 Ti (∈ F) 是否包含一個與q相似的函式。
首先是我們想要解決的問題,比較嚴謹的問題定義如上,給定T1,T2,T3還有查詢程式Q,這些都是二進位制程式,其中Q中包含了一個函式q,我們的目標就是搜尋這些T中是否有函式與q是相似或者相同的。直觀來講就是在F裡面搜尋q這個函式。
應用場景
下面來是解決如上所述問題的意義、解決後可以應用在哪些場景:
跨平臺、跨工具鏈、跨優化等級程式中的程式碼搜尋:最廣泛的應用場景是目前IOT的裝置越來越多,但是裝置的韌體都是經過編譯、去掉符號表、打包好的,相同功能裝置上面的程式可能是跨平臺、跨工具鏈、跨優化等級的,這個時候我們只能從二進位制層面去嘗試大規模的自動化分析。
閉源軟體分析:因為windows上的大多數軟體都是閉源的,也只能從二進位制層面進行分析搜尋。
兩種解決思路
在閱讀了一些文章後總結解決上述問題大致有兩種思路:
利用圖論:
CFG(control-flow-graph)本質是圖,節點是程式碼片段,邊是跳轉關係。利用圖演算法,通過尋找兩個CFG間的同構或子圖同構來進行相似度匹配
生成特徵:
利用一個函式的某些或所有的basic-block的內容以及CFG的結構,執行相應的演算法生成屬於這個函式的特徵,隨後通過比較兩個程式碼片段的特徵來判斷二者的相似度。
Firmup
Firmup是作者這一系列論文中最新的一篇裡的解決方案,也是準確率最高的。Firmup方案中使用的是上面的第二種思路,也就是分別生成屬於兩段程式碼的特徵,然後對比兩個特徵得出相似度。下面介紹Firmup方案裡的特徵提取演算法以及特徵匹配演算法。
特徵提取演算法
第一步:生成CFG
CFG控制流圖中每個節點代表一個基本塊(basic-block),跳躍目標以一個塊開始,和以一個塊結束。生成CFG有很多成熟工具可以完成,同時也允許程式設計,比如IDApython,和angr 二進位制分析框架。
第二步:統一為Vex-IR
VEX-IR是為了方便二進位制自動化分析而創造的跨平臺間差異的中間表示式。第二步是將CFG的每個節點也就是basicblock的內容以Vex-IR的形式來儲存,IR是一種語言的中間形式,類似於clang編譯器前端將c語言翻譯為IR,編譯器後端再將IR編譯成具體平臺的程式碼。但是Vex-IR比較特殊,是由二進位制程式碼提升為IR,是反向的,專門用於跨平臺二進位制分析,能把不同平臺的指令統一為同一種形式。比如上圖第一張是兩條彙編指令,第二張是翻譯成的Vex-IR,以IMark指令為分割,將每條彙編指令都翻譯成了多條Vex-IR,比如0x404ee這條pop指令翻譯之後,包括了讀取rsp當前指向內容、縮小棧再到賦值給rbp、為pc賦值將指令指標指向下一條,可以看出Vex-IR是將一條彙編指令的所有功能翻譯為多條簡單指令(load以及put)來實現跨平臺統一的。這是Firmup的思路中起到跨平臺的主要工具。
第三步:資料流分片
這一步運用資料流分片,將Basic-Block分為更細的粒度:strand,strand是一個BB中的一個Use-Define鏈,每個strand只包含用來計算同一輸出的多條指令。具體分片操作是由下向上的,比如先使用第5條指令cmp,在裡面使用了暫存器r13,接著上找到第四條sub修改了r13的值,同時又讀取了r15,再向上找第2條指令為r15賦值,同時也讀取了rax,至此上面也沒有指令為rax賦值,一個strand就分割完成了。再經過類似的過程,可以將一塊BB分割為多個strand,每一個strand都是一個完整的“賦值-使用”的鏈條。
第四步:Vex-IR to LLVM-IR
進行到這一步之前,每一個basic-block都已經分割為了strand(由Vex-IR表示)。現在將一個Vex-IR塊翻譯為LLVM-IR的一個函式。這麼做的目的在第五步中會詳細說明。這一步轉換的過程在Firmup的文章中一句帶過,但是實際上是技術實現起來最複雜的部分,具體實現在後面在實現部分會有詳細介紹。
第五步:優化LLVM-IR
第五步是對剛剛第四步翻譯出來的strand(LLVM-IR形式)進行語義上的優化,之所以費了很大力氣把Vex-IR轉化成LLVM-IR,是想利用llvm 非常成熟的優化工具opt,這樣一來不同優化等級、不同工具鏈編譯的程式碼,能夠在opt的分析下統一優化為最高優化等級的程式碼,這是Firmup論文的思路中起到跨優化等級跨工具鏈的主要工具。
第六步:統一化
最後會對優化之後的llvm-IR進行命名統一化,包括暫存器名和變數名,統一以其出現的次序重新命名,這也是遮蔽平臺間差異的一項措施,最後提取出的一個函式的特徵就是他的所有的strand的集合,每個strand都是一段LLVM-IR,也是一串字元,最後我們計算每個strand的md5方便比較、儲存。
總結
至此特徵提取的六個步驟就介紹完了,總結起來如圖:一個可執行程式包含多個函式,每個函式經過CFG得到多塊block,每塊block都能經過資料流分片分割為多個strand,所以我們提取出的每個function的特徵,便是由他下面所有的strand組成的集合,function也是我們用來匹配的單位。
特徵匹配演算法
兩個函式相似度指標:擁有的相同strand的數量(交集大小)
現在我們得到了每個函式的特徵,下面介紹文章中的特徵匹配演算法。按照剛剛的思路,每個函式的特徵都是一個strand集合,我們可以簡單的比較和哪個函式的strand交集最多就認為那個函式是最佳的匹配。
但是此時會遇到一個問題,如上圖,上側的ftp retrieve glob與sub443ee2是strand交集是最多的,但實際上443ee2的最佳匹配是getftp,這是函式不同的大小規模造成的,ftpretrieve比sub443ee2小了一些,正確的匹配應該是像上圖右半側裡的對應關係。
下面介紹論文中在一定程度上解決這個問題從而提高準確率的匹配演算法,如上圖:橫縱座標分別是編譯在arm和mips平臺上的curl裡面的函式,2和3代表編譯優化level,函式名後面的括號是他包含的strand數,a1、a2、a3是他們的簡稱。
我們拿a1作為查詢函式,首先看b1、b2、b3哪個函式與a1的strand交集數量最多,是b2,直觀上可以看到是不對的。演算法繼續,計算b2的最佳匹配是a2函式,a2和a1是不同的函式,也就表明a1的最佳匹配應該不是b2。這時候我們把b2也放入tomatch棧,先尋找b2的匹配物件,b2的最佳匹配是a2,a2的最佳匹配也是b2,這時候我們認為a2、b2才是正確的匹配,加入到match列表。 最後這時候tomatch裡又只有a1,繼續計算最佳匹配,現在應該是b1,因為b2已經找到了最佳的匹配物件,繼續再看b1的最佳匹配也是a1,匹配成功。至此匹配完成,可以看出在沒有在3×3匹配都計算的情況下得到了正確的匹配。最後更加詳細的匹配演算法虛擬碼在論文的Algorithm2,感興趣的同學可以自行仔細閱讀,在此不再貼出來贅述。
假設與限制
最後是論文演算法所作出的假設以及限制:
粒度過大
匹配演算法做了一個很強的假設,就是假設Q T兩個程式有相似的函式,或者說從同一套原始碼編譯的,這也就限制了應用的場景在同源程式間的匹配,沒有辦法只單單搜尋函式或片段。
IR的表達能力有限
第二是Vex-IR沒有能夠完全遮蔽平臺間的差異,比如對flag暫存器的操作,平臺特有的指令arm裡的clz。
復現及評估
軟體架構
實現軟體的架構主要分三個角色,matcher、slicer、translator:
matcher裡實現了前面提到的匹配相關的所有演算法
slicer裡面主要是將block分割為strand的函式
translator是主要的部分,能夠將vexIR翻譯為LLVMIR
translator實現
因為translator在論文裡基本是一句帶過,但實際上是技術上實現難度最大的,他的輸入是一塊vexIR,輸出是一個LLVM IR的函式,VexIR裡面未讀先寫的暫存器作為全域性變數傳入函式,最後一個計算的變數作為函式的返回值,右側的函式體內是左側的每條指令逐條翻譯過去的,最後用llvm opt優化翻譯出的函式,最後根據變量出現的位置進行統一重新命名。
效果評估
評估的方法是:分別編譯兩個curl,不去除符號表,對作為查詢者的curl裡面的每個函式都進行一次匹配,由於沒有去除符號表,因此我們可以明確確定匹配是成功還是失敗,最後計算匹配成功的概率。
如上圖,紅色是匹配失敗項,TP指正確匹配,FP指程式聲稱匹配到了結果,但結果是錯誤的。FN指程式認為被搜尋的程式中沒有匹配到對應函式,但是我們知道這個函式是存在的。
上面是一個比較全面的測試結果,arm0代表在arm架構上優化等級為0。可以看出紅色區域是匹配雙方包含編譯優化等級為0的程式的情況,效率都很不理想,綠色是匹配雙方同平臺優化等級相似的情況,這時候準確率高一些。黃色區域是跨平臺但都經過優化的情況。可以看到總體來看效果不是太理想。
剛剛的效果很不理想,後面在尋找原因時,我發現了一個比較典型的現象,下圖中左右兩側分別是兩個curl同一個函式的第一塊block,紅色區域上半部分是Vex-IR,下半部分是翻譯後的LLVM-IR,可以看出Vex-IR部分是明顯不一致的,但是LLVM-IR部分右側的黃線以上的部分是與左側完全相同的。這個現象說明,當前strand的粒度可能還是太大了,前面的復現裡的strand的組成是一條一條的彙編指令,我決定將其粒度改為Vex-IR中的一條條statement指令,也就是細化strand的粒度。
改進後效果評估
上圖更改之後的效果,相同的測試場景,可以看到準確率有了明顯的提升。
每個單元格的第一個數字代表只保留strand數大於5的函式的準確率,第二個數字代表只保留strand數大於15的函式的準確率,因此可以看出Firmup匹配演算法更適合規模較大的函式。