I/O Ring Exploitation
Modern kernel read/write primitive using the Windows I/O Ring API to achieve arbitrary memory access through corrupted buffer registration entries.
Description
I/O Ring was introduced in Windows 11 21H2 (build 22000) as a high-performance asynchronous I/O mechanism modeled after Linux's io_uring. The kernel maintains an _IORING_OBJECT for each I/O Ring instance, which includes a registered buffer table -- an array of _IOP_MC_BUFFER_ENTRY structures describing pre-registered I/O buffers by base address and length. Legitimate I/O Ring operations reference these buffers by index, and the kernel reads the address from the table without re-validating it against user-mode address space.
By corrupting one or more entries in the registered buffer table, an attacker redirects I/O Ring read and write operations to arbitrary kernel addresses. A corrupted IORING_OP_READ operation causes the kernel to write file data to the target kernel address (arbitrary write), while a corrupted IORING_OP_WRITE causes the kernel to read from the target kernel address into a file descriptor (arbitrary read). This provides stable, repeatable arbitrary kernel R/W through legitimate kernel code paths with no need for shellcode, ROP chains, or SMEP/SMAP bypasses.
I/O Ring exploitation has become the dominant R/W primitive on Windows 11 22H2 and later, replacing older techniques like WNF state data corruption and palette/bitmap abuse. Its advantages include simplicity (standard API calls after initial corruption), stability (kernel performs normal memcpy operations), and reusability (the corrupted ring can be used for multiple reads and writes within a single exploitation session).
I/O Ring Architecture
Core Components
_IORING_OBJECT
+0x000 Type / Size
+0x010 SubmissionQueue // mapped into user space
+0x018 CompletionQueue // mapped into user space
+0x020 RegBuffersCount // number of registered buffers
+0x028 RegBuffers // pointer to _IOP_MC_BUFFER_ENTRY array
+0x030 RegFilesCount
+0x038 RegFiles // pointer to registered file objects
_IOP_MC_BUFFER_ENTRY
+0x000 Address // virtual address of registered buffer
+0x008 Length // buffer length
+0x010 Type // buffer type flags
+0x018 Mdl // MDL for the buffer (if locked)
Legitimate Operation Flow
- Create I/O Ring --
NtCreateIoRingallocates the_IORING_OBJECTin NonPagedPoolNx - Register buffers --
IORING_OP_REGISTER_BUFFERScauses the kernel to allocate a_IOP_MC_BUFFER_ENTRYarray and populate it with the addresses and lengths of user-supplied buffers - Submit I/O -- user-mode process writes Submission Queue Entries (SQEs) to the shared submission ring, referencing registered buffer indices
- Kernel processes SQEs -- for each SQE, the kernel looks up
_IOP_MC_BUFFER_ENTRY[index].Addressand performs the I/O operation using that address - Completion -- results are written to the completion ring, readable from user mode
Technique Details
Exploitation Steps
- Obtain initial corruption primitive -- via pool overflow, UAF, TOCTOU race, or other vulnerability in a driver
- Create I/O Ring instance -- call
NtCreateIoRingand register several user-mode buffers to populate the buffer table - Locate
_IORING_OBJECTin kernel memory -- useNtQuerySystemInformation(SystemHandleInformation)to find the kernel address of the I/O Ring handle object, or use pool spray adjacency to place it next to the vulnerable allocation - Corrupt
_IOP_MC_BUFFER_ENTRY.Address-- use the initial write primitive to overwrite theAddressfield of one registered buffer entry to point to a target kernel address - Arbitrary read -- submit an
IORING_OP_WRITESQE referencing the corrupted buffer index with a file descriptor; the kernel reads from the corrupted address (kernel memory) and writes to the file; read the file from user mode to obtain the kernel data - Arbitrary write -- submit an
IORING_OP_READSQE referencing the corrupted buffer index with a file containing attacker-controlled data; the kernel reads from the file and writes to the corrupted address (kernel memory) - Chain R/W operations -- use the read primitive to locate
_EPROCESS, read the SYSTEM token, then use the write primitive for token swapping - Clean teardown -- restore the corrupted buffer table entry to its original value before closing the I/O Ring handle to prevent BSOD during cleanup
Locating the IORING_OBJECT
Before corrupting the buffer table, the attacker must know its kernel address:
- NtQuerySystemInformation: The
SystemHandleInformationclass returns the kernel object address for each handle. Create an I/O Ring, then scan handle entries for theIoRingobject type to extract the_IORING_OBJECTkernel address. - Pool spray adjacency: Place the
_IORING_OBJECTadjacent to the vulnerable allocation via pool spray, and corrupt it directly without needing its absolute address. - Offset calculation: The registered buffer table is at a fixed offset within
_IORING_OBJECT. Once the base address is known, compute the buffer table address by adding the offset for the target Windows build.
Why I/O Ring Replaced Earlier Techniques
| Aspect | I/O Ring | WNF State Data | Palette/Bitmap (Legacy) |
|---|---|---|---|
| Windows version | 11 21H2+ | 10+ | 7/8/10 (patched) |
| Pool type | NonPagedPoolNx | PagedPool | Session Pool |
| R/W capability | Full read + write | Read-back only (typically) | Read + write |
| HVCI compatible | Yes (data-only) | Yes (data-only) | No (function ptr) |
| Stability | High | Moderate | Low on modern |
| Reusability | Multiple R/W per corruption | Limited | Limited |
I/O Ring's key advantages: - Both read and write from a single corrupted entry - Uses existing kernel code paths -- no shellcode or function pointer corruption needed - HVCI compatible -- purely data-oriented exploitation - Long-lived registered buffers remain valid across multiple operations - Simple user-mode API for triggering reads and writes
Practical Considerations
- Minimum version: Windows 11 21H2 (build 22000). Not available on Windows 10 or Server 2019.
- Initial corruption required: I/O Ring is not a vulnerability itself -- it requires an initial write primitive to corrupt the buffer table. Common sources include pool overflows (pool spray chains), UAF reclaim, and TOCTOU races.
- Buffer table in NonPagedPoolNx: The
_IOP_MC_BUFFER_ENTRYarray is allocated from NonPagedPoolNx. The initial corruption must be capable of reaching this pool region. - Offset stability: Structure offsets are stable within major Windows versions but should be verified per build. The
RegBufferspointer offset within_IORING_OBJECTand theAddressfield offset within_IOP_MC_BUFFER_ENTRYmust be correct. - File descriptor requirement: I/O Ring operations require a valid file descriptor. Exploits typically open a temporary file or use a pipe for the I/O target.
- Clean teardown is critical: Failing to restore the corrupted buffer table entry before closing the I/O Ring handle causes the kernel to attempt cleanup on an invalid address, resulting in BSOD.
Mitigations and Limitations
- I/O Ring API restrictions: Microsoft has progressively restricted I/O Ring functionality in newer Windows 11 builds. In 24H2 and later, certain I/O Ring operations are disabled or require additional validation, reducing the attack surface. The
NtCreateIoRingAPI may be restricted in future builds. - No integrity check on buffer entries: Currently, the kernel does not validate that registered buffer entries still point to legitimate user-mode addresses before performing I/O. Adding such validation would mitigate the technique but impact performance.
- HVCI does not block: Because I/O Ring exploitation is purely data-oriented (no code pointer corruption), HVCI provides no protection against this technique.
- KASLR: The attacker needs to know the
_IORING_OBJECTkernel address, which requires an info leak or handle table query. KASLR does not protect pool addresses, butNtQuerySystemInformationrestrictions in newer builds may limit address disclosure. - kCFI/XFG: Control flow integrity protections do not protect data structures like the buffer table, but they make alternative exploitation paths harder if I/O Ring is unavailable.
- Pool hardening: General pool corruption mitigations (segment heap isolation, pool cookies) make the initial corruption step harder but do not protect the I/O Ring structures once corruption is achieved.
Related CVEs
| CVE | Driver | Description |
|---|---|---|
| CVE-2023-21768 | afd.sys |
Missing ProbeForWrite leading to I/O Ring buffer table corruption |
| CVE-2024-38193 | afd.sys |
UAF exploited via I/O Ring R/W for token swap |
| CVE-2024-30085 | cldflt.sys |
Pool overflow chained with I/O Ring for kernel R/W |
| CVE-2024-30088 | ntoskrnl.exe |
TOCTOU race with I/O Ring exploitation |
| CVE-2024-26229 | csc.sys |
Arbitrary write chained with I/O Ring |
See Also
- Token Swapping -- the typical post-R/W escalation step after I/O Ring exploitation
- Pool Spray / Heap Feng Shui -- pool grooming used to position
_IORING_OBJECTfor corruption - Named Pipe Objects -- alternative R/W primitive and spray technique often combined with I/O Ring
- Pipe Attributes -- alternative read primitive that may precede I/O Ring corruption