1本文系原創,轉載請說明出處
關注微信公眾號 信安科研人,獲取更多的原創安全資訊
網上已經有很多介紹angr的官方文件的部落格,但是怎麼去用angr做一次有意義且成就感滿滿的分析的教程很少,目前比較常見的就是CTF中的應用,很少有從二進位制程式分析技術角度介紹。
同時,也沒有幾篇文章介紹angr對應的論文,這篇論文很棒很經典。
因此,本部落格從理論與實踐兩個方面介紹angr,以求讓讓自己更熟練掌握這個庫是怎麼用的,並應用到自己的工作中。
目錄
一 angr簡介
angr是加州大學聖巴巴拉分校Giovanni Vigna大佬領銜的安全實驗室的傑作,對應的論文發表在2016年的網路安全頂會IEEE S&P上,原論文連結
二 研究背景
在這篇文章出來之前,安全界對程式進行漏洞分析的技術存在以下兩種問題:
(1)研究的重複工作太多。每一個安全分析研究工作都要重新實現並呼叫一些以前的技術,浪費時間。
(2)其次,由於複製這些系統所需的工作量不可接受,複製其他人的結果變得不切實際。結果,單個二進位制分析技術相對於其他技術的適用性變得模糊不清。再加上現代作業系統固有的複雜性,很難建立一個共同的比較基礎。
批註:可以看出來,發表在CCF A上的文章所解決的問題往往是能夠推動整個領域前進的問題,或者是解決一個領域內的巨大阻礙。同時,可以看到,真正的科研,往往是問題驅動,就是說解決一個問題而去做科研,往往是能夠做出比為了科研而去解決問題更多的成功。
因此,angr的實現的意義來了:
大佬們建立了angr以解決這些技術共用性的問題,整合了眾多文獻中最先進的二進位制分析技術。這樣做的目的是通過以一種可訪問、開放和可用的方式實施當前研究工作中的有效技術,使該領域系統化,並鼓勵開發下一代二進位制分析技術,以便能夠輕鬆地相互比較。
angr 使用靜態和動態分析技術為多種型別的分析提供構建分析塊,以便可以輕鬆實現所提出的研究方法並比較這些方法之間的有效性。此外,這些構建的分析塊的組合能夠利用它們的不同的組合優勢。
angr做出的三個貢獻:
1) 在一個單一、連貫的框架中再現了攻擊性二進位制分析中的許多現有方法,以瞭解當前攻擊性二進位制分析技術的相對有效性。
2) 展示了將各種二進位制分析技術結合起來並大規模應用的困難(以及解決這些困難的方法)。
3) 將angr開源,以供後代研究二進位制程式碼分析之用。
三 背景知識
3.1 漏洞挖掘的靜態分析技術
靜態技術在不執行程式的情況下對程式進行推理。通常,程式會被抽象表示。例如,諸如儲存器佈局或甚至所採用的執行路徑之類的程式構造也可以被抽象表示。
這篇文章將靜態分析分為兩種樣式:
(1)將程式屬性建模成圖
(2)對程式資料本身進行建模的正規化
靜態分析的兩個缺點:
(1)首先,結果不可重複:靜態分析的檢測必須手動驗證,因為無法恢復有關如何觸發檢測到的漏洞的資訊。
(2)其次,這些分析往往在更簡單的資料域上操作,降低了對語義的洞察。簡言之,分析過於近似:雖然通常可以權威性地推理某些程式屬性(例如漏洞)的缺失,但在就漏洞是否存在而證明時,靜態分析的誤報率很高。
技術一 CFG圖的恢復
控制流圖 (CFG) 的恢復是幾乎所有用於漏洞發現的靜態技術的先決條件,其中圖節點是基本的指令塊,邊緣是指令塊之間可能的控制流傳輸。
主流的CFG圖的生成方法為遞迴演算法,例如,從基本塊BA開始,分解並分析BA基本快,識別可能的出口點BC、BD,那麼就將BC、BD接到BA上,然後從BC、BD遞迴地重複分析,直到識別不出新的出口點。
CFG控制流恢復有一個到目前為止都存在的挑戰點:指令的間接跳轉。 與直接跳轉不同,在直接跳轉中,目標被編碼到指令本身中,因此很容易解析,而間接跳轉的目標可以基於許多因素而變化,如連結庫。
具體來說,間接跳轉分為幾類:
- 計算跳轉。如跳轉表,跳轉表中的索引地址需要依據當時執行的暫存器或者記憶體的值來確定。
- 上下文敏感。間接跳轉可能取決於應用程式的上下文。常見的例子是標準C庫中的qsort()——該函式接受一個回撥,用於比較傳入的值。因此,qsort()中基本塊的一些跳轉目標取決於其呼叫方,因為呼叫方提供回撥函式。
- 物件敏感(object-sensitive)。上下文敏感的一種特殊情況是物件敏感。在物件導向的語言中,物件多型性需要使用虛擬函式,通常實現為在執行時查詢的函式指標的虛表,以確定跳轉目標。因此,跳轉目標取決於其呼叫方傳遞給函式的物件的型別。
angr對如上的CFG圖恢復工作的難點一個個攻克,並定義了CFG恢復技術的兩個屬性:
(1)可靠性。如果在生成的圖中表示所有潛在控制流傳輸的集合,則認定這個CFG圖恢復技術是合理的。
(2)完整性。指CFG圖恢復成一個其中所有邊都表示實際可能的控制流傳輸的CFG圖。
技術二 基於流模型的漏洞檢測
通過分析程式屬性圖可以發現程式中的一些漏洞。程式屬性圖(例如,控制流圖、資料流圖和控制依賴圖)可用於識別軟體中的漏洞。最初應用於原始碼的相關技術
技術三 基於資料建模的漏洞檢測
一種代表方法:Ø值集分析(Value-Set Analysis, VSA)。VSA嘗試識別程式中任何給定點的程式狀態(即記憶體和暫存器中的值),這可以用來解決間接跳轉的可能目標,或記憶體寫入操作的可能目標識別問題。雖然這些近似值缺乏準確性,但它們是比較可靠的。也就是說,它們可能過近似,但決不可能能欠近似。
例如:使用值集分析,通過分析記憶體讀寫的近似訪問模式,可以在二進位制檔案中識別變數和緩衝區的位置。完成後,可以分析恢復的變數和緩衝區位置,以找到重疊的緩衝區。例如,這種重疊緩衝區可能是由緩衝區溢位漏洞引起的,因此每次檢測都是一個潛在漏洞。
3.2 漏洞挖掘的動態分析技術
該論文將動態分析技術分為兩大類:
具體執行和(動態)符號執行。
具體執行:
具體執行的代表方案就是Fuzzing,也就是我們常說的模糊測試。這裡不多介紹其概念,這裡介紹幾種現有的fuzzing的種類:
(1)基於覆蓋率的fuzzing。基於程式碼覆蓋率的模糊測試技術嘗試產生的輸入用例,使目標應用程式中執行的程式碼量最大化,因為執行的程式碼越多,執行易受攻擊程式碼的可能性越高。經典的工具有AFL。
基於覆蓋的Fuzzing缺乏對目標應用程式的語義洞察。這意味著,雖然它能夠檢測到某段程式碼尚未執行,但它無法理解輸入的哪些部分發生變異以導致程式碼被執行。
(2)基於汙點分析的Fuzzing。這樣的模糊測試工具分析應用程式如何處理輸入,以瞭解輸入的哪些部分在未來執行時需要修改。
(動態)符號執行:
符號技術彌合了靜態和動態分析之間的差距,並提供了一種解決方案來應對模糊測試的有限語義洞察力。動態符號執行是符號執行的一個子集,是一種動態技術,因為它在模擬環境中執行程式。
然而,這種執行發生在符號變數的抽象域中。當這些系統模擬應用程式時,它們在整個程式執行過程中跟蹤暫存器和記憶體的狀態以及對這些變數的約束。每當到達條件分支時,執行分支並遵循兩條路徑,將分支條件儲存為對採用分支的路徑的約束,並將分支條件的逆作為對未採用分支的路徑的約束。
四 angr的設計
4.1 設計目標
- 跨架構
- 跨平臺
- 支援不同的分析正規化
- 可用性
4.2 子模組設計
- 中間語言表示。angr借鑑的是libVEX,目前支援ARM、MIPS、PPC和x86和amd64,libVEX專案的研究者目前正在對SPARC架構進行工作移植。
- 二進位制檔案載入。angr的這個模組主要由CLE實行,CLE 是 CLE Loads Everything 的遞迴首字母縮寫詞。 CLE 對不同的二進位制格式進行抽象,以處理載入給定的二進位制檔案和它所依賴的任何庫、解析動態符號、執行重定位以及正確初始化程式狀態。CLE 通過提供眾多表示二進位制物件(即應用程式二進位制檔案、POSIX .so 或 Windows .dll)的基類、這些物件中的段和節以及表示內部位置的符號,為二進位制載入程式提供了一個可擴充套件的介面。CLE 使用檔案格式解析庫(特別是用於 Linux 二進位制檔案的 elftools 和用於 Windows 二進位制檔案的 pefile)來解析物件本身,然後執行必要的重定位以公開已載入應用程式的記憶體映像。
- 程式狀態表示與修改。angr這部分使用的是SimuVEX模組。SimuVEX 提供了通過 VEX 表示的程式碼塊處理輸入狀態的能力,並生成輸出狀態。SimuVEX 模組負責表示程式狀態(即暫存器和記憶體中值的快照、開啟的檔案等)。 在 SimuVEX 術語中名為 SimState 的狀態被實現為狀態外掛的集合,這些外掛由使用者指定的狀態選項控制或在狀態建立時進行分析。 目前,存在以下狀態外掛:
- 暫存器
- 符號記憶體(以幫助符號執行)
- 抽象記憶體
- POSIX
- 日誌記錄
- 除錯
- 求解器
- 架構(用來分析架構的特定資訊)
- 資料模型。儲存在SimState的暫存器和儲存器中的值由另一個模組Claripy提供的抽象表示。這種模組化設計允許 Claripy 以強大的方式組合各種資料域提供的功能,並將其提供給 angr 的其餘部分使用。Claripy 將所有值抽象為表示式的內部表示,該表示式跟蹤使用它的所有操作。具體來說,Claripy 提供支援具體域(整數和浮點數)、符號域(由 Z3 SMT 求解器 提供的符號整數和符號浮點數)和值集抽象域的後端用於值集分析。例如,將後端提供的構造(例如,Z3 後端提供的符號表示式 x+1)解釋為 Python 原語(例如作為約束求解結果的 x+1 的可能整數解)由前端提供。 前端增加了具有不同複雜性的附加功能的後端。Claripy 目前提供了幾個前端:
- 全前端(FullFrontend)。 此前端向使用者公開符號求解、跟蹤約束、使用 Z3 後端求解並快取結果。
- 複合前端(CompositeFrontend)。 正如 KLEE 和 Mayhem等人工作中所建議的,將約束拆分為獨立的集合可以減少求解器的負載。
- 輕量級前端(LightFrontend)。此前端不支援約束跟蹤,僅使用 VSA (值集分析)後端來解釋 VSA 域中的表示式。
- 替換前端(ReplacementFrontend)。 ReplacementFrontend 擴充套件 LightFrontend 以新增對 VSA 值約束的支援。 當引入約束(即 x+1 < 10)時,ReplacementFrontend 會對其進行分析以識別所涉及變數的界限(即 0 <= x<=8)。 當隨後向 ReplacementFrontend 諮詢變數 x 的可能值時,它將與先前確定的範圍相交,從而提供比 VSA 能夠產生的更準確的結果。
- 混合前端(HybridFrontend)。 HybridFrontend 結合了 FullFrontend 和 Replacement-Frontend 為符號約束求解提供快速求解近似值的支援。
- 全程式分析。angr 提供完整的分析,例如動態符號執行和控制流圖恢復。這些分析的“入口點”是Project,它代表一個二進位制檔案及其相關庫。從這個介面,可以訪問其他子模組的所有功能(即,建立狀態、檢查共享物件、檢索基本塊的中間表示、使用 Python 函式掛鉤二進位制程式碼等)。此外,還有兩個用於全程式分析的主要介面:路徑組(Path Group)和分析(Analyses)。
- PathGroup 是動態符號執行的介面——它在路徑通過應用程式、拆分或終止時跟蹤路徑。
- angr 通過 Analysis 類為任何完整的程式分析提供抽象表示。 此類管理靜態分析的生命週期,例如控制流圖恢復和下文中介紹的複雜動態分析。
五 angr的技術實現細節
5.1 CFG圖恢復演算法
給定一個特定的程式,angr執行一個從程式的切入點開始的迭代的CFG圖恢復技術,並進行一些必要的優化。angr利用強制執行、向後切片和符號執行的組合,在可能的情況下,恢復每個間接跳轉的所有跳轉目標。此外,它還生成並儲存了關於目標應用程式的大量資料,這些資料可以用於以後的其他分析,如資料依賴跟蹤。
該演算法有三個主要缺點:它速度慢,不能自動處理“死程式碼”,而且可能會錯過只有通過未恢復的間接跳轉才能到達的程式碼。
為了解決這個問題,angr建立了一個輔助演算法,該演算法使用二進位制檔案的快速反彙編技術(不執行任何基本塊),然後使用啟發式演算法來識別函式、函式內控制流和直接的函式間控制流轉換。然而,次要演算法的準確性要低得多——它缺乏關於函式之間可達性的資訊,對上下文不敏感,並且無法恢復複雜的間接跳躍。
在本節將討論angr稱為 CFGAccurate 的高階恢復演算法,以及快速演算法 CFGFast。
假設:
angr首先對被測試的二進位制檔案做了幾個假設,以優化演算法的執行時間:
- 程式中的所有程式碼都可以分佈到不同的函式中。
- 在控制流中,所有函式要麼由
- 每個函式的堆疊清理行為都是可預測的,無論它是從哪裡呼叫的。這讓 CFGAccurate 在分析呼叫函式時安全地跳過它已經分析過的函式並保持堆疊平衡。
做這些假設的目的是假設這些被測試的程式能夠正常執行, 在分析混淆或異常的二進位制檔案時(如惡意程式碼),可以不考慮這些假設,但這會導致 CFG 恢復的執行時間更長。
迭代的CFG圖生成:
具體來說,使用了四種技術:強制執行、輕量級反向切片、符號執行和值集分析。 要通過這些技術迭代恢復的 CFG,在應用程式的入口點使用基本塊進行初始化。
在 CFG 恢復過程中,CFGAccurate 維護一個間接跳轉列表 Lj,其跳轉目標尚未確定。當分析識別出這樣的跳轉時,將被新增到 Lj中。
在每輪迭代技術結束後,CFGAccurate 觸發列表中的下一個跳轉。 下一輪的技術可能會解決 Lj 中的跳轉問題,可能會向 Lj 新增新的未解決跳轉,並且也可能會向 CFG中新增基本塊和邊。
當所有技術的執行使得 Lj 或 C 沒有變化時,CFGAccurate 終止,因為這意味著任何可用的分析都無法解決進一步的間接跳轉。
第一階段 強制執行技術:
定義: 強制執行確保條件分支的兩個方向都將在每個分支點執行,就是說,if(a>0)會有兩個分支,這兩個分支都會執行。angr中參考的強制執行技術的原論文
定義的引數:基本塊的工作列表 Bw 和分析塊的列表 Ba,CFG圖 C。
分析開始時,使用 C 中但不在 Ba 中的所有基本塊初始化其工作列表。每當 CFGAccurate 分析此工作列表中的基本塊時,基本塊和來自該塊的任何直接跳轉都會新增到 C 中。
但是,不能以這種方式處理間接跳轉。在強制執行下,間接跳轉的目標可能與程式實際執行的目標不同,因為強制執行會以意想不到的順序執行程式碼。因此,每個間接跳轉都儲存在列表 Lj 中以供後續分析。
由於它無法解決任何間接跳轉,因此該分析用作 快速處理的 CFG 恢復分析,以快速地為其他分析提供檢測到的基本塊和未解決的間接跳轉。
第二階段 符號執行:
對於每個跳轉J∈ Lj,CFGAccurate向後(一般來說是從下往上)遍歷CFG,直到找到第一個合併點(即,在通往間接跳轉的路上“萬路歸一”的點)或達到閾值塊數(根據研究經驗,發現合理的閾值為8)。在此基礎上,CFGAccurate對間接跳轉執行正向符號執行,並使用約束求解器檢索間接跳轉目標的可能值。
CFGAccurate中規定:如果計算出的可能的目標集小於閾值大小,則跳轉成功解決。angr規定這個計算閾值為256,但在實踐中,在跳轉未成功解決的情況下,該值是不受約束的(這意味著,可能的目標集僅受地址中的位數限制)。
如果跳轉目標的問題解決,則從Lj中刪除J,併為跳轉目標的每個可能值將邊和節點新增到CFG中。
第三階段 後向切片:
上面兩種方法是針對上下文不敏感的方式進行,如果引數為函式指標,並用該指標作為簡介跳轉的目標,則上面兩種分析是無法解決的。
這意味著CFG的生成需要一個具備上下文敏感的元件。angr通過後向切片來實現這一點。切片擴充套件到上一個呼叫上下文的開頭。也就是說,如果正在分析的間接跳轉位於從Fb和Fc呼叫的函式Fa中,則切片將從Fa中的跳轉向後延伸,幷包含兩個開始節點:Fb開始處的基本塊和Fc開始處的基本塊。
然後,CfgAccurate使用Angr的符號執行引擎執行該切片,並使用約束引擎來識別符號跳轉的可能目標,對於跳轉目標的解集的大小具有相同的閾值256。如果跳轉目標被成功解析,則從Lj和表示控制流轉換的邊中移除跳轉,並且將目標基本塊新增到恢復的CFG中。
第四階段 CFGFast
前面三階段的CFG圖恢復技術知識能夠勉勉強強的識別二進位制檔案中函式的位置和內容,但是缺乏控制流的表示。
CFGFast旨在快速恢復具有高覆蓋率的CFG,而不必考慮瞭解函式之間的可達性。分為三個步驟:
- 函式識別。使用硬編碼的函式序言簽名來識別應用程式內部的函式,該簽名可以通過ByteWeight等技術生成。
- 遞迴反彙編。遞迴反彙編用於恢復已識別函式內的直接跳轉。
- 間接跳轉的解決。將輕量級別名分析、資料流跟蹤與預定義策略相結合,解決函式內的控制流傳輸識別。目前,CFGFast包括用於跳轉表識別和間接呼叫目標解析的策略
上面四個階段為一個完整的CFG恢復技術流程。
5.2 值集分析 VSA
值集分析是一種二進位制程式的靜態分析技術,該技術結合數值分析和指標分析,使用一個成為值集抽象域來近似暫存器或抽象位置在每個程式點可能儲存的值,這個程式點一般稱為固定點(fix-point)。例如,對於向地址A的儲存器寫入操作,查詢並計算出的固定點中的A的值將包含所有可能的寫入目標的完整列表。
VSA演算法原文
第一階段 建立一組離散的跨步間隔
VSA的基本資料型別,即跨距間隔(strided interval),本質上是一組數字的近似值。它非常適合近似一組正常的具體值。然而,如果這些值在程式中被用作跳轉的目標,則跨步間隔的過度近似性質,會通過建立指向不應成為跳轉目標的地址的控制流轉換,在恢復的 CFG 過程中產生不合理性。
為了有效解決這個問題,angr開發了一種稱為“跨步區間集”的新資料型別,它表示一組未聯合在一起的跨步區間。
僅當跨步區間集合包含多於K個元素時,跨步區間集合才會被合併為單個跨步區間,其中K是可以調整的閾值。較高的K值能夠保持高精度,但代價是增加了分析的複雜性。
第二階段 將代數求解器應用於路徑謂詞
跟蹤分支條件有助於在條件退出後或合併過程中約束狀態中的變數,從而產生更精確的分析結果,跟蹤分支條件對應的技術為 ,然而,它不僅實現起來很複雜(通常導致在約束表示式中支援很少的算術運算),而且在現實中計算成本很高。
angr的解決方案是實現一個輕量級的代數求解器,該求解器基於處理一些仿射關係的模算術在跨步區間域上工作。當發現新的路徑謂詞時(即,當遵循條件分支時),嘗試簡化並求解它,以獲得路徑謂詞中涉及的變數的數字範圍。
然後,執行新生成的數字範圍與每個相應變數的原始值之間的交集。這能夠在遇到新的分支條件時不斷改進值集分析的結果,從而提高最終固定點的精度。
第三階段 採用符號不可知域
angr基於
分三個階段使用 VSA 進行記憶體損壞檢測:
- 首先,在 VSA 分析期間收集程式中的所有讀寫訪問模式。在這些訪問模式之上,對堆疊和堆區域上的變數執行變數恢復。 實現類似於
- 接下來,掃描所有堆疊和堆區域以查詢異常緩衝區,包括 a) 重疊緩衝區和 b) 越界緩衝區。 然後簡單地將所有異常緩衝區報告為潛在的記憶體損壞。
angr中對應的VFG圖(CFG的增強版),就包含每個程式位置出的VSA固定點的資訊。VFG中包含的程式狀態以SimuVEX提供的抽象佈局(特別是SimAbstractMemory記憶體模型)呈現記憶體,記憶體中的值由Claripy提供的值集表示。angr通過分析記憶體訪問可能採用的值範圍,對這些程式狀態中包含的資料執行了緩衝區重疊分析。
5.3 動態符號執行
angr的動態符號執行模組主要基於
5.4 欠約束符號執行
angr實現了在
UCSE標記在狀態中缺少上下文,為欠約束。當將此類欠約束資料用作指標時,將建立一個新的欠約束區域,並將指標指向新區域。這種“按需”記憶體分配允許對管理複雜資料結構的程式碼進行分析。當識別出安全違規(即寫入堆疊上儲存的返回地址)時,將檢查所涉及的值是否處於欠約束狀態。在某些情況下(即,如果所有涉及的資料都未得到充分約束),違規將被過濾為假陽性。
angr的改進如下:
全域性記憶體欠約束
最初的UCKLEE的實現並沒有將全域性記憶體訪問視為受限。然而,這種記憶體是UCSE無法預測的程式上下文的一部分,因為在分析給定函式時,全域性資料可能已經被覆蓋。因此,angr將所有全域性資料標記為欠約束,從而降低了誤報率。
路徑限制
最初的UC-KLEE實現有幾個內建限制,以防止路徑爆炸。例如,它們將限制欠約束指標解引用的深度,以避免通過從不終止的欠約束連結串列進行搜尋。angr增加了一個額外的限制器:當發現函式導致路徑爆炸時,中止對函式的分析。angr通過硬編碼限制來檢測這一點,當單個函式分支到這麼多條路徑上時,用立即返回來替換函式,並從該函式的呼叫位置回放分析。這通過避免路徑爆炸保持了分析的可操作性,但使分析更加不準確。
六 總結
以上是angr的核心技術實現的介紹,可以說我這篇博文介紹的還是比較淺顯,因為從某種程度上來說,我只是在原論文翻譯的基礎上,新增了一點我的理解以及別人的理解,後面的我的部分博文將會針對部分angr的核心技術,進行相應的基礎知識介紹,以方便能理解angr是個多麼偉大的工作。
(本篇終)