这篇文章将分析 Windows 操作系统 win32k
内核模块窗口管理器子系统中的 CVE-2015-2546 漏洞,与上一篇分析的 CVE-2017-0263 漏洞类似地,这个漏洞也是弹出菜单 tagPOPUPMENU
对象的释放后重用(UAF)漏洞。分析的环境是 Windows 7 x86 SP1 基础环境的虚拟机。
文章链接:xiaodaozhi.com/exploit/122…
0x0 前言
这篇文章分析了发生在窗口管理器(User)子系统的菜单管理组件中的 CVE-2015-2546 UAF(释放后重用)漏洞。在内核函数 xxxMNMouseMove
调用 xxxSendMessage
向目标菜单窗口对象发送 MN_SELECTITEM
消息期间,执行流存在发生用户回调的可能性;在发送消息的函数调用返回后,函数 xxxMNMouseMove
没有重新获取目标菜单窗口对象所关联的弹出菜单 tagPOPUPMENU
对象的地址,而直接使用在发送 MN_SELECTITEM
消息之前就存储在寄存器 ebx
中的弹出菜单对象地址,将该对象地址作为参数传递给 xxxMNHideNextHierarchy
函数调用,并在该函数中对目标弹出菜单对象进行访问。
如果用户进程先前通过利用技巧构造了特殊关联和属性的菜单窗口对象,并设置特定的挂钩处理程序,那么在调用 xxxSendMessage
向目标菜单窗口对象发送 MN_SELECTITEM
消息期间,执行流返回到用户上下文,用户进程中的利用代码将有足够的能力触发销毁目标菜单窗口对象,从而在内核中直接释放菜单窗口对象关联的弹出菜单对象;当执行流返回到内核上下文时,寄存器 ebx
中存储的地址指向的内存已被释放,而函数在将该地址作为参数传递给函数 xxxMNHideNextHierarchy
之前缺少必要的验证,这将导致 UAF 漏洞的发生。
在触发销毁目标菜单窗口对象之后,用户进程中的利用代码通过巧妙的内存布局,使系统重新分配相同大小的内存区域以占用先前释放的弹出菜单对象的内存块,伪造新的弹出菜单对象并构造相关成员域,在用户进程地址空间中伪造新的子菜单窗口对象和关联的消息处理函数,并将窗口对象的地址存储在重新分配的弹出菜单对象成员域 spwndNextPopup
中。在内核中函数 xxxMNHideNextHierarchy
将向目标弹出菜单对象的成员域 spwndNextPopup
指向的子菜单窗口对象发送 MN_SELECTITEM
消息,这将使执行流直接在内核上下文中直接进入定义在用户进程地址空间中的伪造消息处理函数,执行函数中的内核利用代码,实现内核利用和提权的目的。
0x1 原理
CVE-2015-2546 漏洞发生在内核函数 win32k!xxxMNMouseMove
中。在该函数执行期间,在调用函数 xxxSendMessage
向目标菜单窗口对象发送 0x1F0
(MN_SETTIMERTOOPENHIERARCHY
) 消息之后,如果函数返回值为 0
,系统将在未对寄存器 ebx
中存储的目标弹出菜单 tagPOPUPMENU
对象的内存地址进行有效性校验的情况下就调用函数 xxxMNHideNextHierarchy
并将该地址传入函数的 popupMenu
参数。
.text:00139530pushedi ; lParam
.text:00139531pushedi ; wParam
.text:00139532push1F0h; message
.text:00139537pushesi ; pwnd
.text:00139538call_xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
.text:0013953Dtesteax, eax
.text:0013953Fjnz short loc_139583
.text:00139541pushebx ; popupMenu
.text:00139542call_xxxMNHideNextHierarchy@4 ; xxxMNHideNextHierarchy(x)
.text:00139547jmp short loc_139583
存在漏洞的目标代码片段
与补丁进行对比,发现补丁在调用函数 xxxSendMessage
发送 MN_SETTIMERTOOPENHIERARCHY
消息和调用函数 xxxMNHideNextHierarchy
的语句之间增加对目标窗口对象扩展区域指向关联弹出菜单对象的指针和寄存器 ebx
中存储数值的对比判断,如果不相等则将跳过函数 xxxMNHideNextHierarchy
的调用。
.text:BF93EC2Epushedi ; lParam
.text:BF93EC2Fpushedi ; wParam
.text:BF93EC30push1F0h; message
.text:BF93EC35pushesi ; pwnd
.text:BF93EC36call_xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
.text:BF93EC3Btesteax, eax
.text:BF93EC3Djnz short loc_BF93EC8C
.text:BF93EC3Fmov eax, [ebp+pwnd]
.text:BF93EC42cmp [eax+0B0h], ebx
.text:BF93EC48jnz short loc_BF93EC8C
.text:BF93EC4Apushebx
.text:BF93EC4Bcall_xxxMNHideNextHierarchy@4 ; xxxMNHideNextHierarchy(x)
.text:BF93EC50jmp short loc_BF93EC8C
补丁修复的目标代码片段
在 Windows 内核中,菜单对象在屏幕中的显示通过窗口 tagWND
对象的特殊类型 #32768
(MENUCLASS
) 菜单窗口对象来实现,菜单窗口对象末尾的扩展区域中存储指向关联的弹出菜单 tagPOPUPMENU
对象的指针。
当函数 xxxSendMessage
发送 MN_SETTIMERTOOPENHIERARCHY
消息时,系统最终在函数 xxxMenuWindowProc
中接收并调用函数 MNSetTimerToOpenHierarchy
以处理消息并向调用者返回该函数的返回值。
当执行流返回到函数 xxxMNMouseMove
中时,系统判断返回值,如果返回值为 0
则调用函数 xxxMNHideNextHierarchy
以关闭目标弹出菜单 tagPOPUPMENU
对象的弹出子菜单。
由于在调用函数 xxxMNHideNextHierarchy
之前,函数 xxxMNMouseMove
中还存在调用 xxxSendMessage
函数以发送 MN_SETTIMERTOOPENHIERARCHY
消息的语句,这将有可能导致执行流反向调用到用户进程中。因此,在此期间攻击者可以在用户进程中触发逻辑使目标弹出菜单 tagPOPUPMENU
对象的内存被释放或重新分配,这将导致目标参数 popupMenu
指向内存区域中存在不可控的数据。如果攻击代码对在原位置重新分配的内存块中的数据进行刻意构造,那么在函数 xxxMNHideNextHierarchy
中向子菜单窗口对象发送消息时,将使内核上下文的执行流可能直接进入位于用户进程地址空间的利用代码函数中。
0x2 追踪
在 win32k
内核模块中,存在来自其他函数的两处对函数 xxxMNMouseMove
的调用:
- xxxHandleMenuMessages(x,x,x)+2E9
- xxxMenuWindowProc(x,x,x,x)+D1C
其中一处是在函数 xxxHandleMenuMessages
处理 WM_MOUSEMOVE
或 WM_NCMOUSEMOVE
消息时,另一处是在函数 xxxMenuWindowProc
处理 MN_MOUSEMOVE
消息时。
通过 WinDBG 对函数 xxxMNMouseMove
下断点并在虚拟机桌面区域弹出右键菜单,观测在自然条件下系统会通过哪些路径调用该函数,发现得到的调用栈都基本如下:
# ChildEBP RetAddr
00 98af4a90 94779066 win32k!xxxMNMouseMove
01 98af4aec 94778c1f win32k!xxxHandleMenuMessages+0x2ed
02 98af4b38 9477f8f1 win32k!xxxMNLoop+0x2c6
03 98af4ba0 9477f9dc win32k!xxxTrackPopupMenuEx+0x5cd
04 98af4c14 83e501ea win32k!NtUserTrackPopupMenuEx+0xc3
05 98af4c14 76e170b4 nt!KiFastCallEntry+0x12a
函数 xxxMNMouseMove 的自然条件调用栈
xxxMNMouseMove
函数 xxxMNMouseMove
用来处理鼠标移动到指定坐标点的消息。在函数 xxxMNMouseMove
开始的位置,函数判断通过参数传入的弹出菜单 tagPOPUPMENU
对象是否为当前的根弹出菜单对象,并判断传入的鼠标坐标与先前存储在当前菜单状态 tagMENUSTATE
结构体的坐标相比是否确实改变,如果不满足条件则直接返回。接下来函数通过调用 xxxMNFindWindowFromPoint
函数并将目标弹出菜单对象指针和新的坐标作为参数传入,以查找该坐标点坐落的在屏幕中显示的菜单窗口对象。当返回值是真实的菜单窗口对象地址时,函数将该窗口对象作为目标窗口对象,将鼠标坐标位于的菜单项序号作为参数 wParam
向目标窗口对象发送 0x1E5
(MN_SELECTITEM
) 消息以执行选择菜单项的操作,并接收函数的返回值作为反馈标志变量。
在调用 xxxSendMessage
以发送 MN_SETTIMERTOOPENHIERARCHY
消息的语句之前,函数 xxxMNMouseMove
判断前面返回的反馈标志变量的数值,以确保被指针指向的菜单项关联另一个弹出式菜单(MF_POPUP
)作为子菜单,并且不处于禁用状态(MFS_GRAYED
)。
.text:00139517xor edi, edi
.text:00139519pushedi ; lParam
.text:0013951Apush[ebp+cmdItem] ; wParam
.text:0013951Dpush1E5h; message
.text:00139522pushesi ; pwnd
.text:00139523call_xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
.text:00139528testal, 10h ; MF_POPUP
.text:0013952Ajzshort loc_139583
.text:0013952Ctestal, 3 ; MFS_GRAYED
.text:0013952Ejnz short loc_139583
函数 xxxMNMouseMove 判断选择菜单项反馈的标志变量数值
接下来函数通过调用函数 xxxSendMessage
向目标菜单窗口对象发送 MN_SETTIMERTOOPENHIERARCHY
消息来设置打开弹出子菜单的定时器。如果函数返回值为 0
表示弹出子菜单的操作执行失败,那么函数调用 xxxMNHideNextHierarchy
来关闭所属于当前的目标弹出菜单对象的子弹出菜单。
popupNext = popupMenu->spwndNextPopup;if ( popupNext ){[...]popupNext = popupMenu->spwndNextPopup;if ( popupNext != popupMenu->spwndActivePopup )xxxSendMessage(popupNext, 0x1E4, 0, 0); // MN_CLOSEHIERARCHYxxxSendMessage(popupMenu->spwndNextPopup, 0x1E5, 0xFFFFFFFF, 0); // MN_SELECTITEM[...]}
函数 xxxMNHideNextHierarchy 的代码片段
函数 xxxMNHideNextHierarchy
判断目标弹出菜单对象的成员域 spwndNextPopup
指向的菜单窗口对象是否和成员域 spwndActivePopup
指向的相同。成员域 spwndNextPopup
指向与当前弹出菜单对象直接关联的子菜单的菜单窗口对象;而成员域 spwndActivePopup
用来存储当前正活跃菜单(即当前鼠标或键盘焦点所在的菜单)的菜单窗口对象。如果不相同,那么函数向成员域 spwndNextPopup
指向的子菜单窗口对象发送 MN_CLOSEHIERARCHY
消息,最终在消息处理函数