0x00 前言及原因分析
CVE-2015-2546是发生在win32k!xxxMNMouseMove
函数的一个释放后重引用漏洞(UAF),在win32k!xxxMNMouseMove
中调用xxxSendMessage发送MN_SELECTITEM(0x1E5)
和MN_SETTIMERTOOPENHIERARCHY(0x1F0)
消息的时候,执行流可能会回调进入用户侧,当执行流从用户侧返回之后,win32k!xxxMNMouseMove
函数并没有对tagPopupMenu
对象进行校验,就将其传入win32k!xxxMNHideNextHierarchy
对其进行了访问,从而引用UAF。
|
|
如果在执行流回到用户侧时,销毁目标菜单对象,然后在经过巧妙的内存布局,使系统重新分配内存重新占用销毁的目标菜单对象所占据的内存,然后通过巧妙的伪装,在Win32k!xxxMNHideNextHierarchy
调用发送MN_SELECTITEM(0x1E5)
消息,从而像CVE-2014-4113一样,执行预先设置好的ShellCode提权代码。
0x01 Poc构建
和CVE-2014-4113一样,触发CVE-2015-2546的win32k!xxxMNMouseMove
函数也是在win32k!xxxHandleMenuMessages
被调用。在代码88行调用xxxMNMouseMove(v3, pMenuState, v7);
触发CVE-2015-2546,而在56行,调用xxxSendMessage(v13, 0x1ED, tagPopupMenu, 0);
触发CVE-2014-4113。通过观察代码逻辑,在18行,如果v23不等于0的话,则不会调用xxxMNMouseMove(v3, pMenuState, v7);
,所以网上追溯,当message为0x200(WM_MOUSEMOVE)
的时候,就会执行xxxMNMouseMove(v3, pMenuState, v7);
,所以只要主窗口发送WM_MOUSEMOVE
消息的时候就会调用xxxMNMouseMove
。
win32k!xxxMNMouseMove
作用是处理鼠标移动的消息,首先判断传入的PopupMenu对象是否是根弹出菜单对象,然后检查坐标是否发生移动,继而通过xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, *&ptScreen_);
获取菜单窗口对象。如果是有效的菜单窗口对象,会判断该窗口对象是否被销毁,然后将获取到的窗口对象作为参数,传入xxxSendMessage
发送MN_SELECTITEM(0x1E5)
消息以选择菜单项,当返回的uFlag为MF_POPUP
以及不为MFS_GRAYED
,并且调用xxxSendMessage
发送MN_SETTIMERTOOPENHIERARCHY
消息不为0的时候,则调用xxxMNHideNextHierarchy
函数以关闭弹出窗口的子菜单。
xxxMNHideNextHierarchy
函数逻辑就很简单了,只需要判断传入的ppopupmenu的弹出子菜单是否是活跃的弹出菜单之后,调用xxxSendMessage
发送MN_CLOSEHIERARCHY
消息关闭弹出菜单。然后接着发送MN_SELECTITEM
消息继续选择菜单。
根据上述对于win32k!xxxHandleMenuMessages
描述,CVE-2015-2546和CVE-2014-4113漏洞都产生于xxxHandleMenuMessages
。其不同点在于,CVE-2014-4113的message为WM_LBUTTONDOWN(0x201),而CVE-2015-2546的message为WM_MOUSEMOVE(0x200)。我们都可以复用CVE-2014-4113的poc代码如下。需要改动的地方就是主窗口的WndProc函数。需要其发送WM_MOUSEMOVE(0x200)消息,以使执行流进入xxxMNMouseMove
|
|
在xxxMNMouseMove
中,要触发漏洞,需要有4个条件,根据调试,第一个条件自动满足。
- 1.tagPOPUPMENU 对象是否为当前的根弹出菜单对象
- 2.通过
xxxMNFindWindowFromPoint
获取到的窗口对象的有效性,即不能为-1,也不能为-5,且不能被销毁。 - 3.利用
xxxSendMessage
发送MN_SELECTITEM
消息之后,返回的标志是MF_POPUP
(弹出菜单),以及不为MFS_GRAYED
(禁用状态) - 4.利用
xxxSendMessage
发送MN_SETTIMERTOOPENHIERARCHY
消息返回值为0
|
|
重点分析一下xxxMNMouseMove
函数,CVE-2015-2546是一个UAF漏洞,漏洞本质如上所述,在执行xxxSendMessage
执行流返回用户侧代码,销毁窗口对象pwnd,然后xxxMNHideNextHierarchy没有经过检查便使用了已经销毁的窗口对象pwnd。但是在此之前,调用xxxMNFindWindowFromPoint
是要确保得到的对象有效性。所以,只能在发送MN_SETTIMERTOOPENHIERARCHY
的时机去销毁。
所以,现在劫持的回调函数中,需要处理三种消息。编写的消息处理函数如下。
当返回一个普通的窗口对象的时候,发现IsWindowBeingDestroyed
过不去,这个问题,前辈已经解决,当创建一个”#32768”窗口对象即可。
在调试器中,获取弹出菜单对象Popupmenu的地址为0xfe78d9a8,然后当执行完xxxSendMessage(cmdHitArea_1, MN_SETTIMERTOOPENHIERARCHY, 0, 0)
销毁了窗口对象之后,很显然,该对象被释放,然后PopupMenu作为参数传入xxxMNHideNextHierarchy,会对popupmenu的spwndNextPopup成员进行判断,为空则直接退出,所以,只需要伪造PopupMenu对象,就可以实现对这块内存的控制。
0x02 漏洞利用及验证
总结一下CVE-2015-2546的漏洞利用,通过伪造PopupMenu对象,去占用销毁窗口对象留下的内存空洞,从而实现漏洞利用。
现在,整个逻辑就很清楚了,首先通过加速键表,占用一大段内存空间,每次申请5个ACCEL的大小,一共申请50次,然后释放部分加速键表,造成内存空洞,然后创建的窗口对象便会占用其中一个内存空洞中。
将占用的内存地址打印出来,并在windbg中输出tagWnd2的PopupMenu对象地址为fe440168
,很显然,新创建的窗口对象占用了之前释放的内存空洞。
泄露加速表地址可以借助SHAREDINFO
结构体的PUSER_HANDLE_ENTRY成员,这是ENTRY可以理解为一张句柄表。而每一个USER_HANDLE_ENTRY第一个成员pKernel则指向内存地址。
|
|
很显然,原先的tagWnd对象已经被销毁,由新申请的加速键内存占用。
win32k!xxxMNHideNextHierarchy
中调用win32k!xxxSendMessage
此时传入的对象是伪造的加速键表。剩下的就和CVE-2014-4113差不多的利用方式,区别就是4113的对象是0xFFFFFFFB。
0x03 参考文献
[1] 对 UAF 漏洞 CVE-2015-2546 的分析和利用 : 小刀师傅对于win32k机制的剖析
[2] https://www.anquanke.com/post/id/84911 : k0shl师傅调试UAF的细节
[3] https://xz.aliyun.com/t/6115 : thunderjie师傅提供的信息泄露的方法,即查看加速键表地址的方法。
[4] https://github.com/ThunderJie/CVE/blob/master/CVE-2015-2546/2015-2546.c : ThunderJie 提供的 exp