# CVE-2026-20817 — Windows Error Reporting Service `WerSvc.dll` Elevation of Privilege

---

## Summary

| **Product**           | Microsoft Windows — `WerSvc.dll` (Windows Error Reporting Service) |
|-----------------------|------------------------------------------------------------------|
| **Vendor**            | Microsoft Corporation |
| **Severity**          | CVSS v3.1 **7.8 (High)** |
| **CVSS Vector**       | `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` ([MSRC](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-20817)) |
| **CVE Title**         | Windows Error Reporting Service Elevation of Privilege Vulnerability (MSRC) |
| **Affected Versions** | Windows 10/11 and Windows Server releases with the vulnerable `CWerService::SvcElevatedLaunch` ALPC handler |
| **Tested Version**    | Windows 11 24H2 — WerSvc.dll 10.0.26100.7309 (pre-patch) vs 10.0.26100.7623 (post-patch) |
| **Impact**            | Elevation of Privilege — any local low-privilege user coerces the SYSTEM service into launching `WerFault.exe` with fully attacker-controlled command-line arguments, using a SYSTEM token stripped of only `SeTcbPrivilege` (retains SeDebugPrivilege, SeImpersonatePrivilege, …) |
| **CVE ID**            | CVE-2026-20817 |
| **CWE**               | CWE-280: Improper Handling of Insufficient Permissions or Privileges |
| **PoC Available**     | Yes — [itm4n's trigger PoC](https://github.com/itm4n/CVEs/tree/master/CVE-2026-20817) and a pure-C blue-team variant (`poc_cve_2026_20817.c`) derived from it |
| **Exploit Available** | No public full privesc chain; itm4n's PoC stops at controlled-arguments `WerFault.exe` as SYSTEM. MSRC exploitability: "Exploitation More Likely". Beware a known **fake** PoC at `github.com/oxfemale/CVE-2026-20817` (called out by itm4n) |
| **Patch Available**   | Yes |
| **Patch Date**        | January 2026 Patch Tuesday — KB5074109 |
| **Discovered**        | Denis Faiustov & Ruslan Sayfiev (GMO Cybersecurity) |

---

## Root Cause

`WerSvc` hosts an ALPC server (`\WindowsErrorReportingServicePort`). One of its
dispatch codes, `0x50000000`, reaches `CWerService::SvcElevatedLaunch`, a
privileged "launch a process on my behalf" endpoint. The handler performs **no
authorization check on the ALPC client** — no impersonation-level check, no
token privilege check, no admin check. Any local user that can open the port
gets the service to:

1. open the client process with
   `PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION`
   (`0x440`, plus `0x80` = `PROCESS_CREATE_PROCESS` when `CopySourceHandles`
   is set — see the verified decompilation),
2. duplicate a **client-supplied file-mapping handle** from the message,
   `MapViewOfFile` it, and `memcpy` **0x208 bytes** into a stack buffer
   (rejecting only if no NUL terminator appears within `0x104` wide chars),
3. build a command line of the form `"<SystemDir>\<fixed-exe>" <client-string>`
   and launch it with `CreateProcessAsUserW` under a token that is either the
   client's *elevated* token (`LUAGetElevatedToken`) or — when the client has
   none, i.e. the interesting case — **the WerSvc process's own SYSTEM token,
   restricted only by removing `SeTcbPrivilege`** via `CreateRestrictedToken`,
4. spoof the new process's **parent PID to the client's PID**
   (`PROC_THREAD_ATTRIBUTE_PARENT_PROCESS`), and
5. duplicate the new process handle back to the client with `SYNCHRONIZE`
   access (`0x100000`).

The executable itself is fixed (`WerFault.exe`, path assembled from
`GetSystemDirectory`-equivalent `UtilGetSystemDirectory2` — the client cannot
swap the binary), but **every byte of the argument tail is attacker
controlled**, and the process runs as SYSTEM (minus SeTcbPrivilege).

> Weaponisation (turning controlled `WerFault.exe` arguments into arbitrary
> code execution as SYSTEM) is, per itm4n, "further work required" — but the
> design-level primitive (unauthenticated ALPC → SYSTEM process creation with
> controlled cmdline + handle inheritance of up to 16 client handles) is the
> vulnerability.

### Why it couldn't be fixed in place

Microsoft did not add a permission check. The patch **deletes the feature**:
a WIL Controlled-Feature-Rollout flag is consulted at the very top of
`SvcElevatedLaunch`, and when enabled the function immediately fails the
request. Verified via headless Ghidra decompilation (2026-07-21) of both
builds:

```c
// WerSvc.dll 10.0.26100.7623 (post-patch) — SvcElevatedLaunch @ 0x18001a980
bVar2 = wil::details::FeatureImpl<__WilFeatureTraits_Feature_2473284922>::__private_IsEnabled(...);
if (bVar2) {
    *(undefined4 *)(param_2 + 0x28) = 1;            // response MessageFlags
    *(undefined4 *)(param_2 + 0x2c) = 0xc0000001;   // LastError = STATUS_UNSUCCESSFUL
    lVar3 = -0x7fffbffb;                            // 0x80004005 E_FAIL
}
else {
    // ... entire original vulnerable body, unchanged ...
}
```

The pre-patch build (10.0.26100.7309, `SvcElevatedLaunch @ 0x18001a7e0`) has
**no such block** — it goes straight to `OpenSenderProcessThread` →
`ElevatedProcessStart`. Everything else in the function is byte-for-byte
equivalent logic; the diff's single code-changed function is exactly this one.

### Message validation (the only gatekeeper)

`CWerService::CheckIfValidPortMessage` (@ `0x1800159d4`, unchanged by the
patch) checks:

- `TotalLength == 0x578` for 64-bit clients; **`0x510` for WoW64 (32-bit)
  clients** — it opens the sender process, calls `IsWow64Process`, and accepts
  the shorter message for 32-bit senders (verified in decompilation);
- `MessageFlags` (`+0x28`) must be one of the known dispatch codes —
  `0x50000000` (elevated launch) is explicitly whitelisted.

So the "validation" authenticates the *shape* of the message, never the
*rights* of the sender.

---

## Complete Call Flow (userspace → vulnerable code)

```
[userspace, any integrity level — no privileges required]
CreateFileMappingW(INVALID_HANDLE_VALUE, PAGE_READWRITE, 0x208 bytes)
MapViewOfFile → write wide-string arguments (must contain NUL within 0x104 WCHARs)
NtAlpcConnectPort("\\WindowsErrorReportingServicePort",
                  MaxMessageLength = 0x578, ALPC_MSGFLG_SYNC_REQUEST)
NtAlpcSendWaitReceivePort(msg 0x578 bytes)
  msg.PortMessage.u1.s1.TotalLength = 0x578
  msg.PortMessage.u1.s1.DataLength  = 0x578 - sizeof(PORT_MESSAGE) = 0x550
  msg.MessageFlags (+0x28) = 0x50000000          // dispatch code
  msg.Unknown      (+0x30) = 1                   // MUST be 1 (ElevatedProcessStart checks param_1==1)
  msg.FileMapping  (+0x38) = client mapping handle
  msg.SourceHandles(+0x40) = up to 16 handles duplicated into the new process
  msg.CopySourceHandles(+0xc0) = 0/1             // adds PROCESS_CREATE_PROCESS to client open

[WerSvc service process, NT AUTHORITY\SYSTEM]
ALPC worker thread
  └─ CWerService::_ProcessRequest
       └─ StaticDispatchPortRequestWorkItem
            └─ CWerService::DispatchPortRequestWorkItem   // giant switch on +0x28
                 │  case 0x50000000:
                 ├─ CWerService::CheckIfValidPortMessage  // size 0x578/0x510 + code whitelist ONLY
                 └─ CWerService::SvcElevatedLaunch        // ← PATCHED FUNCTION
                      ├─ OpenSenderProcessThread(access = 0x440 | (CopySourceHandles ? 0x80 : 0))
                      ├─ copy up to 16 SourceHandles into local array
                      └─ ElevatedProcessStart(Unknown, hClientProc, FileMapping, handles, n, &hNewProc)
                           │  if (Unknown != 1) → E_INVALIDARG
                           ├─ DuplicateHandle(client FileMapping → service)
                           ├─ MapViewOfFile(..., 0x208) + memcpy 0x208 → stack buf
                           │     (NUL required within 0x104 WCHARs, else E_INVALIDARG)
                           ├─ CreateElevatedProcessAsUser(hClientProc, argsBuf, ...)
                           │     ├─ GetCreateProcessToken / GetProcessToken:
                           │     │     ├─ OpenProcessToken(client, 0x4f)
                           │     │     ├─ LUAGetElevatedToken → use client's elevated token if any
                           │     │     └─ else: OpenProcessToken(WerSvc itself, 0xf01ff)
                           │     │            + CreateRestrictedToken(disable ONLY SeTcbPrivilege)
                           │     │            → SYSTEM token minus SeTcbPrivilege  ← THE PRIMITIVE
                           │     ├─ app  = "<SystemDir>\WerFault.exe"        (fixed, safe)
                           │     ├─ cmd  = "\"<SystemDir>\WerFault.exe\" <attacker args>"
                           │     ├─ ImpersonateLoggedOnUser(token)
                           │     ├─ CreateProcessAsUserW(token, app, cmd, ..., EXTENDED_STARTUPINFO_PRESENT,
                           │     │                       ..., attr list: PARENT_PROCESS = client PID,
                           │     │                                          HANDLE_LIST = SourceHandles)
                           │     └─ RevertToSelf()
                           └─ DuplicateHandle(new process → client, access = 0x100000 SYNCHRONIZE)

[response message]
  +0x28 MessageFlags = 0x50000001 (success) / 0x50000002 (handler error) / 1 (patched E_FAIL)
  +0x2c LastError    = HRESULT from ElevatedProcessStart
  +0xc8 NewProcessHandle = handle to the SYSTEM WerFault.exe process (in client table)
```

### Relevant interfaces

| Layer | Interface |
|---|---|
| Native API | `NtAlpcConnectPort`, `NtAlpcSendWaitReceivePort` (ntdll.dll — dynamically resolvable, no WDK needed) |
| ALPC port | `\WindowsErrorReportingServicePort` (default; overridable via `HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting` value `ErrorPort` REG_SZ) |
| Dispatch code | `MessageFlags = 0x50000000` in `DispatchPortRequestWorkItem` |
| Win32 helper objects | `CreateFileMappingW` (PAGE_READWRITE, ≥ 0x208 bytes) + `MapViewOfFile` carrying the wide-char argument string |
| Vulnerable sink | `WerSvc!CWerService::SvcElevatedLaunch` → `ElevatedProcessStart` → `CreateElevatedProcessAsUser` → `CreateProcessAsUserW` |

### Message struct (verified against itm4n's PoC + decompiled offsets)

| Offset | Size | Field |
|---|---|---|
| 0x00 | 0x28 | `PORT_MESSAGE` header (TotalLength `0x578`, DataLength `0x550`) |
| 0x28 | 4 | `MessageFlags` — in: `0x50000000`; out: `0x50000001`/`0x50000002`/`1` |
| 0x2c | 4 | `LastError` (out) |
| 0x30 | 4 | `Unknown` — **must be 1** |
| 0x38 | 8 | `FileMapping` — client handle, duplicated by the service |
| 0x40 | 16×8 | `SourceHandles[16]` — duplicated into the new process's handle table |
| 0xc0 | 4 | `CopySourceHandles` — also adds `PROCESS_CREATE_PROCESS` to the client-open |
| 0xc8 | 8 | `NewProcessHandle` (out, SYNCHRONIZE access) |
| 0xd0 | 0x4a8 | padding → total exactly `0x578` (`0x510` variant for WoW64 clients) |

### Operational prerequisite: WerSvc must be running

WerSvc is **trigger-started** and usually not running; a low-privilege user
cannot start it manually (`sc start WerSvc` is denied). itm4n explicitly left
the low-priv trigger as an open item. Practically, on a test VM: trigger WER
by crashing any process (e.g. run a null-deref dummy app), or check
`sc qtriggerinfo wersvc`. On patched hosts the request fails cleanly with
`MessageFlags=1`, `LastError=0xC0000001`, return `E_FAIL`.

---

## Exploitation Scenario

1. **Trigger WerSvc** (crash a dummy app / wait for a genuine WER event).
2. **Create the argument mapping** — `CreateFileMappingW` (pagefile-backed,
   `PAGE_READWRITE`, ≥ 0x208 bytes) + write the wide-char argument string with
   an early NUL.
3. **Connect** — `NtAlpcConnectPort` to `\WindowsErrorReportingServicePort`
   with `MaxMessageLength = 0x578`, `ALPC_MSGFLG_SYNC_REQUEST`.
4. **Send** — the 0x578 message described above via
   `NtAlpcSendWaitReceivePort`; on success `NewProcessHandle` references a
   `WerFault.exe` running as SYSTEM with your arguments.
5. **(Not public)** weaponise the controlled arguments/handle inheritance
   into code execution — left as future work by itm4n.

Note: the service spoofs the client's PID as the parent of the new process —
Microsoft Defender flags this behaviour even for the benign trigger PoC.

---

## Detection & Hunting (blue team)

### High-signal telemetry

| Source | Indicator |
|---|---|
| Sysmon **Event 1** / Security 4688 | `WerFault.exe` (or any SYSTEM binary) whose **command line contains an unexpected/unknown argument tail**; especially when the **parent process is a non-service, low-integrity user process** (parent-PID spoof → parent is the ALPC client, not `svchost.exe`/WerSvc) |
| Sysmon **Event 1** | `WerFault.exe` started **without** its usual switches (`-p`, `-s`, `-ip`, `-ss` …) or with a single junk/marker argument |
| Sysmon **Event 8** (CreateRemoteThread) / **Event 10** | follow-on abuse of the SYSTEM process (if weaponised) |
| Service telemetry | `WerSvc` (svchost) starting shortly before an anomalous `WerFault.exe` — attacker had to trigger the service |
| Defender alert | "Suspicious process created with a spoofed parent" on WerFault.exe — the writeup shows Defender already fires on this PoC |
| File mapping | `CreateFileMapping` + ALPC usage in processes that never do WER work (requires ETW/API monitoring — low fidelity, hunting only) |

### Sigma rule (starter)

```yaml
title: CVE-2026-20817 WerSvc Elevated Launch - WerFault with Spoofed Parent / Odd Args
id: 7d2c9a10-2081-74c2-9a10-020260208170
status: experimental
logsource:
  product: windows
  category: process_creation
detection:
  selection_image:
    Image|endswith: '\WerFault.exe'
  selection_user:
    User|contains: 'SYSTEM'
  filter_normal_parents:
    ParentImage|endswith:
      - '\svchost.exe'
      - '\WerFault.exe'
      - '\wermgr.exe'
  filter_normal_args:
    CommandLine|contains:
      - ' -p '
      - ' -s '
      - ' -ip '
      - ' -ss '
      - '-nonelevated'
  condition: selection_image and selection_user and not (filter_normal_parents and filter_normal_args)
level: high
```

(Tune the parent/argument filters to your fleet's real WerFault baselines —
the exploit's defining features are a **user-process parent** and a
**free-form argument tail**, both impossible in legitimate WER flows.)

### YARA (PoC marker — message constants)

```yara
rule CVE_2026_20817_PoC_marker {
    meta:
        description = "Detects WerSvc ALPC elevated-launch PoC tooling (message constants)"
        reference = "https://itm4n.github.io/cve-2026-20817-wersvc-eop/"
    strings:
        $port    = "\\WindowsErrorReportingServicePort" wide
        $api1    = "NtAlpcConnectPort" ascii
        $api2    = "NtAlpcSendWaitReceivePort" ascii
        $code    = { 00 00 00 50 }                       // dispatch code 0x50000000 (LE)
        $marker  = "cve_2026_20817_marker" wide
    condition:
        uint16(0) == 0x5A4D and ($port and 1 of ($api*)) and ($code or $marker)
}
```

### Sysmon config fragment

```xml
<RuleGroup name="cve-2026-20817">
  <ProcessCreate onmatch="include">
    <Image condition="end with">\WerFault.exe</Image>
  </ProcessCreate>
</RuleGroup>
```

(Pair with the parent/argument Sigma logic above — including every WerFault
launch is noisy, but the exclusion set is small and stable.)

---

## Remediation

1. **Apply KB5074109** (January 2026) or later; verify
   `WerSvc.dll ≥ 10.0.26100.7623`. The patch disables the endpoint outright
   (WIL `Feature_2473284922` → `E_FAIL`), so a functional check works: the
   PoC prints "host is NOT vulnerable" when the flag is active.
2. **Do not rely on blocking the ALPC port** — the port is created with a
   default DACL reachable by local users by design; the fix belongs in the
   service (and Microsoft chose removal).
3. **Hunt retroactively** for the parent-spoof/odd-argument WerFault pattern
   above, especially on hosts that were unpatched after PoC publication
   (March 2026).

---

## Timeline

| Date | Event |
|---|---|
| 2026-01-13 | Microsoft January 2026 Patch Tuesday (KB5074109), CVE-2026-20817 assigned |
| 2026-03-22 | itm4n publishes full technical analysis + trigger PoC |
| 2026-07-21 | This analysis: blue-team package; patch **verified** via ghidriff + headless Ghidra decompilation of both builds (`Feature_2473284922` early-`E_FAIL` block in `SvcElevatedLaunch`) |

---

## References

- [itm4n — CVE-2026-20817: Windows Error Reporting Service EoP](https://itm4n.github.io/cve-2026-20817-wersvc-eop/)
- [itm4n — PoC (GitHub)](https://github.com/itm4n/CVEs/tree/master/CVE-2026-20817)
- [MSRC — CVE-2026-20817](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-20817)
- [Hexacorn — SilentProcessExit analysis (prior WerSvc ALPC research)](https://www.hexacorn.com/blog/) (context for the ALPC server)
- [OnlyFm252 diff report — wersvc_dll-kb5074109](/data/patch_diffs/wersvc_dll-kb5074109.md)

---

<sub>Analysis: patch verified 2026-07-21 via ghidriff (VersionTrackingDiff) of
WerSvc.dll 10.0.26100.7309 vs 10.0.26100.7623 (Windows 11 24H2) plus headless
Ghidra decompilation of `SvcElevatedLaunch` (pre @ 0x18001a7e0 / post @
0x18001a980), `CheckIfValidPortMessage`, `DispatchPortRequestWorkItem`,
`ElevatedProcessStart`, `CreateElevatedProcessAsUser`, and `GetProcessToken`.
Message layout and reachability per itm4n's public analysis and PoC. The
`poc_cve_2026_20817.c` trigger PoC is a pure-C port of itm4n's work (ntdll
resolved dynamically; compiles with `cl.exe poc_cve_2026_20817.c /link
kernel32.lib`).</sub>
