# CVE-2026-20817 – Windows Error Reporting Service EoP

This vulnerability was such a gaping hole in the Windows Error Reporting service that Microsoft completely removed the affected feature. A low privilege user could simply send a specially crafted ALPC message with a reference to a command line that the service executed with `SYSTEM` privileges. At least that’s what I thought initially.

## MSRC Vulnerability Summary

CVE-2026-20817 is local privilege escalation vulnerability caused by an “ _Improper Handling of Insufficient Permissions or Privileges_”, according to its CWE classification, in the Windows Error Reporting service. Its discovery is credited to Denis Faiustov and Ruslan Sayfiev, with GMO Cybersecurity.

_High-level description of CVE-2026-20817_

According to the FAQ section, “ _an attacker who successfully exploited this vulnerability could gain SYSTEM privileges_”, and its exploitation is deemed “ _more likely_”.

## Patch Analysis

The Windows Error Reporting service is a common target, so we already know that we should start our analysis with a “binary diffing” of `WerSvc.dll`, which is the main DLL implementing the service. For reference, I compared the version `10.0.26100.7309` (before the patch) with the version `10.0.26100.7623` (containing the patch). As usual, you can find their download links easily thanks to Winbindex.

The analysis is straightforward. The only function that appeared to have undergone changes is `SvcElevatedLaunch`, which is a rather unequivocal name.

Usually, I find it hard to understand a patch right from the graph view, but this time, it offered an immediate overview of the fix. As we can see on the annotated screenshot below, two code blocks (in gray) and a “feature test” were added. It shows that, if `__private_IsEnabled()` ( _i.e._ the patch) is enabled, the function returns immediately with the error code `0x80004005` ( `E_FAIL`).

In other words, the patch completely removes the vulnerable feature. Now, we have two things to figure out. First, what is the vulnerability, and second, how to reach the vulnerable code.

## The vulnerability

The prototype of `SvcElevatedLaunch` generated by Ghidra, based on the DLL’s PDB file published by Microsoft, is as follows. I just edited the parameter names.

`1 2 3 4 5long CWerService::SvcElevatedLaunch( CWerService *this, _WERSVC_MSG *p_msg_in, // Pointer to input ALPC message _WERSVC_MSG *p_msg_out // Pointer to output ALPC message )`

Although the symbol `_WERSVC_MSG` is public, the structure is not documented publicly. However, a quick analysis shows that it is an extension of the known `_PORT_MESSAGE` structure, which acts as a sort of common header for ALPC messages.

> For a deep dive into ALPC, I’d recommend 0xcsandker’s blog post Offensive Windows IPC Internals 3: ALPC.

The `SvcElevatedLaunch` method does two things. First, it opens the ALPC client’s process. Second, it invokes the internal function `ElevatedProcessStart`, passing a reference to a File Mapping object opened by the ALPC client. Again, it should be noted that all the parameter names, and the content of the `_WERSVC_MSG` structure, were edited by me as a result of a few hours of reverse engineering the internal functions down the call tree.

`1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21// Open the ALPC client’s process dwProcAccessRights = PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION; OpenSenderProcessThread( this, // CWerService class p_msg_in, // Pointer to input ALPC message dwProcAccessRights, // Client process access rights &hClientProcessHandle, // Pointer to output client process handle 0, // Client thread access rights, not user here NULL, // Pointer to output client thread handle, not used here 0 ); // Start elevated process ElevatedProcessStart( p_msg_in->Unknown, hClientProcessHandle, p_msg_in->FileMapping, pClientHandleListCopy, uVar3, &hNewProcessHandle ); // Return result p_msg_out->NewProcessHandle = hNewProcessHandle; p_msg_out->MessageFlags = 0x50000001;`

The internal function `ElevatedProcessStart` performs the following steps.

1. It duplicates the client’s File Mapping handle into the current process with the Win32 API `DuplicateHandle`.
2. It maps a view of the client’s File Mapping object with the Win32 API `MapViewOfFile`.
3. It reads the content of the mapped file into a local (wide) string buffer.
4. If calls the internal function `CreateElevatedProcessAsUser`, with this input string as a parameter.
5. If the previous call succeeds, the output process handle is copied to the client’s process with the Win32 API `DuplicateHandle`.

As for `CreateElevatedProcessAsUser`, it does the following.

1. It creates a `SYSTEM` Token (or an elevated Token on behalf of the current user, if it’s an admin under UAC).
2. It constructs a `WerFault.exe` command line.
3. It invokes the Win32 API `CreateProcessAsUserW`, with the command line constructed previously.

The `WerFault.exe` command is as follows. The absolute path of `WerFault.exe` is constructed securely using the `GetSystemDirectoryW` API, and is passed as the `lpApplicationName` parameter of `CreateProcessAsUserW`. However, the ALPC client has full control over the command line options, passed to the service through its File Mapping buffer.

`1C:WindowsSystem32WerFault.exe [USER CONTROLLED OPTIONS HERE]`

So, by sending a specially crafted ALPC message, a client is able to coerce the Windows Error Reporting service to start `WerFault.exe` as `SYSTEM`, with controllable command line options. All of a sudden, this vulnerability sounds a lot less exciting because the client doesn’t control the program that is executed.

## Reaching the Vulnerable Code

According to its incoming call tree, `SvcElevatedLaunch` is reached from an (A)LPC thread which invokes a generic handler named `_ProcessRequest`. This handler eventually calls `DispatchPortRequestWorkItem`.

_Call tree leading to `SvcElevatedLaunch`_

As its name suggests, `DispatchPortRequestWorkItem` is a dispatcher which contains a huge switch statement. In our case, `0x50000000` is the code that triggers the call to `SvcElevatedLaunch`.

_Extract of `DispatchPortRequestWorkItem`_

By going even further up in the call tree, we can see that `WerSvc` creates an (A)LPC server when it starts. The name of the port is `WindowsErrorReportingServicePort`.

> The call to
>
> `NtAlpcCreatePort` appears further down, and is not represented on the screenshot below.

Interestingly enough, this ALPC server has already been the object of offensive security research in the past. Most notably, I found an analysis of the `SilentProcessExit` feature, by Hexacorn. Searching a bit further for online resources also lead me to the (re)discovery of the “LSASS Shtinkering” technique (notably this implementation), which contains some sample code to send and receive ALPC messages.

Sending an ALPC message is rather simple. You first need to invoke `NtAlpcConnectPort` to connect to an ALPC port, and then call `NtAlpcSendWaitReceivePort` to send your message and receive the response from the server. The real challenge is to figure out the format of the message, and make sure you set all the important values inside that structure are set appropriately. I already alluded to it in the previous part. I’ll spare you the tedious reverse engineering part and give you what I found directly.

`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 29typedef struct _WERSVC_MSG_ELEVATED_LAUNCH { // Offest: 0x00 PORT_MESSAGE PortMessage; // Offest: 0x28 (40) DWORD MessageFlags; // p_msg_out + 0x28 (40) -> Message type + error code // on return. (DWORD) // Offset: 0x2c (44) DWORD LastError; // p_msg_out + 0x2c (44) -> Last error code returned by // “ElevatedProcessStart”. (DWORD) // Offset: 0x30 (48) BOOL Unknown; // p_msg_in + 0x30 (48) -> unknown value, but must be “1” // according to if condition in “ElevatedProcessStart”. (DWORD) // Offset: 0x38 (56) HANDLE FileMapping; // p_msg_in + 0x38 (56) -> A File Mapping Object, containing // options to pass to “WerMgr.exe”. (HANDLE) // Offset: 0x40 (64) HANDLE SourceHandles[16]; // p_msg_in + 0x40 (64) -> An array of client handles // to be duplicated into the new process. (HANDLE[16]) // Offset: 0xc0 (192) BOOL CopySourceHandles; // p_msg_in + 0xc0 (192) -> A boolean determining access // rights for opening client process? // Offset: 0xc8 (200) HANDLE NewProcessHandle; // p_msg_out + 0xc8 (200) -> Receives the handle of the // created process. (HANDLE) // Offset: 0xd0 (208) BYTE Padding[1192]; // Total structure size must be 0x578, this is checked in // “CWerService::CheckIfValidPortMessage”. } WERSVC_MSG_ELEVATED_LAUNCH, * PWERSVC_MSG_ELEVATED_LAUNCH;`

As mentioned previously, the structure starts with a `PORT_MESSAGE` header. Then, the `MessageFlags` attribute must contain the value `0x50000000` if we want to reach `SvcElevatedLaunch`. The second most important attribute is `FileMapping`. It contains a handle to a File Mapping object, which acts as shared memory to transfer the arguments used in the `WerFault.exe` command line to the WER service. Optionally, the client can also pass up to 16 handles that will be duplicated into the created process.

## Proof-of-Concept

Following this analysis, I wrote a proof-of-concept which you can find on my GitHub. It only triggers the `WerFault.exe` command line execution as `SYSTEM` with a user-controlled buffer containing the command line arguments. It does not try to actually gain arbitrary code execution. Further work is required to achieve this result.

> While working on this research, I came across a fake PoC on GitHub here:
>
> `https://github.com/oxfemale/CVE-2026-20817`. Although the Visual Studio solution doesn’t seem malicious at first glance, the code doesn’t make much sense. Nonetheless, this is a good reminder we should all be extra cautious with what we download from online repositories.

_Proof-of-Concept for CVE-2026-20817_

A couple of things are important to note about this PoC:

– the WER service must be running;
– it is assumed that the ALPC port name is `WindowsErrorReportingServicePort`;
– Defender detects a malicious behavior.

Obviously, the WER service must be running before attempting to run the PoC, otherwise the ALPC server won’t be listening. However, this service is usually not running, and is triggered only when required. Although, low privilege users cannot start it manually, it has a start trigger that can most likely be used for that purpose. I haven’t looked into this particular aspect yet.

By default, the port name `WindowsErrorReportingServicePort` is used to start the ALPC server, but a custom one can be defined in the registry key `HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting` ( `ErrorPort` value of type `REG_SZ`). This PoC assumes that the default port name is used.

The last thing to note is that the `WerFault.exe` process is created by the WER service as a child process of the client’s process. To do so, it implements a succession of API calls to spoof the client’s PID and set it as a parent of the new process. Because this technique is widely abused by malware, Defender flags it as a suspicious behavior and raises an alert.

## Conclusion

Although Microsoft do not require a fully functional proof-of-concept to take action, meaning that they don’t ask you to absolutely pop a `SYSTEM` shell, it is likely that this primitive was abused by the original researchers to do so.

More concretely, I suspect that a clever combination of `WerFault.exe` command line options can be used in conjunction with some Windows internals trickery, such as shown in the last part of the blog post A Deep Dive into the GetProcessHandleFromHwnd API recently published by James Forshaw.

To be continued?…