CVE-2023-21768
AFD WinSock — missing ProbeForWrite allows kernel write-what-where via IO ring
Summary
| Field | Value |
|---|---|
| Driver | afd.sys |
| Vulnerability Class | User Boundary Validation |
| Vulnerable Build | 10.0.22621.608 (KB5020044) |
| Fixed Build | 10.0.22621.1105 (KB5022303) |
| Exploited ITW | No |
Affected Functions
AfdRioValidateRequestBufferAfdNotifyRioRemoveCompletion
Root Cause
The afd.sys driver (Ancillary Function Driver for WinSock) is a kernel-mode driver that handles socket operations on behalf of user-mode applications. It sits between the user-mode WinSock library and the underlying transport drivers, processing I/O requests related to socket communication. The vulnerability exists in a code path related to I/O completion handling, specifically within functions that manage Registered I/O (RIO) completion notifications.
The root cause is a missing ProbeForWrite call. When processing a socket I/O completion via the IO ring mechanism, afd.sys writes a completion status to a buffer address supplied by user mode. Under normal secure coding practices, the driver must call ProbeForWrite on any user-supplied pointer before writing to it, which verifies that the target address falls within user-mode address space and is writable. In the vulnerable code path, this probe was omitted entirely.
Because the probe is absent, an attacker can supply a kernel-mode address as the completion target buffer pointer. The driver blindly writes the I/O completion status -- a value the attacker can influence through the socket operation -- directly to this kernel address. This provides a write-what-where primitive: the attacker controls both the destination address (via the completion buffer pointer passed to the driver) and the value written (via manipulating the I/O status result).
AutoPiff categorizes this as user_boundary_check with detection rules:
added_probe_for_writeadded_probe_call
Exploitation
The write-what-where primitive obtained through the missing probe allows writing attacker-controlled values to arbitrary kernel-mode addresses. The chompie1337 proof-of-concept demonstrates exploitation using the I/O Ring API, a feature introduced in Windows 11. The exploitation flow is: create an IO ring, register a buffer specifying a kernel-mode address as the completion target, perform an AFD socket operation that triggers the completion write path, and the kernel writes the I/O status to the attacker-specified kernel address without validation.
The PoC uses this write primitive to overwrite the current process's token pointer in the EPROCESS structure with a copy of the SYSTEM process token, achieving elevation of privilege from a standard user to SYSTEM. The target addresses (the EPROCESS token field and the SYSTEM token value) are obtained through standard information leak techniques.
The exploit is highly reliable because the write-what-where primitive is fully deterministic. There is no race condition to win and no heap layout dependency -- the write occurs exactly once to the exact address specified by the attacker, making exploitation essentially 100% reliable on vulnerable builds.
Patch Analysis
The patch shipped in KB5022303 (build 10.0.22621.1105) added ProbeForWrite calls before writing to user-supplied buffer addresses in the I/O completion path within afd.sys. This ensures the target address is validated to be within user-space bounds and writable before the driver performs the completion status write. If the address falls in kernel space, the probe raises an exception that is caught by the driver's exception handler, preventing the write.
AutoPiff detects this patch pattern via the added_probe_for_write and added_probe_call rules, which identify the introduction of new ProbeForWrite invocations in the patched binary's disassembly that were absent in the vulnerable build.
Detection
YARA Rule
rule CVE_2023_21768_AFD {
meta:
description = "Detects vulnerable version of afd.sys (pre-patch)"
cve = "CVE-2023-21768"
author = "KernelSight"
severity = "high"
strings:
$mz = { 4D 5A }
$driver_name = "afd.sys" wide ascii nocase
$vuln_build = "10.0.22621.608" wide ascii
$func_validate = "AfdRioValidateRequestBuffer" ascii
$func_notify = "AfdNotifyRioRemoveCompletion" ascii
condition:
$mz at 0 and $driver_name and $vuln_build and ($func_validate or $func_notify)
}
ETW Indicators
| Provider | Event / Signal | Relevance |
|---|---|---|
| Microsoft-Windows-AFD | RIO completion events | Registered I/O completion notification path where the missing ProbeForWrite allows kernel address writes |
| Microsoft-Windows-Kernel-Audit | Token modification events | Detects the EPROCESS token overwrite used to escalate from standard user to SYSTEM |
| Microsoft-Windows-Security-Auditing | Event ID 4672 (Special privileges assigned) | Low-privilege process acquiring SYSTEM-level privileges immediately after IO ring operations |
| Microsoft-Windows-Security-Auditing | Event ID 4688 (Process creation) | Child process launched with SYSTEM token from an unprivileged parent following AFD socket activity |
| Microsoft-Windows-Kernel-Process | IO ring registration events | Abnormal IO ring buffer registrations specifying kernel-mode addresses as completion targets |
Behavioral Indicators
- A user-mode process creates an IO ring via
CreateIoRing, registers a buffer withBuildIoRingRegisterBuffers, and then performs an AFD socket operation that triggers a completion write to a kernel-mode address - The exploit does not require pool spraying or heap grooming -- the write-what-where primitive is fully deterministic, making the syscall pattern (IO ring create, buffer register, AFD operation) a reliable detection signature
- The process calls
NtDeviceIoControlFileon an AFD socket handle with I/O completion targeting an address above the user/kernel boundary (typically above0x7FFFFFFFFFFF) - Token replacement in the current process's EPROCESS structure: the process transitions from a standard user token to SYSTEM without any legitimate elevation mechanism
- Post-exploitation, the process (or a newly spawned child) operates with SYSTEM integrity, often performing actions inconsistent with its original user context