Black Hat USA 2002 - Third Generation Exploitation
三句話摘要
從第一代到第三代漏洞的演進,重點揭示堆結構溢位的利用原理與各種堆管理器的攻擊方式。 隨著直接的棧溢位日漸消失,漏洞利用正向堆管理器、庫函數等更深層、更隱蔽的目標演進,使得除人工程式碼與二進位分析外無他法的時代正式到來。 三代漏洞的本質差異在於攻擊深度與隱蔽性的遞進。第一代漏洞直接操縱硬體機制(返回位址機制),容易偵測;第二代利用編譯器慣例但不直接控制指令指標,隱蔽性提升;第三代則完全脫離棧操作,轉而利用記憶體管理本身的邏輯,使自動偵測基本不可能。
重點整理
重點- 1
三代漏洞的本質差異在於攻擊深度與隱蔽性的遞進。第一代漏洞直接操縱硬體機制(返回位址機制),容易偵測;第二代利用編譯器慣例但不直接控制指令指標,隱蔽性提升;第三代則完全脫離棧操作,轉而利用記憶體管理本身的邏輯,使自動偵測基本不可能。
- 2
堆管理器的多樣性決定了攻擊的複雜性。不同作業系統(Ball-C++、Windows 2000)、不同編譯器、甚至同一應用的自訂實作都有完全不同的堆結構與控制邏輯,每種都需要獨立的利用方案,這創造了安全分析的龐大工作量。
- 3
堆溢位利用的核心機制是操縱堆管理器的控制結構。當相鄰記憶體區塊被標記為「空閒」時,堆管理器會執行合併操作,此時攻擊者預置的假造控制結構會引導指標交換,實現向任意位址寫入的目標。
- 4
自動化防禦的終極困境在於:格式字串漏洞雖然強大卻易被識別(因此快速絕跡),但堆溢位則需要特定數據格式才能觸發,導致模糊測試失效,人工代碼審查成為唯一有效手段。
實用技巧與重點
乾貨- 第一代漏洞機制
- 觸發函數:strcpy、sprintf、gets
- 覆蓋順序:區域變數 → 框架指標 → 返回位址
- 防守方式:邊界檢查、安全函數
- 第二代漏洞機制
- 觸發來源:strncpy、strnccat 邊界外一位元組(off-by-one)
- 利用邏輯:修改框架指標最低位元組 → 函數返回時棧指標偏移 → 控制流劫持
- 難點:依賴編譯器前言/結尾,在複雜迴圈(Base64、MIME 解碼)中難以偵測
- 第三代漏洞:格式字串
- 觸發源:printf 風格函數用使用者數據當格式字串
- 能力:任意記憶體讀寫
- 消滅速度:極快(自動檢測可達 100% 覆蓋)
- 現狀:開源軟體基本絕跡,Checkpoint 曾修復 100+ 漏洞
- 堆溢位:Ball-C++ 實作
- 控制結構大小:4 位元組
- 位置:每個記憶體塊前方
- 合併機制:相鄰空閒區塊自動合併成單一大塊
- 利用路徑:覆蓋次區塊控制結構 → 偽造空閒標記 → 觸發合併 → 指標交換寫入任意位址
- 指標交換格式要求:
- [edx+4] = 寫入目標位址(必須合法分頁)
- [edx+8] = 寫入數值
- [edx] = 特殊值(如 0xfff1ffff,會減去 4 後仍為合法分頁)
- Off-by-One 變體攻擊
- 限制:只能覆蓋次區塊控制結構的最低位元組(設為 0x00)
- 效果:改變堆管理器的代碼分支路徑
- 要求:攻擊者需控制下一個相鄰的記憶體區塊內容
- 堆溢位:Win2k 實作
- 控制結構大小:8 位元組
- 檢查項目:
- Bit 0 of flags(位址+5) = 必須設定
- Bit 3 of flags = 必須設定
- 字段 4(位址+4 的值)< 0x40(保證指標交換位置有效)
- 大小字段 > 0x80
- 指標回溯:當滿足所有檢查時,控制指標往回 24 位元組進入攻擊者控制的緩衝區
- 指標交換:同 Ball-C++,但涉及更複雜的前置條件檢查
- Malloc 整數溢位漏洞
- 觸發場景:malloc(user_size × sizeof(struct)) 當 user_size 過大
- 溢位行為:四捨五入向上對齐時發生整數回繞
- 結果:配置遠小於預期的記憶體,後續寫入破壞堆
- 重點應用:任何接收使用者提供的大小參數的 malloc 呼叫都是危險源
結論
結論“隨著直接的棧溢位日漸消失,漏洞利用正向堆管理器、庫函數等更深層、更隱蔽的目標演進,使得除人工程式碼與二進位分析外無他法的時代正式到來。”
完整解析
詳細漏洞利用技術的演進反映了軟體安全攻防的根本轉變。第一代的棧溢位利用了返回位址機制這一基本硬體特性,但因利用方式直白,一旦程式碳擊(試圖跳轉到非法位址),異常拋出就暴露了漏洞存在。微軟曾聲稱模糊測試能夠發現多數棧溢位,這就是為什麼這類漏洞如今在開源專案中幾乎絕跡。
第二代漏洞的發現者意識到,邊界外僅一位元組的寫入足以改變框架指標最低位元組,進而偏移棧指標。這類 off-by-one 漏洞巧妙地利用了所有 C 編譯器共通的函數前言結構(mov ebp into esp, pop ebp),使得即使沒有直接控制返回位址也能間接劫持控制流。它們隱蔽性更高,在複雜的解碼邏輯中甚至難以被檢測。
第三代的格式字串漏洞是一個轉折點。當開發者將未驗證的使用者輸入直接傳遞給 printf 作為格式字串時,格式字符(如 %x、%n)可以直接讀寫棧上的任意位址。這類漏洞之所以迅速消滅(開源專案基本絕跡),正是因為它們同時具備「極度危險」(可任意讀寫)與「極度明顯」(格式字串的簽名特徵明確)的特性,使自動化工具能達到接近 100% 的檢測率。
堆溢位則打開了全新的戰場。堆是程式動態記憶體的來源,不同平台有完全不同的實作。講者以 Ball-C++ 和 Windows 2000 堆為例,詳細展示了如何透過覆蓋堆的元資料(控制結構)來實現任意寫入。在 Ball-C++ 中,每個配置的記憶體塊前 4 位元組存儲了大小與狀態資訊。攻擊者先溢位目標緩衝區,然後在下一個記憶體塊的控制結構中偽造「空閒」標記。當堆管理器嘗試合併這兩個「空閒」區塊時,它會執行指標交換操作——讀取攻擊者預置的假造控制結構中的位址和數值,將數值寫入位址指向的位置。只要這兩個位置都映射到合法的記憶體分頁,就能實現任意寫入。
更精妙的是 off-by-one 變體:攻擊者只需修改下一個區塊控制結構的最低位元組為 0,即可改變堆管理器的代碼分支,跳過一些檢查而到達相同的指標交換邏輯。但這要求攻擊者同時控制兩個相鄰的記憶體區塊內容。
Windows 2000 的實作將復雜性提升了一個數量級。控制結構變為 8 位元組,管理器加入了多個檢查條件:特定位的標誌位必須設定、大小字段必須在特定範圍內。這些檢查本意是防禦,卻成了攻擊者必須精心構造數據格式才能通過的關卡。RTL_FREE_HEAP 的合併函數(稱為 RTL_COALESCE)龐大複雜,講者指出即使完整反編譯也難以窮盡所有利用路徑。
堆溢位最難對付的特性在於它無法透過簡單的模糊測試發現。傳統模糊測試往往填充大量 'A' 或隨機位元組,但堆管理器的觸發條件要求特定的數據格式——控制結構的位模式必須完全匹配才能走上利用路徑。絕大多數隨機數據根本無法通過那些檢查。這就是為什麼講者強調,堆溢位的尋找與驗證必須依賴人工代碼審查與反彙編分析,沒有自動化的捷徑。
最後,講者指出庫函數(尤其是 malloc)中隱藏的邏輯漏洞同樣致命。當 malloc 接收使用者提供的大小參數時(如 `malloc(user_count * sizeof(struct))`),整數溢位可能導致配置遠小於預期的記憶體,隨後的寫入會無聲地破壞堆結構。這類漏洞之所以危險,不只因為其隱蔽性,更因為它出現在被廣泛使用的基礎函數中,任何調用不當的地方都可能成為突破口。
關鍵時刻
Pipeline v2帶時間戳的重點,會在逐字稿層級分析上線後產生。目前請先透過原始影片觀看。
事實查核
Pipeline v2說法查證是下一次管線升級的一部分。KeyFrame 只會顯示它真正能驗證的內容。

