解析XP版永恆之藍中的一個Bug
0x00 背景
永恆之藍漏洞剛出來時,我可以順利搞定Windows+7/">Windows 7,但在攻擊Windows XP時我一直沒有成功。我嘗試了各種補丁和Service Pack的組合,但利用程式要麼無法成功,要麼會導致系統藍屏。當時我沒有深入研究,因為FuzzBunch(NSA洩露工具集)還有待探索許多點。
直到有一天,我在網際網路上找到了一個Windows XP節點,我想嘗試一下FuzzBunch。令人驚訝的是,在第一次嘗試時,漏洞利用竟然成功了。
那麼問題來了,為什麼在我的“實驗”環境中,漏洞利用無法成功,而實際環境中卻可以?
這裡先揭曉答案:在單核/多核/PAE CPU上NT/HAL的實現有所區別,因此導致FuzzBunch的XP系統攻擊載荷無法在單核環境中使用。
0x01 多條利用鏈
大家需要知道一點,EternalBlue(永恆之藍)有多個版本。網上已經有人詳細分析了Windows 7核心的利用原理,我和 ofollow,noindex" target="_blank">JennaMagius 以及 sleepya_ 也研究過如何將其移植到Windows 10系統上。
然而對於Windows XP而言,FuzzBunch包含一個完全不同的利用鏈,不能使用完全相同的基本原語(比如該系統中並不存在SMB2以及SrvNet.sys)。我在DerbyCon 8.0演講中深入討論過這方面內容(參考 簡報 及 演講視訊 )。
在Windows XP上, KPCR (Kernel Processor Control Region)啟動處理器為靜態結構,為了執行shellcode,我們需要覆蓋 KPRCB . PROCESSOR_POWER_STATE .IdleFunction的值。
0x02 載荷工作方式
事實證明,漏洞利用在實驗環境中沒有問題,出現問題的是FuzzBunch的攻擊載荷。
ring 0 shellcode主要會執行如下幾個步驟:
1、使用現在已棄用的 KdVersionBlock 技巧獲得 nt
及 hal
地址;
2、解析利用過程中需要用到的一些函式指標,如 hal!HalInitializeProcessor
;
3、恢復在漏洞利用過程中被破壞的KPCR/KPRCB啟動處理器結構體;
4、執行 sar-initial-smb-backdoor-ring.html" rel="nofollow,noindex" target="_blank">DoublePulsar ,利用SMB服務安裝後門;
5、恢復正常狀態執行流程( nt!PopProcessorIdle
)。
單核分支異常
在 IdleFunction
分支以及 +0x170
進入shellcode處(經過XOR/Base64 shellcode解碼器初始處理之後)設定硬體斷點(hardware breakpoint)後,我們可以看到搭載多核處理器主機的執行分支與單核主機有所不同。
kd> ba w 1 ffdffc50 "ba e 1 poi(ffdffc50)+0x170;g;"
多核主機上能找到指向 hal!HalInitializeProcessor
的一個函式指標。
該函式可能用來清理處於半損壞狀態的KPRCB。
單核主機上並沒有找到 hal!HalInitializeProcessor
, sub_547
返回的是 NULL
。攻擊載荷無法繼續執行,會盡可能將自身置零來清理現場,並且會設定ROP鏈來釋放某些記憶體,恢復執行流程。
注意:shellcode成功執行後,也會在首次安裝DoublePulsar後執行此操作。
0x03 根源分析
shellcode函式 sub_547
無法在單核CPU主機上正確找到 hal!HalInitializeProcessor
的地址,因此會強制終止整個載荷執行過程。我們需要逆向分析shellcode函式,找到攻擊載荷失敗的確切原因。
這裡核心shellcode中存在一個問題,沒有考慮到Windows XP上所有可用的不同型別的NT核心可執行檔案。更具體一點,多核處理器版的NT程式(比如 ntkrnlamp.exe
)可以正常工作,而單核版的(如 ntoskrnl.exe
)會出現問題。同樣, halmacpi.dll
與 halacpi.dll
之間也存在類似情況。
sub_547
所執行的第一個操作是獲取NT程式所使用的HAL匯入函式。 攻擊載荷首先會讀取NT程式中 0x1040
偏移地址來查詢HAL函式。
在多核主機的Windows XP系統中,讀取這個偏移地址能達到預期效果,shellcode能正確找到 hal!HalQueryRealTimeClock
函式:
然而在單核主機上,程式中並沒有HAL匯入表,使用的是字元表:
一開始我認為這應該是問題的根本原因,但實際上這只是一個幌子,因為這裡存在修正碼(correction code)的問題。shellcode會檢查 0x1040
處的值是否是位於HAL範圍內的一個地址。如果不滿足條件,則會將該值減去 0xc40
,然後以 0x40
增量值在HAL範圍內開始搜尋相關地址,直到搜尋地址再次到達 0x1040
為止。
最終,單核版載荷會找到一個HAL函式,即 hal!HalCalibratePerformanceCounter
:
目前一切操作都沒有問題,可以看到Equation Group(方程式組織)在能夠檢測不同型別的XP NT程式。
HAL可變位元組表
現在shellcode已經找到了HAL中的一個函式,會嘗試定位 hal!HalInitializeProcessor
。shellcode內建了一張表(位於 0x5e7
偏移處),表中包含1位元組的長度欄位,隨後為預期的位元組序列。shellcode會遞增最開始發現的HAL函式地址,將新函式的前 0x20
位元組與表中位元組進行對比。
我們可以在多核版的HAL中找到待定位的5位元組資料:
然而,單核版的HAL情況有所不同:
這裡有一個類似的 mov
指令,但該指令並不是 movzx 指令。這個函式中並沒有shellcode搜尋的位元組序列,因此shellcode發現不了這個函式。
0x04 總結
大家都知道,在Windows的不同版本和Service Pack中,想通過搜尋位元組序列來識別函式並不是一件靠譜的事情(這一點我們可以從Windows核心開發郵件列表上的各種爭論一窺究竟)。從這個bug中,我們學到了一個教訓:漏洞利用開發者必須考慮周全,注意NTOSKRNL和HAL在單核/多核/PAE上存在的差異。
非常奇怪的是,漏洞利用開發者會在攻擊載荷中使用 KdVersionBlock
技巧和位元組序列搜尋技術來查詢函式。在Windows 7載荷中,開發者會從KPCR IDT開始向前搜尋記憶體,然後解析PE頭,最終找到NT程式及其匯出表,這是一種更可靠的處理方式。
我們也可以通過其他方法來找到這個HAL函式(比如通過HAL匯出方式),也可以通過其他方法來清理被破壞的KPCR結構,這些工作留待讀者來完成。
有間接證據表明,漏洞開發人員在2001年末開始開發FuzzBunch的主要框架。開發人員似乎只在多核處理器上編寫並測試攻擊載荷?也許這可能是一個線索,表明攻擊者開發XP版漏洞利用程式的時間段。Windows XP於2001年10月25日釋出,雖然在同一年IBM發明了第一款雙核處理器(POWER4),但英特爾和AMD直到2004年和2005年才開始提供類似產品。
這也是ETERNAL系列漏洞利用演進過程的一個例子。方程式組織可能會重複使用相同的漏洞利用和攻擊載荷原語,但會使用不同的方法來開發漏洞,這樣如果一種方法無法成功,也可以通過漏洞利用多樣化特點來最終完成攻擊任務。研究這些漏洞利用樣本後,我們可以從中學到許多深奧的Windows核心內部原理。