Black Hat Europe 2025 | Ghost In The Stack: Evolving Call Stack Spoofing In A Post-CET Era
三句話摘要
Windows 堆棧欺騙技術在 CET(Control-flow Enforcement Technology)時代的進化與檢測規避方法。 堆疊欺騙從規避記憶體執行異常的防禦手段,演變為對抗 CET 的展開資料改寫技術,雖然檢測機制持續進化,但改變攻擊向量(從堆疊到展開資訊)仍可繞過現有防禦,代表對手防禦雙方的對抗空間仍未關閉。 堆疊展開原理與欺騙基礎:Windows 執行緒透過呼叫堆疊追蹤函數執行流,展開器透過 Runtime Function Table(存於 pdata/xdata section)查詢展開資訊恢復返回地址。堆疊欺騙原理是篡改堆疊框架大小,讓展開器誤以為代理函數直接呼叫目標函數,從而隱藏中間的惡意程式碼執行。
重點整理
重點- 1
堆疊展開原理與欺騙基礎:Windows 執行緒透過呼叫堆疊追蹤函數執行流,展開器透過 Runtime Function Table(存於 pdata/xdata section)查詢展開資訊恢復返回地址。堆疊欺騙原理是篡改堆疊框架大小,讓展開器誤以為代理函數直接呼叫目標函數,從而隱藏中間的惡意程式碼執行。
- 2
檢測與規避的對抗升級:早期檢測著眼於 jmp gadget 和缺失的 call 指令,但講者展示如何選用有正常 call 前導的 gadget、跨越有效轉移位址規則等方式逃脫。Elastic 的 Call Stack Enrichment Detection 整合多個指標,但仍存在校驗邏輯漏洞。
- 3
CET Shadow Stack 破局:CET 引入影子堆疊防止 ROP 鏈執行,使傳統堆疊欺騙失效。講者改變思路,改寫 NDR_SERVER_CALL_TWO 等代理函數的展開資料,實現在 CET 環境下的執行隱蔽性——堆疊本身不變,只改變展開解析結果。
- 4
堆積上的虛擬機框架:最隱蔽的做法是在堆積動態注入執行框架,利用不規則虛擬機狀態機維持程式流,再搭配代理函數的已注入展開資訊,使影子堆疊與標準堆疊展開一致,規避檢測同時保持功能性。
實用技巧與重點
乾貨- 關鍵數字與比例
- 虛擬 alloc small 到 alloc large 轉換:3 位元組 → 7 位元組
- Virtual alloc XNUMA 作為掩蔽 gadget 可規避 98% Windows 正常呼叫檢測
- 函數與技術名稱
- NDR_SERVER_CALL_TWO(RPC 代理函數)
- RTL_ADD_FUNCTION_TABLE(動態運行時函數表註冊)
- Virtual Alloc / Virtual Alloc XNUMA
- SetFpReg frame / PushReg frame
- RopNop landing area
- Shadow Stack(CET 防禦機制)
- 檢測方法
- Elastic Call Stack Enrichment Detection
- Unwind Inspector(跨進程 DLL 對比檢測)
- 運行時函數表項數量檢查
- 展開資料內容對比(alloc small vs alloc large)
- Runtime Function 位址範圍檢查
- 動態運行時函數表檢測
- 規避策略清單
- 直接展開資料改寫(修改 Rdata section)
- NIJAC(修改 Runtime Function 的展開資料偏移)
- 運行時函數注入(在 Text section 注入函數並添加表項)
- 未使用函數重用(劫持二進位內未使用函數的位址範圍)
- JIT 版本(在堆積註冊動態 Runtime Function,不修改 Rdata)
- 虛擬機狀態機(在堆積建構執行框架,使用代理函數隱蔽跳轉)
- 工具與資源
- GitHub: 搜尋 "RPC proxy in bulk" 獲得前期研究
- Elastic 防禦研究(2020-2023)
- 講者部落格:即將釋出完整技術細節
結論
結論“堆疊欺騙從規避記憶體執行異常的防禦手段,演變為對抗 CET 的展開資料改寫技術,雖然檢測機制持續進化,但改變攻擊向量(從堆疊到展開資訊)仍可繞過現有防禦,代表對手防禦雙方的對抗空間仍未關閉。”
完整解析
詳細Windows 堆疊欺騙的基礎在於理解展開機制:執行緒執行時,每個函數呼叫都會在堆疊上建立框架,儲存本地變數和返回位址。當安全軟體審查程式碼時,展開器(unwinder)從指令指標出發,查詢 Runtime Function Table 以計算框架大小並恢復返回位址,逐層展開至程序起點。堆疊欺騙的目標就是篡改這些框架大小,使展開器誤認為執行流與實際不符。
2022 年講者等人發表的 Stack Move Walking 技術透過插入特殊框架(SetFpReg frame 和 PushReg frame),將隱藏的惡意程式碼執行完全隱藏在代理框架下——展開時看到的呼叫者只是 BaseThreadInitThunk,所有原始模組的執行軌跡消失無蹤。但這種方法會在函數返回時崩潰,須透過 Sync frame 呼叫棧修復函數才能恢復執行。檢測工作隨之跟進:Elastic 的防禦整合了多項指標,包括不完整展開、缺乏檔案備份、jmp 返回位址、call 指令驗證等。
CET(Control-flow Enforcement Technology)的出現改變了遊戲規則。Shadow Stack 是一個使用者模式無法直接存取的隱藏堆疊,記錄每個 call 指令的返回位址。當從核心模式回到使用者模式時,系統檢查影子堆疊與實際堆疊是否一致,任何不符都會觸發 CP Fault。這意味著基於 ROP 鏈或直接修改堆疊的舊方法全部失效。講者的突破在於改變攻擊目標——不修改堆疊本身,而是改寫展開資料。透過選用 NDR_SERVER_CALL_TWO 這樣具有簡潔展開結構的代理函數,講者發現可以在其展開代碼中的填充區域(原本的零位元組)注入展開指令,將小幅分配改成大幅分配,使得隱藏程式碼區域完全被框架大小覆蓋。堆疊本身未變,只有展開解析發生改變,因此影子堆疊檢查通過。
進一步的隱蔽化包括多種改寫策略:直接改寫 Rdata section 的展開資料(侵入性最高)、NIJAC 技術(修改 Runtime Function 表項的展開資料偏移)、運行時函數注入(在二進位的 Text section 注入新函數並新增表項)、以及最隱蔽的 JIT 方式(在堆積動態註冊 Runtime Function,完全避免修改原始二進位)。最終形態是「自帶資料」虛擬機框架:在堆積注入一個小迴圈(dispatcher),配合可讀寫與可執行區段,實現不依賴額外 call 指令的間接執行框架。核心思想是將主執行流保持在經過正確展開資訊註冊的代理函數內,當需要呼叫 Windows API 時透過該代理跳轉,返回時再跳回虛擬機迴圈。這樣影子堆疊與標準堆疊展開結果一致,規避檢測同時維持功能性。
檢測這些技術仍有機會,主要利用堆疊展開是進程相依性的特性:在檢查器進程載入相同的 DLL,對比模組中的 Runtime Function 表項、展開資料內容、位址範圍等,即可發現異常——額外的表項意味著注入、展開資料內容不符意味著篡改、函數指標指向堆積意味著動態註冊。但隨著防禦能力提升,堆疊欺騙是否仍值得投入也成為問題;講者認為研究空間仍存在,尤其在例外處理框架和間接調用機制上。
關鍵時刻
Pipeline v2帶時間戳的重點,會在逐字稿層級分析上線後產生。目前請先透過原始影片觀看。
事實查核
Pipeline v2說法查證是下一次管線升級的一部分。KeyFrame 只會顯示它真正能驗證的內容。

