# RCA — CVE-2023-28229 (keyiso.dll CNG Key Isolation UAF race)

- **Binary:** `keyiso.dll` (CNG Key Isolation service, hosted in lsass)
- **Patch:** KB5025239 (April 2023), `10.0.22621.819` → `10.0.22621.1555` (Win11 22H2)
- **Class:** CWE-416 use-after-free (race; 6 objects, same root cause) | **Impact:** EoP / sandbox escape | **ITW:** no (MS: Exploitation Less Likely — but a full exploit was written)
- **Primary source:** [k0shl / Cyber Kunlun](https://whereisk0shl.top/post/Isolate%20me%20from%20sandbox%20-%20Explore%20elevation%20of%20privilege%20of%20CNG%20Key%20Isolation/) (reporter's own writeup)
- **Diff:** `ghidriff/CVE-2023-28229/output/keyiso-10.0.22621.819.dll-keyiso-10.0.22621.1555.dll.ghidriff.md`

## Component

CNG Key Isolation is an RPC server inside **lsass** that brokers private-key
operations. Critically, it is reachable from **AppContainer IL** (browser
renderer sandboxes). Objects: `Context` (global `SrvCryptContextList`) →
`Provider` / `Key` / `Secret` / `MemoryBuffer`. Provider/Key/Secret share a
layout: magic at +0x0 (0x44444446/47/49), refcount at +0x8, predictable index
("handle") at +0x30 (per-context counter at +0xA0, increments per allocation).

## Root cause

Refcount lifetime race between allocation and free — shown for the Key object;
the other five object families share the pattern:

```c
// SrvCryptCreatePersistedKey:
keyobject->refcount = 1;            // [a] set to 1 (NO LOCK held)
SrvAddKeyToList(ctx, keyobject);    // [b] bumps refcount

// SrvCryptFreeKey:
if (InterlockedExchangeAdd(&obj->refcount, -1) == 1) {   // [c]
    SrvFreeKey(obj);                                       // [d] frees
}
if (InterlockedExchangeAdd(&obj->refcount, -1) == 1) {   // [e] use after free
    obj->vtable->fn(obj->vtable->arg, obj->field);         // [f] call thru +0x20 vftable (+0x80 fn, +0x118 arg)
}
```

Between [a] and [b] there is no lock: a racing free drops the count to 1, the
object is freed at [d], yet execution continues and at [f] calls through a
pointer stored at offset 0x20 of the freed buffer. Reclaiming the buffer
(`Use Context` property object is ideal: attacker-controlled size + content)
yields a controlled indirect call. Handles are **predictable** (sequential
index), so the attacker can call the free interface with the exact handle of
the just-allocated object.

## Patch

The free path is wrapped in the context critical section and refcounting moved
to 64-bit interlocked ops, closing the [a]→[b] window. Confirmed in the
ghidriff diff: **`SrvCryptFreeKey` only 46% match** (heavy rewrite),
`SrvFreeKey` 87%.

## Exploitation notes (for detection content)

- Chained with **CVE-2023-36906** ("Use Context" provider-property OOB read) to
  leak a provider object address from an adjacent memory-buffer allocation.
- lsass has XFG → no ROP; the controlled call is pointed at `LoadLibraryW`
  with a controlled DLL path → code execution in lsass → AppContainer escape.
- Trigger: three racing threads — allocate key / free key / spray property.

## Reaching the bug

Key isolation RPC interfaces (`s_SrvRpcCryptCreatePersistedKey`,
`s_SrvRpcCryptFreeKey`, property set/get) from an AppContainer or Medium IL
process — no IOCTL, no admin; RPC over the ncrypt isolation endpoint in lsass.

## Detection notes (blue team)

- Any non-lsass-typical process making high-frequency paired
  create/free key-isolation RPC calls from AppContainer IL.
- Suspicious DLL loads **by lsass.exe** (the LoadLibraryW endgame) — high-value
  Sigma/Sysmon signal (ImageLoad from non-system path).
- PoC: `static/data/patch_diffs/poc/poc_cve_2023_28229.c`
- Sibling entry: `kb/CVE-2023-36906.md` (info-leak half of the chain; its fix
  location on 22H2 is still open — see that file).
