Skip to content

Windows Notification Facility (WNF) arbitrary R/W primitive

Abusing WNF state objects (_WNF_NAME_INSTANCE / _WNF_STATE_DATA) as a controllable, sprayable kernel-pool building block that converts a pool corruption into a relative-then-arbitrary read/write and an _EPROCESS leak.

Mechanism

Bug class / invariant

The Windows Notification Facility (WNF) is a lightweight publish/subscribe mechanism whose state lives in pool-allocated objects an unprivileged process can create, write, and query through Nt*WnfStateData syscalls. Two structures matter: _WNF_STATE_DATA (holds the published bytes plus AllocatedSize/DataSize length fields) and _WNF_NAME_INSTANCE (metadata, including a StateData pointer to the data object and a CreatorProcess pointer to the creator's _EPROCESS). The primitive does not come from a WNF bug — WNF is the interchangeable victim object. If a separate pool overflow corrupts a neighbouring _WNF_STATE_DATA's size field, the length check that normally bounds a read no longer reflects the real allocation; the invariant "a query reads only the bytes you actually own" is broken, yielding an out-of-bounds (relative) read across the pool. Because attacker-controlled WNF data is also fully sprayable, it doubles as ideal grooming filler.

Walkthrough

Conceptual reproduction drawn from NCC Group's public CVE-2021-31956 (NTFS + WNF) writeups. No offsets or working exploit are reproduced.

  1. Spray WNF state objects. Create many same-size WNF states so they pack a pool subsegment, establishing predictable adjacency for whatever pool overflow is in hand.

  2. Corrupt a _WNF_STATE_DATA size field. A small adjacent overflow inflates the victim's DataSize/AllocatedSize. The object becomes unbounded: querying it now reads far past its real buffer — a relative read across the subsegment.

    Conceptual effect (high level)
    before:  [ _WNF_STATE_DATA  DataSize = small  | data ]
    after :  [ _WNF_STATE_DATA  DataSize = HUGE   | data ........ >> reads neighbours ]
    
  3. Locate an adjacent _WNF_NAME_INSTANCE. Scan the over-long read for the recognizable layout of a name-instance object that the spray placed nearby.

  4. Leak _EPROCESS. Read the CreatorProcess member of that _WNF_NAME_INSTANCE to obtain the kernel address of the creating process's _EPROCESS — a free KASLR/info-leak that removes the need for a separate disclosure bug.

  5. Pivot to arbitrary read/write. Overwrite the _WNF_NAME_INSTANCE's StateData pointer (via the relative write) so it points at an attacker-chosen kernel address. Subsequent NtQueryWnfStateData / update calls then read or write that location — provided plausible AllocatedSize/DataSize values exist there to pass validation.

Limitations

LFH randomization makes the precise adjacency unreliable and crash-prone; reads near a page boundary can fault; and on later Windows builds WNF writes are capped (around 0x1000), so attackers often graduate to a sturdier primitive — e.g. flipping a thread's PreviousMode so NtReadVirtualMemory/NtWriteVirtualMemory skip address validation.

Detection

  • WNF API anomalies: EDR/syscall telemetry showing an unprivileged process creating an unusually large number of WNF states then issuing many NtQueryWnfStateData calls is atypical of normal apps.
  • Bugcheck clustering: failed corruptions surface as pool/UAF stops (BAD_POOL_HEADER, KERNEL_MODE_HEAP_CORRUPTION); repeated identical crashes from one process indicate exploit retries.
  • Post-exploitation signals: a process whose token/integrity jumps to SYSTEM, or evidence of PreviousMode tampering, follows the typical WNF-to-R/W chain.
  • Driver Verifier Special Pool on the suspected overflowing driver makes the originating corruption deterministic in triage.

Mitigation

  • Patch the upstream corruption bug (e.g. the NTFS EA underflow in CVE-2021-31956) — WNF is only the amplifier.
  • Keep Windows current: later builds cap WNF write size and harden pool metadata, shrinking the primitive.
  • Enable VBS/HVCI, kCFG/kCET, SMEP so a kernel R/W is harder to escalate to code execution.
  • Credential Guard reduces the value of an _EPROCESS/token read by isolating secrets.
  • Developers: never trust user-supplied length/size fields that bound a copy or query.

References

See also