Control-Flow Integrity(控制流完整性) 的原理 ——本質上就是一個hash表記錄持續返回地址 然后運行中對比 發現是否代碼被惡意篡改


 

展開
本文討論的原理基於Control-Flow Integrity Principles, Implementations, and Applications這篇論文。

1 回顧為什么需要CFI
1.1 控制流劫持
攻擊者能夠通過控制流劫持來獲取目標機器的控制權,甚至進行提權操作,對目標機器進行全面控制。
早期的攻擊通常采用代碼注入的方式,通過上載一段代碼,將控制轉向這段代碼執行。
代碼重用攻擊使得硬件支持下的DEP保護機制仍能被繞過。
1.2 早期防范措施
堆棧金絲雀[Cowan et al. 1998],運行時消除緩沖區溢出[Ruwase and Lam 2004]等。
局限性:緩解范圍有限,性能損失高,依賴於硬件修改等。
What we need:高可靠性,易於理解,強制執行,可部署性,低開銷。
2 CFI概述
2.1 間接指令
CFI關注的是間接指令,所以在這里對匯編語言中不同尋址方式的指令進行補充說明。

在匯編語言中,根據尋址方式的不同可以分為兩種跳轉指令。一種是間接跳轉指令,另一種是直接跳轉指令。

直接跳轉指令的示例如下所示:

CALL 0x1060000F
1
在程序執行到這條語句時,就會將指令寄存器的值替換為0x1060000F。這種在指令中直接給出跳轉地址的尋址方式就叫做直接轉移。在高級語言中, 像if-else,靜態函數調用這種跳轉目標往往可以確定的語句就會被轉換為直接跳轉指令。

間接跳轉指令則是使用數據尋址方式間接的指出轉移地址,如:

JMP EBX
1
執行完這條指令之后,指令寄存器的值就被替換為EBX寄存器的值。它的轉換對象為作為回調參數的函數指針等動態決定目標地址的語句。

在CFI中還有一個比較特殊的分類方式,就是前向和后向轉移。將控制權定向到程序中一個新位置的轉移方式, 就叫做前向轉移, 比如jmp和call指令;而將控制權返回到先前位置的就叫做后向轉移,最常見的就是ret指令。

將以上兩種分類方式結合起來,前向轉移指令call和jmp根據尋址方式不同又可以分為直接jmp, 間接jmp,直接call,間接call四種。而后向轉移指令ret沒有操作數,它的目標地址計算是通過從棧中彈出的數來決定的。正因為ret指令的特性,引發了一系列針對返回地址的攻擊。

2.2 首次提出
Abadi等人, 2005年(本篇主要討論的論文): 通過保證控制流的完整性來實現軟件防篡改的方法。

2.2.1 核心思想
限制程序運行中的控制轉移,使之始終處於原有的控制流圖所限定的范圍內。
它規定軟件執行必須遵循提前確定的控制流圖(CFG)的路徑。

2.2.2 具體做法
通過分析程序的控制流圖,獲取間接轉移指令(包括間接跳轉、間接調用、和函數返回指令)目標的白名單,並在運行過程中,核對間接轉移指令的目標是否在白名單中。

通過二進制代碼重寫實現:插樁—— IDs ID檢查

利用二進制重寫技術向軟件函數入口及調用返回處分別插入標識符ID和ID_check,通過對比ID和ID_check的值是否一致判斷軟件的函數執行過程是否符合預期,從而判斷軟件是否被篡改。

2.3 示例:通過插樁執行CFI
CFI要求在程序執行期間,只要機器代碼指令轉移控制,只能轉移到有效目標,這是由提前創建的CFG確定的。

文中提到,期望在不久的將來部署硬件CFI支持是不現實的,所以該文章僅討論軟件CFI實現(也是有局限性的,在提出和發展那篇里曾提到)。內聯CFI插樁可以在當前處理器上的軟件中實現,特別是在x86處理器上,只需要適度的開銷。

CFI插樁根據給定的CFG修改每個源指令和計算控制流傳輸的每個可能的目標指令。

示例:

左側是一個C程序片段,其中函數sort2調用sort的函數排序兩次,首先使用lt,然后使用gt。它們作為指向比較函數的指針。右側顯示了這四個函數的二進制代碼塊的輪廓以及它們之間的所有CFG邊。在圖中,直接調用的邊為淺色虛線箭頭; 源指令的邊為實線箭頭,返回邊為虛線箭頭。 在此示例中,sort可以返回到兩個不同的位置。

因此,CFI檢測包括sort2主體中的兩個ID,以及從排序返回時的ID檢查,使用55(這里是隨意使用55來表示)作為ID位模式。同樣,因為sort可以調用lt 或者gt,兩個比較函數都以ID 17開頭; 並且使用寄存器R中的函數指針的調用指令對17執行ID檢查。最后,ID 23在sort中標識比較調用點之后的塊,因此兩個比較函數都檢查返回ID 23。

CFI檢測不會影響直接函數調用:只有間接調用需要ID檢查,並且只有間接調用的函數(例如虛方法)才需要添加ID。

函數返回多個ID檢查時,必須在每個函數調用點之后插入ID,無論該函數是否間接調用。剩余的計算控制流通常是switch語句和異常的結果。在兩種情況下,每個可能的目標都需要一個ID,並且在發送點需要ID檢查。

2.4 CFI插樁代碼
選擇特定的二進制碼序列實現ID和ID檢查。


上圖中,這里,目標已在ecx中,所以ID檢查不必將其移動到寄存器(通常ID檢查需要這樣做來避免競爭條件)。跳轉指令jmp ecx的目標可能是來自堆棧的mov(下圖所示)。

在(a)中,ID作為數據插入到目標mov指令之前,並且ID檢查使用lea指令修改計算的目標,以跳過四個ID字節。ID檢查直接將原始目的地與ID值進行比較。ID位模式嵌入在ID-check cmp操作碼字節內。 因此,在(a)中,可能以某種方式影響ecx寄存器的值的攻擊者可能會導致跳轉到jne指令而不是預期的目標。

(b)通過在ID檢查中使用ID-1作為常量並將其遞增以在運行時計算ID來避免(a)的微妙之處。 另外,替代方案(b)不修改計算的跳轉目標,而是有效地在目標的開始處插入labelID:使用無副作用的x86預取指令來合成labelID指令。

 

2.5 CFI的三個重要假設
實現CFI,三個假設成立至關重要。 這三個假設是:

UNQ. 唯一ID:在CFI檢測之后,除了ID和ID檢查之外,選擇為ID的位模式不得出現在代碼存儲器中的任何位置。通過使ID足夠大(例如32位,對於合理大小的軟件)並且通過選擇ID使得它們不與軟件的其余部分中的操作碼字節沖突,可以容易地實現該屬性。

NWC. 不可寫代碼:程序必須無法在運行時修改代碼內存。否則,攻擊者可能能夠繞過CFI,例如通過覆蓋ID檢查。除了在加載動態庫和運行時代碼生成期間,NWC在大多數當前系統中已經是正確的。

NXD. 不可執行數據:程序必須不能像執行代碼那樣執行數據。否則,攻擊者可能會導致執行標有預期ID的數據。最新的x86處理器上的硬件支持NXD,Windows XP SP2使用此支持來強制分離代碼和數據[Microsoft Corporation 2004]。 NXD也可以用軟件實現[PaX Project 2004]。NXD本身(沒有CFI)阻止了一些攻擊,但不適於那些利用預先存在的代碼的攻擊,例如“jump-to-libc”攻擊。

2.6 CFI執行的階段
第一階段,即用於CFI執行的CFG的構建,從程序分析到安全策略規范。實際實施可以使用標准控制流分析技術(例如,[Aho et al. 1985; Atkinson 2002; Wagner and Dean 2001])。

在CFI檢測之后(可能在安裝時),另一種機制可以建立UNQ假設。無論何時安裝或修改軟件,都可以更新ID以保持唯一性,就像某些操作系統中的預綁定信息一樣[Apple Computer 2003]。

最后,CFI驗證階段可以靜態驗證直接跳轉和類似指令,正確插入ID和ID檢查以及UNQ屬性。驗證可以看作是PCC校對檢查的一個特例,其中插樁不需要明確的邏輯校驗。建立CFI只需要驗證:設備中的設計或實施缺陷不會危及安全性。

3 CFI實施
Vulcan [Srivastava et al.2001]:一個成熟的、最先進的x86二進制文件檢測系統,既不需要重新編譯也不需要源代碼訪問。該系統以實用的方式解決了二進制代碼重寫的挑戰。

使用Vulcan來構建正在檢測的程序的CFG。這個CFG構造正確處理執行計算控制流傳輸的x86指令,包括函數返回,通過函數指針調用,以及為switch語句和動態調度發出的指令。每個計算出的調用指令可以轉到任何采用其地址的函數:通過對二進制文件中的重定位條目進行流不敏感分析來發現這些函數。
————————————————
版權聲明:本文為CSDN博主「SuckerForPain」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zko1021/java/article/details/85250383


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM