/*
 * CVE-2022-21877 -- spaceport.sys Storage Spaces Information Disclosure PoC
 *
 * Description:
 *   Demonstrates the out-of-bounds read vulnerability in spaceport.sys's
 *   SpIoctlCreateTier function. The IOCTL 0xE7D410 handler accepts a
 *   POOLTIER structure with user-controlled offsetGuids and numOfGuids
 *   fields. The driver calls SpIdsCopyHelper which copies GUIDs from
 *   (buffer + offsetGuids) without validating that the offset falls within
 *   the buffer boundary. By providing an out-of-range offset, adjacent
 *   kernel pool memory is copied into the SDB_TIER object and can be
 *   retrieved via SpIoctlGetTierInfo (IOCTL 0xE71408).
 *
 *   This PoC is a BLUE TEAM DETECTION TRIGGER only. It:
 *   1. Checks if spaceport.sys is present and its version
 *   2. Enumerates storage pools via IOCTL 0xE70004 (SpIoctlGetPools)
 *   3. Gets pool info via IOCTL 0xE70008 (SpIoctlGetPoolInfo)
 *   4. Does NOT send the CreateTier IOCTL (requires admin + valid pool)
 *
 * Build (MSVC x64 Native Tools Command Prompt):
 *   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2022_21877.c /link kernel32.lib version.lib advapi32.lib
 *
 * Usage:
 *   poc_cve_2022_21877.exe         (normal)
 *   poc_cve_2022_21877.exe /v      (verbose)
 *
 * Expected (pre-patch):  Reports VULNERABLE based on spaceport.sys version
 * Expected (post-patch): Reports PATCHED
 *
 * NOTE: Full exploitation requires admin access to a storage pool with
 *       tiers enabled (3+ disks). See github.com/Big5-sec/cve-2022-21877
 *
 * Author: OnlyFm252
 * Date:   2026-07-26
 * CVE:    CVE-2022-21877
 *
 * DISCLAIMER: FOR DEFENSIVE RESEARCH AND BLUE TEAM DETECTION TESTING ONLY.
 */

/* -- poc_common.h configuration -- */
#define POC_CVE     "CVE-2022-21877"
#define POC_BINARY  L"spaceport.sys"

static int g_verbose = 0;
#define POC_VERBOSE g_verbose

#include "poc_common.h"

/* ======================================================================
 *  IOCTL codes for spaceport.sys
 * ====================================================================== */

#define IOCTL_GET_POOLS      0xE70004
#define IOCTL_GET_POOL_INFO  0xE70008
#define IOCTL_CREATE_TIER    0xE7D410
#define IOCTL_DELETE_TIER    0xE7D414
#define IOCTL_GET_TIER_INFO  0xE71408

/* Device path for spaceport — it uses a standard device interface */
#define SPACEPORT_DEVICE L"\\\\.\\spaceport"

/* ======================================================================
 *  Structures
 * ====================================================================== */

#pragma pack(push, 1)

typedef struct _POOLS_LIST {
    ULONG nbPools;
    /* GUID listGuids[nbPools] follows */
} POOLS_LIST;

typedef struct _POOL_INFO {
    int size;
    GUID poolGUID;
    int field_14;
    WCHAR friendlyName[256];
    WCHAR description[1024];
    /* more fields follow */
} POOL_INFO;

/* POOLTIER structure — the vulnerable input for CreateTier */
typedef struct _POOL_TIER {
    int length_bis;                  /* +0x00 */
    int length;                      /* +0x04 */
    GUID PoolGUID;                   /* +0x08 */
    GUID TierGUID;                   /* +0x18 */
    GUID spaceGUID;                  /* +0x28 */
    WCHAR friendlyName[256];         /* +0x38 */
    WCHAR description[1024];         /* +0x238 */
    int usage;                       /* +0xA38 */
    int field_A3C;                   /* +0xA3C */
    UINT64 field_A40;                /* +0xA40 */
    BYTE gapA48[16];                 /* +0xA48 */
    int field_A58;                   /* +0xA58 */
    int field_A5C;                   /* +0xA5C */
    UINT64 field_A60;                /* +0xA60 */
    int mediatype;                   /* +0xA68 */
    int field_A6C;                   /* +0xA6C */
    int faultDomainAwareness;        /* +0xA70 */
    int AllocationUnitSize;          /* +0xA74 */
    int numOfGuids;                  /* +0xA78: attacker-controlled count */
    int offsetGuids;                 /* +0xA7C: attacker-controlled offset (BUG) */
    int field_A84;                   /* +0xA84 */
    int physicalDiskRedundancy;      /* +0xA88 */
    int NumberOfDataCopies;          /* +0xA8C */
    int field_A90;                   /* +0xA90 */
    int NumberOfColumns;             /* +0xA94 */
    int Interleave;                  /* +0xA98 */
    int field_A9C;                   /* +0xA9C */
    int field_AA0;                   /* +0xAA0 */
    int field_AA4;                   /* +0xAA4 */
    UINT64 field_AA8;                /* +0xAA8 */
} POOL_TIER;

#pragma pack(pop)

/* ======================================================================
 *  Version comparison for spaceport.sys
 * ====================================================================== */

static int check_spaceport_version(void)
{
    DWORD verHi, verLo;
    DWORD major, minor, build, rev;

    if (!poc_get_binary_version(POC_BINARY, &verHi, &verLo)) {
        POC_WARN(L"Cannot read spaceport.sys version");
        return -1;
    }

    poc_print_binary_version(POC_BINARY, verHi, verLo);

    major = (verHi >> 16) & 0xFFFF;
    minor = verHi & 0xFFFF;
    build = (verLo >> 16) & 0xFFFF;
    rev   = verLo & 0xFFFF;

    POC_DETAIL(L"Version: %u.%u.%u.%u", major, minor, build, rev);

    (void)major;
    (void)minor;

    /* Win10 19041/19044/19045: vulnerable before Jan 2022 CU */
    if (build == 19041 && rev < 1466) {
        POC_WARN(L"Build %u.%u is PRE-PATCH (vulnerable)", build, rev);
        return 1;
    }
    if (build == 19044 && rev < 1466) {
        POC_WARN(L"Build %u.%u is PRE-PATCH (vulnerable)", build, rev);
        return 1;
    }
    /* Win11 22000: vulnerable before Jan 2022 CU */
    if (build == 22000 && rev < 434) {
        POC_WARN(L"Build %u.%u is PRE-PATCH (vulnerable)", build, rev);
        return 1;
    }
    /* Win Server 2019 (17763): vulnerable before Jan 2022 */
    if (build == 17763 && rev < 2452) {
        POC_WARN(L"Build %u.%u is PRE-PATCH (vulnerable)", build, rev);
        return 1;
    }

    POC_OK(L"Build %u.%u appears PATCHED", build, rev);
    return 0;
}

/* ======================================================================
 *  Try to open spaceport device
 * ====================================================================== */

static HANDLE try_open_spaceport(void)
{
    HANDLE hDevice;

    hDevice = CreateFileW(
        SPACEPORT_DEVICE,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL
    );

    return hDevice;
}

/* ======================================================================
 *  Enumerate storage pools (no access check required)
 * ====================================================================== */

static int enumerate_pools(HANDLE hDevice)
{
    BYTE outBuf[4096];
    DWORD inVal = 0;
    DWORD bytesReturned = 0;
    BOOL ok;
    POOLS_LIST *poolsList;
    GUID *guids;
    ULONG i;

    memset(outBuf, 0, sizeof(outBuf));

    ok = DeviceIoControl(hDevice, IOCTL_GET_POOLS,
                         &inVal, sizeof(inVal),
                         outBuf, sizeof(outBuf),
                         &bytesReturned, NULL);

    if (!ok) {
        POC_DETAIL(L"SpIoctlGetPools failed (error %u)", GetLastError());
        return -1;
    }

    poolsList = (POOLS_LIST *)outBuf;
    POC_OK(L"SpIoctlGetPools succeeded: %u pools found", poolsList->nbPools);

    if (poolsList->nbPools == 0) {
        POC_INFO(L"No storage pools — vulnerability requires at least one pool");
        return 0;
    }

    guids = (GUID *)(outBuf + sizeof(ULONG));
    for (i = 0; i < poolsList->nbPools && i < 4; i++) {
        POC_DETAIL(L"  Pool %u: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
                   i,
                   guids[i].Data1, guids[i].Data2, guids[i].Data3,
                   guids[i].Data4[0], guids[i].Data4[1],
                   guids[i].Data4[2], guids[i].Data4[3],
                   guids[i].Data4[4], guids[i].Data4[5],
                   guids[i].Data4[6], guids[i].Data4[7]);
    }

    return (int)poolsList->nbPools;
}

/* ======================================================================
 *  Explain the vulnerability and exploitation chain
 * ====================================================================== */

static void explain_vulnerability(void)
{
    POC_INFO(L"=== Vulnerability Details (CVE-2022-21877) ===");
    POC_INFO(L"1. SpIoctlCreateTier (IOCTL 0xE7D410) accepts POOLTIER struct");
    POC_INFO(L"2. POOLTIER contains offsetGuids (+0xA7C) and numOfGuids (+0xA78)");
    POC_INFO(L"3. SpIdsCopyHelper copies GUIDs from buffer+offsetGuids");
    POC_INFO(L"4. BUG: No check that offsetGuids + numOfGuids*16 <= buffer_length");
    POC_INFO(L"5. Attacker sets offsetGuids beyond buffer → OOB read from kernel pool");
    POC_INFO(L"6. Leaked data stored in SDB_TIER object");
    POC_INFO(L"7. SpIoctlGetTierInfo (IOCTL 0xE71408) returns tier data with leak");
    POC_INFO(L"");
    POC_INFO(L"=== Patch ===");
    POC_INFO(L"Adds RtlULongAdd(offsetGuids, totalGuidSize, &endOffset)");
    POC_INFO(L"Then checks: endOffset <= buffer_length");
    POC_INFO(L"Three functions patched: SpIoctlCreateTier, SetTierInfo, SetSpaceInfoTransaction");
}

/* ======================================================================
 *  Main
 * ====================================================================== */

int wmain(int argc, wchar_t *argv[])
{
    int result = -1;
    int verResult;
    HANDLE hDevice;

    g_verbose = poc_parse_verbose(argc, argv);

    /* Unbuffer stdout */
    setvbuf(stdout, NULL, _IONBF, 0);

    poc_banner(L"spaceport.sys SpIoctlCreateTier Out-of-Bounds Read");

    /* Pre-flight */
    {
        static const poc_cfr_info cfr[] = { { 0, NULL } };
        poc_preflight(POC_BINARY, NULL, cfr, 0);
    }

    /* -- Step 1: Check spaceport.sys version -- */
    POC_STEP("Check spaceport.sys version for patch status");

    verResult = check_spaceport_version();
    if (verResult > 0) {
        result = 1;
    } else if (verResult == 0) {
        result = 0;
    }

    /* -- Step 2: Open spaceport device -- */
    POC_STEP("Open handle to spaceport device");

    hDevice = try_open_spaceport();
    if (hDevice == INVALID_HANDLE_VALUE) {
        POC_WARN(L"Cannot open spaceport device (error %u)", GetLastError());
        POC_INFO(L"Storage Spaces Controller may not be running");
        POC_INFO(L"Requires Storage Spaces subsystem with 3+ disks");
    } else {
        POC_OK(L"Spaceport device opened: 0x%p", hDevice);

        /* -- Step 3: Enumerate pools -- */
        POC_STEP("Enumerate storage pools (no access check)");

        int numPools = enumerate_pools(hDevice);
        if (numPools > 0) {
            POC_INFO(L"Pools available — CreateTier requires SpAccessCheckPool (admin)");
            POC_INFO(L"Pool security descriptors can be modified to grant user access");
        }

        CloseHandle(hDevice);
    }

    /* -- Step 4: Explain vulnerability -- */
    POC_STEP("Display vulnerability details");
    explain_vulnerability();

    /* -- Step 5: Show POOLTIER structure layout -- */
    POC_STEP("Show POOLTIER structure offset analysis");

    POC_INFO(L"POOLTIER structure key offsets:");
    POC_INFO(L"  +0x08: PoolGUID (16 bytes)");
    POC_INFO(L"  +0x18: TierGUID (16 bytes)");
    POC_INFO(L"  +0xA78: numOfGuids (4 bytes) — count of GUIDs to copy");
    POC_INFO(L"  +0xA7C: offsetGuids (4 bytes) — offset within buffer (BUG)");
    POC_INFO(L"  Total base size: ~0xAB0 bytes");
    POC_INFO(L"");
    POC_INFO(L"Attack: set offsetGuids > 0xAB0 to read kernel pool memory");
    POC_INFO(L"Retrieve leaked data via IOCTL 0xE71408 (GetTierInfo)");

    /* -- Step 6: Evaluate results -- */
    POC_STEP("Evaluate results");

    if (result == 0) {
        POC_OK(L"System is PATCHED — SpIoctlCreateTier validates offset+size bounds");
    } else if (result > 0) {
        POC_WARN(L"System appears VULNERABLE — spaceport.sys version is pre-patch");
        POC_INFO(L"Exploitation requires admin access to a storage pool");
        POC_INFO(L"See: github.com/Big5-sec/cve-2022-21877");
    } else {
        POC_INFO(L"Could not determine patch status — check spaceport.sys version");
    }

    poc_results(result);
    return result > 0 ? 1 : 0;
}
