<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】代碼指針完整性

    一顆小胡椒2022-04-06 08:18:24

    控制流劫持攻擊是當前較為主流的攻擊方式之一,包括ROP、JOP等等。相應的緩解、防御措施則包括數據執行保護DEP、棧保護、地址隨機化ASLR、控制流完整性CFI等等。以上措施是大家比較常見和常用的,也有許多人寫了大量的相關文章進行分析。現在我想給大家介紹很少有人提及的一種防御措施——代碼指針完整性CPI,希望對大家有所裨益,錯漏之處,還請大家不吝斧正。

    CPI即code-pointer integrity,是由Volodymyr Kuznetsov(可譯作庫茲涅佐夫,很帶感有沒有,推薦大家去看一看他的視頻,那英語更帶感)等人于2014年提出的一種防御控制流劫持攻擊的機制。核心思想是將進程占用的內存劃分為安全區(safe region)和常規區(regular region),并基于硬件(也有軟件方式,但不常用)對兩個區域做了隔離。安全區只能存放敏感指針和元數據(metadata,用來描述敏感指針指向對象的值,地址上下界,以及分配的時序id,如下圖所示),同時對安全區的訪問要么是在編譯時證明安全的,要么是通過運行時安全檢查的。對常規區的訪問沒有任何特殊之處。

    內存分區如下圖所示:

    比如以下這段代碼:

    在CPI機制下,對應的進程在內存中應該是這樣的:

    CPI假設攻擊者已經有足夠強大的能力,他們:

      對進程內存有完全的控制權,但不能修改代碼段可以對任意地址

      進行讀寫不能干涉程序加載這樣的假設已經大大高估了攻擊者的能力,同時也    能保證代碼插樁的進行和區域隔離的實施。

    在具體實施時,是通過以下步驟來進行的:

      對源碼進行靜態分析

    靜態分析時將會對敏感指針進行識別。如果一個指針的類型是敏感的,那么這個指針就是敏感的。敏感類型主要包括:

      指向函數的指針

      指向敏感類型的指針

      有成員是敏感類型的復合類型(結構體或數組)

      泛型指針(void*, char*, 在定義結構體或類以前就聲明的指針)

      用戶自定義的敏感類型 (如存儲有操作系統UID信息的結構體)

      所有在編譯或運行是隱式生成的代碼指針(返回地址,C++虛函數表,setjmp   緩存)

    另外,所有對敏感指針進行操作的指令也需要被識別,主要包括:

      解引用

      指針運算

      分配或釋放內存

    由于敏感指針類型里包含char*這樣的泛型指針,所以靜態分析時會高估靜態指針數量,為了減少開銷,CPI將作為libc字符串函數參數和指向常量的char*指針認為是不敏感的。

    代碼插樁

    代碼插樁的目的在于:

      保證所有敏感指針存儲在安全區

      運行時創建和傳遞這類指針的相關元數據

      解引用時檢查元數據

    插樁時將會在安全區和常規區都分配空間給敏感指針,但同時只能有?個是有效的。這樣做能夠解決內存布局改變帶來的兼容性問題,同時也能避免類似void*這樣的指針的敏感性發生改變帶來的問題。這樣的方案能夠按照指針在常規區的位移來計算其在安全區的相應地址。

    靜態分析過程中已經找到了對敏感指針進行操作的指令,插樁將會對其進行針對性的改寫,創建新的或者是直接沿用之前已有的元數據。其中,對敏感指針進行讀取和存儲的指令將會由CPI機制設計的指令代替以將敏感指針從安全區取出或者是存入安全區。call指令和ret指令的保護將會通過安全棧(safe stack)來進行。

    敏感指針的每次解引用都要進行插樁,以便在運行時檢查元數據來檢測該指針是否安全。泛型指針在安全區和常規區都占有內存,若不是敏感指針,則將其元數據中的下界設置為大于上界,這樣一來,訪問元數據時就會認定為非法訪問,從而轉為訪問常規區進行相應操作。

    以下是插樁過程中提供的部分接口函數:

    隔離安全區

    實施隔離的具體措施是與系統架構相關的。主要有:

    1. x86-32架構

    在此架構下,CPI依賴于硬件段保護,使得安全區只能通過特定、專用的段寄存器訪問,實際上是將該寄存器當做程序裝載器來使用。考慮如下代碼片段:

    隔離后實際指令變為:

    此處使用gs寄存器來實施隔離,而syscall指令往往也要訪問該寄存器,所以CPI改寫指令,禁用了相關操作。

    1. x86-64架構

    此架構下段保護已無法保證,但仍可以基于信息隱藏完成隔離,因為常規區中的地址不會指向安全區(否則就是敏感指針),信息也就不會泄露(作者認為這是一個事實,但有趣的是,這其實只是一個不總是成立的假設)。該架構下48bit(Linux內核將x86-64的進程地址空間定義為“48 bit – 1 protect page”)的地址空間也可以做到防止暴力破解,攻擊者在試圖破解之后程序可能會崩潰(這是另外一個作者認為是事實的假設)。

    其他架構可以使用地址隨機化或者是software fault isolation(翻譯沒有英文那么準確,姑且用其英文名)。

    敏感指針只占很少一部分,且空間分布高度分散。為了節省內存,采用哈希表、多級查找表,或者是將地址作為下標的數組來實現。

      安全棧

    為了減少開銷和降低復雜度,CPI特別引入了安全棧機制。因為棧上存放的返回地址等數據需要被頻繁訪問。

    絕大多數棧上數據的訪問能在編譯時就驗證其安全性,無需運行時來檢查,且大都是通過esp寄存器加上棧內偏移來訪問的。因此,CPI把所有這些被證明是安全的對象存放到安全區的某片區域,稱為安全棧。如果函數中的內存對象(全局或局部變量,動態分配的內存塊等)需要檢查,則在常規區給他們分配?塊獨立的棧幀。

    安全棧的實施同樣需要進行靜態分析和插樁,靜態分析得安全棧需要包含哪些對象,插樁完成相應結構的填充。另外安全棧還進行了運行時支持(runtime support),為每一個線程分配常規區上的棧,要么作為線程庫的一部分,要么用來干預線程的創建和銷毀。

    為了更高的性能,弱化版本的CPI——Code-Pointer Separation(CPS)被提出。相比CPI,CPS對敏感指針的認定有所放松,只識別代碼指針,指向代碼指針的指針不再是敏感的。同時,代碼指針只指向控制流目標(control flow destination)。控制流目標是代碼段的某個位置,包括函數入口和返回地址等等。這樣的機制可以防止偽造代碼指針,但是無法阻止攻擊者對代碼指針的讀寫。同時,由于控制流目標是精確的某一個位置,無需上下界等信息,CPS的敏感指針是在編譯時就能確定的,是靜態的,不用再分配id(當卸載共享庫時情況有所不同,需要特殊處理)所以無需引入元數據,使得安全區范圍縮小了,同時對代碼指針的訪問次數也大大減少。

     形式化證明

    為了驗證該機制的正確性,作者做了形式化的證明。水平有限,我嘗試為大家梳理一下。

    首先,定義運行時環境為E,它是一個三元組,為以下形式:

    S表示將某變量映射到其類型和地址,Mu是常規區的某一地址,存儲著一個值,記作v,Ms是安全區某一地址,存儲著某值

    和其上下界信息v(b,e),b為下界,e為上界,或者被標記為none。

    對Mu和Ms的操作有以下幾類:

    涵蓋了讀寫和內存分配操作。操作的結果有以下幾類:

    v(b,e)和v表示安全,OK代表操作成功,OutOfMem、Abort代表錯誤。lu和ls是有某個左表達式產生的位置(location)。

    a表示原子類型int或者是p*。

    左表達式lhs包括某變量,結構體成員,指針解引用。

    右表達式包括整數、函數地址,左表達式、左表達式地址、指針大小、為右表達式分配內存情況。

    特別地,lhs=rhs表示變量賦值等操作。

    表示左表達式分配至安全區或是常規區。

    表示將右表達式分配至安全區或是常規區,可能還伴有運行時環境的轉換(E和)。

    表示執行命令c,得到結果r,可能伴有運行時環境的轉換(E和)。

    表示取某函數地址,得到其位置l,同時返回結果r。

    表示分配i單位內存,得到某位置上下界l、l+i,同時返回結果r。

    以上三個式子表示對敏感指針的操作(如解引用等)將會被判定,返回結果r。

     

    是對讀寫操作進行判定。對從安全區讀出的值進行檢查,如果與元數據信息(上下界)相符,則可以進行后續操作,否則返回Abort(錯誤)。對寫入安全區的數據也進行檢查,通過之后返回OK。

    表示任何經由常規區對安全區的訪問都是非法的,返回Abort。

    處理了泛型指針的敏感性會動態變化的情形。由于泛型指針在安全區和常規區均占有內存,讀取時先在安全區標記為none,然后從常規區直接取值。寫操作是類似的,直接寫入常規區,然后將安全區標記為none,成功返回OK。

    對于常規區的操作無需任何干涉,也不會產生任何問題,直接返回OK。

    函數的直接調用是很簡單的,如果函數指針位于安全區(ls),則返回OK,位于常規區(lu)則返回Abort。

    至此,對于敏感指針操作狀態的情況已經討論完畢,其判斷路徑是完備的,CPI機制的正確性也就證明了。

    測試

    為了驗證有效性,CPI基于RIPE(runtime intrusion prevention evaluator,運行時入侵防御檢測,能自動生成溢出攻擊代碼,有興趣的可以看一下https://github.com/johnwilander/RIPE),進行了測試,成功進行了防御。并對新出現的幾種能繞過DEP、ASLR、CFI等機制的攻擊也實現了防御。

    CPI基于SPEC CPU2006標準,對100多個軟件包進行了測試,結果如下:

    統計結果如下:

    可以看到,CPI,尤其是CPS的表現還是相當不錯的。

    同時,在WEB平臺上也做了測試(FreeBSD + Apache + SQLite + mod_wsgi + Python +Django),結果如下:

    至此,對 CPI的介紹就完成了。

    CPI進行了形式化的證明,證明了其能百分百防御控制流劫持攻擊,這一點是毋庸置疑的。但正如前文所提到的,CPI依賴于兩個實際上是假設的“事實”,這就為針對它的攻擊(至少是針對某一種具體實現方式的攻擊)提供了可能。欲知后事如何,請聽下回分解。

    指針cpi
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    控制流劫持攻擊是當前較為主流的攻擊方式之一,包括ROP、JOP等等。
    kernel-pwn之ret2dir利用技巧
    QEMU逃逸系列
    2022-12-01 09:19:27
    qemu用于模擬設備運行,而qemu逃逸漏洞多發于模擬pci設備中,漏洞形成一般是修改qemu-system代碼,所以漏洞存在于qemu-system文件內。而逃逸就是指利用漏洞從qemu-system模擬的這個小系統逃到主機內,從而在linux主機內達到命令執行的目的。
    然而在內核態中,堆內存的分配策略發生了變化。并把這個slab劃分為一個個object,并將這些object組成一個單向鏈表進行管理,這里需要注意slub系統把內存塊當成object看待,而不是伙伴系統中的頁。本次選擇演示的例題是2019-SUCTF的sudrv例題,查看start.sh中的信息可以發現開啟了kaslr保護與smep保護。
    Kernel PWN從入門到提升
    2023-03-23 10:17:57
    所以我決定用此文章結合一道不錯的例題盡可能詳細的來講一下kernel pwn從入門過渡到較高難度的部分,供想要學習kernel pwn的小伙伴們參考。文件系統kernel題一般都會給出一個打包好的文件系統,因此需要掌握常用到的打包/解包命令。
    00 前言在 HWS2021 入營選拔比賽的時候,遇到了一道 QEMU 逃逸的題目,那個時候就直接莽上去分析了一通,東拼西湊的把 EXP 寫了出來。但是 QEMU 逃逸這部分的內容實在是比較復雜,而且涉及到了很多我完全沒有了解過的知識,所以一直鴿到了現在。System mode:系統模式,在這種模式下,QEMU 可以模擬出一個完整的計算機系統。
    能運行的環境包括I/O,權限控制,系統調用,進程管理,內存管理等多項功能都可以歸結到上邊兩點中。需要注意的是,kernel 的crash 通常會引起重啟。注意大多數的現代操作系統只使用了 Ring 0 和 Ring 3。
    感覺算是一個很不錯的IoT固件分析入門教程,今天收到《路由器0day》后在路上粗略地看了下目錄,除了沒有涉及到硬件外,這個教程差不多把固件分析的起始工作都涉及到了。
    從代碼視角來分析漏洞問題
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类