# ncsi.dll Patch Diff — CVE-2025-59201

| | |
|---|---|
| Binary | ncsi.dll (Network Connection Status Indicator — hosted in the Network List Service, `netprofm`) |
| Pre-patch version | 10.0.26100.6725 (Windows 11 24H2, KB5065789 Sept 2025 preview) |
| Post-patch version | 10.0.26100.6899 |
| KB | KB5066835 (October 14, 2025) |
| CVE | CVE-2025-59201 — Improper Access Control, Elevation of Privilege to NETWORK SERVICE, CVSS 7.8 |
| Diff tool | ghidriff (Ghidra VersionTrackingDiff engine) |
| Functions changed | **1** with code changes (`StoreNcsiIEProxyString`) out of 6,130 matched |
| Functions added | **7**, incl. `ContainsRelativePathDoubleDot` and the WIL accessor for `Feature_3499399482` |
| Independent analysis | [itm4n — "CVE-2025-59201 - NCSI Elevation of Privilege"](https://itm4n.github.io/cve-2025-59201-ncsi-eop/) |
| Proof-of-concept | ETW trigger (itm4n blog); blue-team trigger variant in this repo |

## Summary

The binary side of this fix is tiny: one function changed, one helper added.
But — as itm4n showed — the DLL change is only half the story; the *actual*
vulnerability was a registry DACL, and KB5066835 also tightened
`HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters` (the `INTERACTIVE`
group lost its create-subkey right). Both halves are needed to understand the
CVE.

**Pre-patch.** `StoreNcsiIEProxyString` @ `0x1800546d0` builds a
`"<0|1><input>"` string (`StringCchPrintfExW(..., L"%s%s", ...)`), opens the
NCSI `ManualProxies` registry location with `KEY_SET_VALUE`, and calls
`RegSetValueExW(hKey, NULL, 0, REG_SZ, str, cb)` on the **default value** —
with **no validation of the input string at all**:

```c
// ncsi.dll 10.0.26100.6725 (pre-patch) — StoreNcsiIEProxyString @ 0x1800546d0
StringCchPrintfExW(buf, ..., L"%s%s", param_2 ? L"1" : L"0", param_1);
RegOpenKeyExW(HKLM, ncsiKey, 0, KEY_SET_VALUE, &hKey);
if (param_1 == NULL)
    RegDeleteValueW(hKey, NULL);
else
    RegSetValueExW(hKey, NULL, 0, REG_SZ, buf, cb);   // anything goes
```

**Post-patch.** Same function @ `0x1800549f0`, but the write is now gated
behind a WIL Controlled-Feature-Rollout check and the new helper:

```c
// ncsi.dll 10.0.26100.6899 (post-patch) — StoreNcsiIEProxyString @ 0x1800549f0
bVar1 = wil::details::FeatureImpl<__WilFeatureTraits_Feature_3499399482>
            ::__private_IsEnabled(...);
if (bVar1) {
    bVar1 = ContainsRelativePathDoubleDot(param_1);   // wcsstr(s, L"..") != NULL
    if (!bVar1)
        RegSetValueExW(hKey, NULL, 0, REG_SZ, buf, cb);   // only if NO ".."
} else {
    RegSetValueExW(hKey, NULL, 0, REG_SZ, buf, cb);        // legacy path (flag off)
}
```

`ContainsRelativePathDoubleDot` @ `0x180053294` (added) is literally
`wcsstr(input, L"..") != NULL` plus WPP tracing. Strings containing `..` are
silently dropped — the service just doesn't store them.

## How the write is reached

The trigger is not a direct API call into the service — it's an **ETW event**.
`netprofm`'s `ncsi.dll` registers a listener for the
`Microsoft-Windows-WinINet-Config` provider (GUID
`{5402e5ea-1bdd-4390-82be-e108f1e634f5}`); event ID **5600** (level
INFORMATION) carries `bAutoDetect`, `pwszAutoConfigUrl`, `pwszProxy`,
`pwszProxyBypass`. `EtwListener::ProcessEvent` handles it and funnels the
strings into `StoreNcsiIEProxyString`. Any local user can fire that ETW event
(it's what the Settings app's proxy page does legitimately), which means any
local user can make a NETWORK SERVICE process write an attacker-chosen string
into an HKLM registry value.

Combine that with the pre-patch DACL on `...\NlaSvc\Parameters` (INTERACTIVE
could create subkeys) → create a **registry symbolic link** so that
`...\Internet\ManualProxies` resolves elsewhere in HKLM → the coerced write
lands at an arbitrary HKLM location as NETWORK SERVICE. NETWORK SERVICE →
SYSTEM is a well-known step (James Forshaw's technique).

## Diff stats (ghidriff)

| Metric | Value |
|---|---|
| Functions matched | 6130 / 6137 (99.89%) |
| Matched with code changes | 1 — `StoreNcsiIEProxyString` (`0x1800546d0` → `0x1800549f0`) |
| Added | 7 — incl. `ContainsRelativePathDoubleDot` @ `0x180053294`, `Feature_3499399482` WIL plumbing |
| Deleted | 0 |
| Strings added/deleted | 1 / 1 |

See the [RCA](/data/patch_diffs/rca-cve-2025-59201.md) for the full call flow,
the DACL half of the bug, exploitation scenario, and detection guidance.

---

<sub>Diff produced 2026-07-22 with ghidriff against Win11 24H2 binaries from
the Microsoft symbol server; findings verified by headless Ghidra
decompilation of both builds (`StoreNcsiIEProxyString`,
`ContainsRelativePathDoubleDot`, `EtwListener::ProcessEvent`).</sub>
