对 UAF 漏洞 CVE-2016-0167 的分析和利用

这篇文章将对 Windows 释放后重用(UAF)内核漏洞 CVE-2016-0167 进行一次简单的分析并构造其利用验证代码。该漏洞在 2016 年据报道称被用于攻击支付卡等目标的数据,并和之前分析的 CVE-2016-0165 在同一个补丁程序中被微软修复。针对该漏洞的分析和测试是在 Windows 7 x86 SP1 基础环境的虚拟机中进行的。

文章链接:xiaodaozhi.com/exploit/135…

该漏洞是弹出菜单 tagPOPUPMENU 对象的释放后重用漏洞,虽然是两年前的“老漏洞”,但由于触发条件特殊,需要同步和异步的消息请求相互配合才能最终实现满足漏洞利用条件的目标弹出菜单对象,所以当前对于学习和研究 win32k 内核漏洞利用来说,该漏洞还是有一定的研究价值。

0x0 前言

这篇文章分析了发生在窗口管理器(User)子系统的菜单管理组件中的 CVE-2016-0167 释放后重用(UAF)漏洞。在内核函数 xxxMNDestroyHandler 调用 xxxSendMessage 向目标弹出菜单对象关联的通知窗口对象发送 WM_UNINITMENUPOPUP 消息期间,执行流存在发生用户回调的可能性;在发送消息的函数调用返回后,函数 xxxMNDestroyHandler 没有重新验证目标弹出菜单对象内存的有效性而继续对其进行访问。

如果用户进程在特殊时机触发菜单取消的操作使作为利用目标的弹出菜单对象的成员标志位 fDelayedFree 被取消置位,并在特定时机调用函数销毁该弹出菜单对象关联的菜单窗口对象,执行流在内核中执行函数 xxxMNDestroyHandler 时发送 WM_UNINITMENUPOPUP 消息期间回调到用户进程中,用户进程对同一菜单窗口对象再次执行销毁操作,在内核中使执行流针对相同的目标弹出菜单对象重复进入函数 xxxMNDestroyHandler 中,并在第二次调用期间销毁目标弹出菜单对象;当执行流回到第一次调用的函数中时,目标弹出菜单对象已被销毁,但函数将在缺少必要的验证的情况下直接对目标弹出菜单对象的成员域进行访问甚至执行重复释放的操作,这将导致 UAF 漏洞的发生。

在触发销毁目标菜单窗口对象之后,用户进程中的利用代码通过巧妙的内存布局,使系统重新分配相同大小的内存区域以占用先前释放的弹出菜单对象的内存块,伪造新的弹出菜单对象并构造相关成员域。借助代码逻辑,实现对特定窗口对象的成员标志位 bServerSideWindowProc 的修改,使系统能够在内核中直接执行位于用户进程地址空间中的自定义窗口消息处理函数,得以通过内核上下文执行用户进程构造的利用代码,实现内核提权的目的。

0x1 原理

漏洞发生在内核模块 win32k.sys 的函数 xxxMNDestroyHandler 中,该函数用于在销毁指定的菜单窗口对象期间执行销毁其关联的弹出菜单 tagPOPUPMENU 对象的任务,目标弹出菜单对象的指针通过参数 popupMenu 传入。

弹出菜单 tagPOPUPMENU 结构体定义如下:

kd> dt win32k!tagPOPUPMENU +0x000 flags: Int4B +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 

弹出菜单 tagMENUSTATE 结构体的定义

在函数中存在向目标弹出菜单对象的成员域 spwndNotify 指向的通知窗口对象发送 WM_UNINITMENUPOPUP 消息的调用语句:

if ( *(_DWORD *)popupMenu & 0x200000 )// fSendUninit
{spwndNotify = popupMenu->spwndNotify;if ( spwndNotify ){ptl = gptiCurrent->ptl;gptiCurrent->ptl = (_TL *)&ptl;pwndTarg = spwndNotify;++spwndNotify->head.cLockObj;pmenu = popupMenu->spmenu;if ( pmenu )hmenu = p->head.h;xxxSendMessage(popupMenu->spwndNotify,0x125,// WM_UNINITMENUPOPUP(WPARAM)hmenu,(LPARAM)(((*(_DWORD *)popupMenu >> 2) & 1) << 13) << 16);ThreadUnlock1();}
} 

函数 xxxMNDestroyHandler 存在发送消息的调用

其中,作为判断依据的成员标志位 fSendUninit 早在目标弹出菜单对象初始化期间默认被置位;而通知窗口对象成员域 spwndNotify 也会在初始化期间被赋值为作为菜单拥有者的窗口对象的地址。这将导致函数 xxxMNDestroyHandler 的执行流存在回调到用户进程上下文的可能性。

接下来函数通过对目标弹出菜单对象成员标志位 fDelayedFree 进行判断,以决定是否立即为目标弹出菜单对象调用 MNFreePopup 执行具体的释放操作。函数 MNFreePopup 调用 HMAssignmentUnlock 等函数解除 spwndPopupMenu 等各个对象成员域的赋值锁。在执行相应的预处理之后,函数调用 ExFreePoolWithTag 释放传入的弹出菜单 tagPOPUPMENU 对象缓冲区。

由于在前面函数 xxxMNDestroyHandler 发送 WM_UNINITMENUPOPUP 消息期间执行流可能回调到用户进程中,因此,攻击者可以在用户进程中触发逻辑使目标弹出菜单 tagPOPUPMENU 对象的内存被释放或重新分配,这将导致目标参数 popupMenu 指向内存区域中存在不可控的数据。如果攻击代码对在原位置重新分配的内存块中的数据进行刻意构造,那么在对某个保存特殊对象地址的对象成员域进行解锁时,将使内核上下文的执行流可能直接进入位于用户进程地址空间的利用代码函数中。

0x2 追踪

函数 xxxMNDestroyHandler 是用于在销毁指定的菜单窗口对象期间执行销毁其关联的弹出菜单 tagPOPUPMENU 对象任务的函数,仅在菜单窗口对象指定的消息处理函数 xxxMenuWindowProc 处理 WM_FINALDESTROY 消息时调用。


xxxMNDestroyHandler

该函数接收通过参数 tagPOPUPMENU *popupMenu 传入的弹出菜单对象作为目标对象。在函数开始位置,判断目标弹出菜单成员域 spwndNextPopup 是否指向真实的子菜单窗口对象,如是则表明当前菜单存在已弹出的子菜单。因此函数向成员域 spwndPopupMenu 指向的当前菜单窗口对象(如果为空则向子菜单窗口对象)发送 MN_CLOSEHIERARCHY 以关闭当前菜单的子菜单。该消息最终在 xxxMenuWindowProc 函数中接收并对目标窗口对象关联的弹出菜单对象调用 xxxMNCloseHierarchy 以处理关闭子菜单的任务。

if ( popupMenu->spwndNextPopup )
{pwnd = popupMenu->spwndPopupMenu;if ( !pwnd )pwnd = popupMenu->spwndNextPopup;ptl = gptiCurrent->ptl;gptiCurrent->ptl = (_TL *)&ptl;++pwnd->head.cLockObj;xxxSendMessage(pwnd, 0x1E4, 0, 0); // xxxMNCloseHierarchyThreadUnlock1();
} 

函数 xxxMNDestroyHandler 的代码片段

接着函数判断目标弹出菜单对象的成员标志位 fSendUninit 是否处于置位状态。该标志位决定在弹出菜单对象销毁之后系统是否应向接收通知的窗口对象发送 WM_UNINITMENUPOPUP 消息。在根弹出菜单对象或子弹出菜单对象初始化期间,系统通常在函数 xxxTrackPopupMenuExxxxMNOpenHierarchy 中置位该标志位。

如果成员标志位 fSendUninit 处于置位状态,那么函数向成员域 spwndNotify 指向的用于接收通知的窗口对象发送 WM_UNINITMENUPOPUP(0x125) 消息,以使拥有者窗口能在第一时间清理与将被销毁的弹出菜单相关的数据。

if ( *(_DWORD *)popupMenu & 0x200000 )// fSendUninit
{spwndNotify = popupMenu->spwndNotify;if ( spwndNotify ){ptl = gptiCurrent->ptl;gptiCurrent->ptl = (_TL *)&ptl;pwndTarg = spwndNotify;++spwndNotify->head.cLockObj;pmenu = popupMenu->spmenu;if ( pmenu )hmenu = (tagMENU *)p->head.h;xxxSendMessage(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值