WNF State Data
The Windows Notification Facility (WNF) is a kernel-mode publish/subscribe system that lets components signal state changes across the OS. Most Windows developers never interact with WNF directly, and most security researchers did not pay it much attention until the mid-2010s, when exploit developers discovered that WNF state data objects had properties that made them remarkably useful for kernel pool exploitation. A single pair of syscalls, NtUpdateWnfStateData and NtQueryWnfStateData, creates and reads back variable-size PagedPool allocations with fully attacker-controlled content. The allocation size can be tuned to any pool bucket. The data persists until explicitly overwritten or until the WNF state name is destroyed. And if a pool overflow or UAF corrupts the state data's internal length field, querying the state returns more bytes than were written, leaking adjacent pool memory.
WNF state data served as the primary PagedPool spray and relative-read primitive from approximately Windows 10 RS5 through Windows 10 21H2. It filled the gap left by the mitigation of GDI palette and bitmap objects (which were isolated in RS3/RS4) and was itself largely superseded by I/O Ring on Windows 11. Despite this evolution, WNF spray remains relevant for targeting legacy Windows 10 systems and for situations where PagedPool spray is needed on builds where pipe attribute isolation limits pipe attribute spray.
How WNF state data works
WNF operates on "state names," 64-bit identifiers that represent a piece of observable state. When a process calls NtUpdateWnfStateData with a state name and a data buffer, the kernel allocates a _WNF_STATE_DATA structure in PagedPool to hold the data. The allocation size is approximately sizeof(_WNF_STATE_DATA header) + data_length, where the header is a small fixed-size structure containing metadata like the data size, a change stamp, and internal flags.
The data content is fully attacker-controlled: whatever the process passes as the update buffer is stored verbatim in the kernel allocation. The change stamp is an incrementing counter that tracks modifications to the state. When a process calls NtQueryWnfStateData, the kernel locates the state data and copies the stored bytes to the user-mode output buffer, up to the number of bytes indicated by the internal DataSize field.
This read-back mechanism is the key to WNF's exploitation value. If a corruption extends the DataSize field of a _WNF_STATE_DATA to a value larger than the actual data, NtQueryWnfStateData copies the extended number of bytes, reading past the allocation boundary into adjacent pool memory. The excess bytes may contain kernel pointers from neighboring allocations (for KASLR bypass), pool metadata, or the content of adjacent kernel structures.
WNF state data as a spray primitive
For pool spray, WNF state data objects offer several advantages. Creating many WNF state names and updating each with data of a specific size fills the target PagedPool bucket with controlled allocations. The data content can include marker bytes for corruption detection, fake pointers for type confusion, or fake structure fields for UAF reclamation.
State data updates are fast: thousands of allocations can be created in under a second. Individual state names can be destroyed to free their allocations, enabling the hole-poking pattern needed for pool feng shui. And because state data sizes are precisely controllable, the attacker can target specific pool buckets with single-byte granularity.
The primary limitation is pool type. WNF state data allocations live in PagedPool, making them unsuitable for grooming NonPagedPoolNx targets. For NonPagedPoolNx, named pipe data queue entries are the standard alternative. Many exploit chains use both: WNF for PagedPool grooming and pipe queue entries for NonPagedPoolNx grooming.
WNF state data as a relative read
After a pool overflow or UAF corrupts an adjacent WNF state data entry, the relative read works through the same mechanism as pipe attribute reads. The corruption extends the DataSize field, causing NtQueryWnfStateData to read past the allocation boundary. The leaked bytes come from adjacent pool memory and are returned to user mode in the query output buffer.
The CVE-2023-28252 clfs.sys exploit chain demonstrates this pattern. After corrupting a CLFS container structure, WNF state data spray objects were used to control the pool layout and extract kernel pointers from adjacent allocations. The leaked pointers provided the KASLR bypass needed for subsequent exploitation steps.
Position in the primitive timeline
WNF state data exploitation occupies a specific window in the Windows kernel exploitation timeline. Before RS5, GDI palette and bitmap objects provided similar capabilities but were mitigated by type isolation starting in RS3. WNF filled the gap, providing a PagedPool spray and relative-read primitive that worked on RS5 through 21H2.
On Windows 11 21H2 and later, I/O Ring provides a superior kernel R/W mechanism that is more stable, more reusable, and supports both reads and writes from a single corrupted structure. WNF state data remains functional on these builds but is rarely the first choice when I/O Ring is available. The primitive matrix tracks this progression across Windows versions.
For modern exploit development targeting Windows 10 systems (still common in enterprise and embedded environments), WNF state data remains a relevant technique. The API is stable, the exploitation mechanics are well-understood, and there is no complete mitigation on any Windows 10 build.
Related CVEs
| CVE | Driver | Role of WNF |
|---|---|---|
| CVE-2023-28252 | clfs.sys |
WNF spray for controlled PagedPool layout |
See Also
- Pool Spray / Heap Feng Shui -- general pool spray theory and object selection criteria
- Pipe Attributes -- the alternative PagedPool spray primitive using named pipe attributes
- Named Pipe Objects -- the NonPagedPoolNx counterpart using
DATA_QUEUE_ENTRY - I/O Ring -- the successor technique for kernel R/W on Windows 11
- Primitive Matrix -- version-aware tracking of technique availability