---
title: 'WSAPatch — Binary Patcher for WSABuilds on Windows 11 26H1'
url: 'http://kvc.pl/repositories/wsapatch'
markdown: 'http://kvc.pl/repositories/wsapatch.md'
date: '2026-08-29'
description: 'WSAPatch is a pure MASM x64 binary patcher for WSABuilds 2407.40000.4.0 that fixes the STATUS_STOWED_EXCEPTION crash on Windows 11 26H1 (build 28000+) caused by AppUriHandlerRegistrationManager.UpdateAsync() returning E_ACCESSDENIED.'
---

> WSAPatch is a pure MASM x64 binary patcher for WSABuilds 2407.40000.4.0 that fixes the STATUS\_STOWED\_EXCEPTION crash on Windows 11 26H1 (build 28000+) caused by AppUriHandlerRegist

 [ Download WSAPatch.7z](http://kvc.pl/repositories/wsapatch/WSAPatch.7z) [ Download Source Code](http://kvc.pl/repositories/wsapatch/WSAPatch_source_code.zip) [ Source on GitHub](https://github.com/wesmar/WSAPatch)

# WSAPatch — Binary Patcher for WSABuilds on Windows 11 26H1

![WSAPatch](http://kvc.pl/user/pages/04.repositories/25.wsapatch/images/WSAPatch.jpg)

**Pure MASM x64 · Zero CRT · 8.5 KB · Windows 11 26H1** *Fixes `STATUS_STOWED_EXCEPTION` crash in WSABuilds 2407.40000.4.0* *Two precise binary patches to `WsaClient.exe` — anchor-based scan, no fixed offsets* *Android apps run stably after patch — deep link registration silently skipped* 

---

## Table of Contents

- [The Problem](#the-problem)
- [Debugging Session](#debugging-session)
- [Full crash call chain](#crash-chain)
- [Why E\_ACCESSDENIED on 26H1](#why-e-accessdenied)
- [The Two Patches](#the-two-patches)
- [Anchor-based scan](#anchor-based-scan)
- [Usage](#usage)
- [Project layout](#project-layout)
- [Technical highlights](#technical-highlights)
- [Building from source](#building-from-source)
- [Requirements](#requirements)

---

## The Problem

WSA (WSABuilds / MustardChef **2407.40000.4.0**) crashes within **a few seconds to ~40 seconds** on Windows 11 **26H1** (build 10.0.28000+). The subsystem loads, apps launch — then `WsaClient.exe` silently dies.

| Property | Value |
|---|---|
| **Target binary** | WSABuilds / MustardChef 2407.40000.4.0 — `WsaClient.exe` |
| **Affected OS** | Windows 11 26H1 (build 10.0.28000+) |
| **Exception code** | `STATUS_STOWED_EXCEPTION (0xC000027B)` |
| **Root HRESULT** | `0x80070005` — `E_ACCESSDENIED` |
| **Faulting API** | `AppUriHandlerRegistrationManager.UpdateAsync()` |
| **Effect of patch** | Android apps run stably; URI handler registration silently skipped |

Everything starts fine. WSA loads, Android apps launch. Then `WsaClient.exe` calls `AppUriHandlerRegistrationManager.UpdateAsync()` to register URI scheme handlers (deep links) — the API returns `E_ACCESSDENIED`, the WinRT exception propagates uncaught, and `combase!RoFailFastWithErrorContext` terminates the process.

---

## Debugging Session

### Step 1 — Capturing the crash dump

WER (Windows Error Reporting) was set to capture a full dump on `WsaClient.exe` crash. Initial triage with CDB:

```
.ecxr
k 50
!analyze -v
```

Output:

```
Faulting module:  combase!RoFailFastWithErrorContextInternal2
Stowed exception: ResultCode = 0x80070005 (E_ACCESSDENIED)
                  ExceptionAddress = WsaClient+0x82AEB
```

`STATUS_STOWED_EXCEPTION` is a WinRT mechanism: when a WinRT exception crosses the ABI boundary without being caught, the runtime stores it ("stows" it) and later calls `RoFailFastWithErrorContext`. The stored HRESULT here was `E_ACCESSDENIED`.

### Step 2 — Identifying the WinRT interface

The stowed exception address led to a vtable pointer. Inspecting the interface GUID:

```
da WsaClient+0x35f788
dq WsaClient+0x316d58 L1
ln poi(WsaClient+0x316d58)
dq WsaClient+0x4ad278 L1
ln poi(WsaClient+0x4ad278)
```

Result:

| Symbol | Value |
|---|---|
| **GUID** | `{D54DAC97-CB39-5F1F-883E-01853730BD6D}` |
| **Interface** | `IAppUriHandlerRegistrationManagerStatics` |
| **Method** | `consume_Windows_System_IAppUriHandlerRegistrationManagerStatics<...>::GetDefault()` |

### Step 3 — Finding the two crash points

Two independent code paths both lead to the fatal handler:

**Point A** — HRESULT dispatcher at `WsaClient+0xC93D`

A 13-entry dispatch table maps known error codes to handlers. One entry checks for `E_ACCESSDENIED`:

```asm
; WsaClient+0xC93D
cmp  ebx, 80070005h    ; E_ACCESSDENIED
jne  +0x19             ; 75 19 — skip to next entry
; falls through to fatal handler if matched
```

**Point B** — directly after `IAsyncOperation::GetResults()` at `WsaClient+0x81EE5`

```asm
; WsaClient+0x81EE5
call qword ptr [rdi+40h]   ; GetResults() → eax = 0x80070005
test eax, eax              ; SF=1 (HRESULT failure bit)
js   +0x26                 ; 78 26 — jump to error path
; should fall through to normal epilogue
```

---

## Full crash call chain

flowchart TD START[WsaClient.exe startup] --> CALL[AppUriHandlerRegistrationManager.UpdateAsync] CALL --> GETRES["IAsyncOperation::GetResults()\n→ returns 0x80070005 E_ACCESSDENIED"] GETRES --> TSTEAX["TEST EAX, EAX\nJS +0x26 ← Point B\njumps to error path"] TSTEAX --> CHKHR["winrt::check_hresult(0x80070005)\nthrows C++ exception"] CHKHR --> UNWIND["Stack unwind through catch-all block\n@ WsaClient+0x30dc5b"] UNWIND --> DISPATCH["HRESULT dispatcher\nCMP EBX, 80070005h\nJNE +0x19 ← Point A\nfalls through to fatal path"] DISPATCH --> RFFC["combase!RoFailFastWithErrorContext\n(0x80070005)"] RFFC --> RAISE["KERNELBASE!RaiseFailFastException"] RAISE --> CRASH["STATUS_STOWED_EXCEPTION\n0xC000027B → process terminated"] 

---

## Why E\_ACCESSDENIED on 26H1

`AppUriHandlerRegistrationManager.UpdateAsync()` registers URI scheme handlers (deep links) for MSIX/UWP/Android apps at runtime. WSA calls it at startup so Windows knows which Android app should handle a given URI scheme.

At some point Microsoft tightened the capability requirements for this API — it now requires `restrictedAppUriHandlerHost` or equivalent package trust level. WSA's `AppxManifest.xml` declares `windows.appUriHandler` and `runFullTrust`, but that is no longer sufficient on 26H1.

|  |  |
|---|---|
| **Affected builds** | Windows 11 26H1 (10.0.28000+) |
| **Unaffected builds** | Windows 11 22H2, 23H2 — API succeeds or code path unreached |
| **Root cause** | API capability requirement tightened mid-2025 |
| **Impact of skipping** | Deep links declared statically in `AppxManifest.xml` remain registered; only the dynamic runtime update is omitted |

---

## The Two Patches

Both patches are located by **anchor-based scanning** — pre-bytes and post-bytes bracket the site — making the patcher robust against minor linker layout differences.

### Patch 1 — Bypass E\_ACCESSDENIED in HRESULT dispatcher

Turns a conditional branch into an unconditional one: the dispatcher always skips to the non-fatal path, so `E_ACCESSDENIED` is never routed to the fatal handler.

|  |  |
|---|---|
| **File offset** | `0x0C943` |
| **RVA** | `0xD543` |
| **Original** | `75 19` — `JNE rel8 +0x19` |
| **Replacement** | `EB 19` — `JMP rel8 +0x19` |
| **Pre-anchor** | `81 FB 05 00 07 80` (`CMP EBX, 80070005h`) |
| **Post-anchor** | `19` (jump offset byte) |

### Patch 2 — Ignore HRESULT from GetResults()

Removes the signed-jump that redirected to the error path. Execution falls through to the normal function epilogue, stack canary check and `ret` — no exception thrown, no crash.

|  |  |
|---|---|
| **File offset** | `0x81EE7` |
| **RVA** | `0x82AE7` |
| **Original** | `78 26` — `JS rel8 +0x26` |
| **Replacement** | `90 90` — `NOP NOP` |
| **Pre-anchor** | `85 C0` (`TEST EAX, EAX`) |
| **Post-anchor** | `48 8B 4D` (next instruction prefix) |

---

## Anchor-based scan

Each patch site is described by a `PATCH_DESC` struct (48 bytes):

| Field | Size | Description |
|---|---|---|
| `name_ptr` | 8 B | Pointer to ANSI description string |
| `pre[8]` | 8 B | Anchor bytes immediately before the patch site |
| `pre_len` | 4 B | Valid bytes in `pre[]` |
| `orig_bytes[4]` | 4 B | Original bytes at patch site |
| `repl_bytes[4]` | 4 B | Replacement bytes |
| `patch_len` | 4 B | Number of bytes to patch (1–4) |
| `post[8]` | 8 B | Anchor bytes after `orig`/`repl` |
| `post_len` | 4 B | Valid bytes in `post[]` (0 = skip post check) |

`ScanPatch` (implemented in `scan.asm`) walks the file image byte-by-byte:

flowchart LR START[i = 0] --> PRE{data at i\nmatches pre?} PRE -->|No| NEXT[i++] NEXT --> START PRE -->|Yes| POST{post_len > 0?} POST -->|Yes| POSTCHK{data at i+pre+patch\nmatches post?} POSTCHK -->|No| NEXT POSTCHK -->|Yes| ORIG{patch site\nmatches orig?} POST -->|No| ORIG ORIG -->|Yes| NEEDS[ST_NEEDS_PATCH\nreturn i] ORIG -->|No| REPL{patch site\nmatches repl?} REPL -->|Yes| ALREADY[ST_ALREADY_PATCHED\nreturn i] REPL -->|No| NEXT 

The result is one of three states: `ST_NEEDS_PATCH`, `ST_ALREADY_PATCHED`, or `ST_NOT_FOUND`. If any patch returns `ST_NOT_FOUND`, the file is not written — unsupported version, no damage.

---

## Usage

Run from an **elevated command prompt** (Administrator):

```
WSAPatch.exe [path\to\WsaClient.exe]
```

Without a path argument, WSAPatch discovers `WsaClient.exe` automatically via the registry:

```
HKLM\SYSTEM\CurrentControlSet\Services\WsaService\ImagePath
```

`FindWsaPath` (`reg.asm`) reads the `ImagePath` value, strips `WsaService.exe` and the `WsaService\` directory component, then appends `\WsaClient\WsaClient.exe`. Supports both `REG_SZ` and `REG_EXPAND_SZ` (calls `ExpandEnvironmentStringsA`).

A `.bak` backup is created before any write. Running on an already-patched file prints `Already patched` and exits cleanly (idempotent).

**Example output — fresh patch:**

```
WsaClient.exe patcher for Windows 11 26H1
==========================================
[*] No path given - searching via WsaService registry...
[*] Target: C:\wsa\WsaClient\WsaClient.exe
[*] Path found via registry

[*] Read 0x4DFC00 bytes

Patch 1: Bypass E_ACCESSDENIED check (JNE->JMP)
  [+] Found at 0xC93D, patching 0xC943: 75  -> EB

Patch 2: Ignore HRESULT from GetResults() (JS->NOP NOP)
  [+] Found at 0x81EE5, patching 0x81EE7: 78 26  -> 90 90

[*] Backup saved to *.bak
[*] Done. 2 patches applied.
```

---

## Project layout

```
WSAPatch/
├── x64/
│   ├── consts.inc   Win32 constants, PATCH_DESC struct definition
│   ├── globals.inc  Exported data variable declarations
│   ├── io.asm       OutStr / OutHex / OutByte (WriteFile, no CRT)
│   ├── scan.asm     ScanPatch — anchor-based binary scan
│   ├── reg.asm      FindWsaPath — registry path discovery
│   └── patch.asm    Entry point, patch table, orchestration
└── build.ps1        ML64 + LINK64, /NODEFAULTLIB, UAC manifest
```

| Module | File | Responsibility |
|---|---|---|
| **Entry / orchestration** | `patch.asm` | `mainCRTStartup`, patch loop, file I/O, backup, output |
| **Console output** | `io.asm` | `OutStr`, `OutHex`, `OutByte` — raw `WriteFile`, no CRT |
| **Binary scan** | `scan.asm` | `ScanPatch` — anchor-based pattern search, returns state + offset |
| **Registry discovery** | `reg.asm` | `FindWsaPath` — reads `WsaService\ImagePath`, derives `WsaClient.exe` path |
| **Constants / structs** | `consts.inc` | `PATCH_DESC` layout, Win32 constants, scan states |
| **Build script** | `build.ps1` | `ML64` + `LINK64`, `/NODEFAULTLIB`, UAC manifest injection |

---

## Technical highlights

| Feature | Detail |
|---|---|
| **Pure MASM x64** | Zero CRT, zero C runtime — only `kernel32.dll` + `advapi32.dll` |
| **8.5 KB binary** | No imports beyond Win32, no startup overhead |
| **Anchor-based scan** | Finds patch sites by surrounding byte patterns, not fixed offsets — robust across minor linker layout differences |
| **Full unwind tables** | Proper `.pushreg` / `.allocstack` / `.endprolog` in every `proc frame` — correct stack walking under WER and debuggers |
| **UAC manifest** | `/MANIFESTUAC:level='requireAdministrator'` via linker flag — elevation prompt on launch |
| **Idempotent** | Detects already-patched bytes (`ST_ALREADY_PATCHED`), never double-patches |
| **Registry autodiscovery** | Reads `HKLM\SYSTEM\CurrentControlSet\Services\WsaService\ImagePath`, strips service exe, appends client path |
| **No file written on failure** | If any patch returns `ST_NOT_FOUND`, the file is not touched — safe on unsupported versions |

---

## Building from source

Requires **Visual Studio 2026 / Build Tools v18** (MASM x64 + LINK):

```powershell
.\build.ps1
```

Output: `bin\WSAPatch.exe` — verified no CRT imports by the build script (`dumpbin /imports`).

The build script calls `ML64.exe` for each `.asm` file, then `LINK64.exe` with `/NODEFAULTLIB` and an embedded UAC manifest (`/MANIFESTUAC:level='requireAdministrator'`).

---

## Requirements

|  |  |
|---|---|
| **Target binary** | WSABuilds / MustardChef 2407.40000.4.0 (`WsaClient.exe`, verified by anchor scan) |
| **OS** | Windows 11 26H1 (tested on build 10.0.28000.2113) |
| **Elevation** | Administrator required (UAC manifest embedded) |
| **Build tools** | Visual Studio 2026 / Build Tools v18 — `ML64.exe` + `LINK64.exe` |

---

*WSAPatch — Pure MASM x64, zero external dependencies.*
[kvc.pl](https://kvc.pl) | <marek@wesolowski.eu.org> | [GitHub](https://github.com/wesmar/WSAPatch)

### Add a comment

---

## Navigation

- Parent: [Repositories](http://kvc.pl/repositories.md)
- Previous: [LnkForge — Windows Shortcut Payload Builder](http://kvc.pl/repositories/lnkforge.md)
- Next: [VaultGuard](http://kvc.pl/repositories/vaultguard.md)
