CVE-2024-38193
AFD — use-after-free race on Registered I/O buffers allows EoP
Exploited in the Wild
This vulnerability was exploited in the wild before or shortly after patching.
Summary
| Field | Value |
|---|---|
| Driver | afd.sys |
| Vulnerability Class | Use-After-Free / Lifetime |
| Vulnerable Build | 10.0.22621.3672 (KB5039212) |
| Fixed Build | 10.0.22621.4036 (KB5041585) |
| Exploited ITW | Yes |
Affected Functions
AfdRioDeregisterBufferAfdRioCloseCompletion
Root Cause
The vulnerability resides in afd.sys, the Ancillary Function Driver that implements core Winsock functionality in the Windows kernel. Specifically, the bug is in the driver's handling of Registered I/O (RIO) buffers, a high-performance I/O API introduced in Windows 8 that allows applications to pre-register memory buffers for use with asynchronous socket operations, reducing per-operation overhead by avoiding repeated buffer validation and locking.
When a RIO buffer is registered via AfdRioRegisterBuffer, the driver allocates internal tracking structures that maintain metadata about the registered memory region, including its virtual address, length, MDL (Memory Descriptor List), and a reference count. The vulnerability is a race condition in the buffer deregistration path (AfdRioDeregisterBuffer): when a RIO buffer is deregistered while an asynchronous I/O operation referencing that buffer is still in flight, the driver frees the tracking structure prematurely. However, a dangling reference to the freed structure persists in the context of the pending I/O operation.
The race window exists because the deregistration path does not properly synchronize with pending I/O completions. It decrements the reference count and proceeds to free the tracking structure without ensuring that all outstanding I/O operations referencing the buffer have completed. When the in-flight I/O eventually completes and the completion callback (AfdRioCloseCompletion) fires, it dereferences the now-freed tracking structure pointer. Because the freed memory can be reallocated with attacker-controlled data via kernel pool spraying techniques, this results in a classic use-after-free condition where the driver operates on attacker-crafted data.
AutoPiff categorizes this as lifetime_fix with detection rules:
added_refcount_guardadded_use_after_free_guardadded_null_check_before_deref
Exploitation
The use-after-free provides a controlled object reuse primitive in the Windows kernel pool. Because the attacker controls when the deregistration occurs and can influence what data fills the freed allocation, the primitive is powerful: the freed RIO buffer tracking structure's slot can be reclaimed with a crafted object of the same size, and the subsequent I/O completion callback will operate on that fake object as though it were a legitimate tracking structure. This gives the attacker the ability to corrupt kernel memory in a targeted manner.
The exploitation sequence proceeds as follows: (1) register a RIO buffer to allocate the tracking structure, (2) initiate an asynchronous I/O operation that references the registered buffer, (3) race to deregister the buffer while the I/O is still pending, causing the tracking structure to be freed, (4) spray the kernel pool with crafted objects sized to reclaim the freed slot, and (5) when the I/O completion callback fires, it dereferences fields in the attacker-controlled fake object. Although the exploit requires winning a race condition, the RIO API provides sufficient control over I/O timing and completion ordering to make exploitation reliable in practice.
The Lazarus Group (North Korea-linked threat actor) weaponized this vulnerability to achieve arbitrary kernel read/write capabilities. They leveraged the corrupted pointer dereferences to overwrite process token structures in kernel memory, escalating privileges from a standard user context to SYSTEM. This was used to deploy FudModule v3, a data-only rootkit from the same malware family previously deployed via CVE-2024-21338 (an appid.sys vulnerability). FudModule operates entirely through direct kernel object manipulation (DKOM), disabling security monitoring callbacks and ETW providers to evade endpoint detection.
Patch Analysis
The patch shipped in KB5040442 (build 10.0.22621.4036) adds reference count guards around the RIO buffer deregistration path in AfdRioDeregisterBuffer. The fix ensures that the tracking structure cannot be freed while I/O operations are still outstanding: the reference count is properly incremented before initiating any I/O operation that references the buffer, and the deregistration path now checks the reference count and defers freeing the structure until all outstanding operations have completed. This closes the race window by making the lifetime of the tracking structure dependent on both explicit deregistration and the completion of all in-flight I/O operations.
Additionally, the patch introduces null checks before dereferencing the buffer tracking pointer in the I/O completion path (AfdRioCloseCompletion), providing defense-in-depth against the dangling pointer scenario. AutoPiff detects this patch via the added_refcount_guard, added_use_after_free_guard, and added_null_check_before_deref rules, which collectively identify the addition of reference counting logic around resource lifetime boundaries and protective null-pointer checks in completion handlers.
Detection
YARA Rule
rule CVE_2024_38193_AFD {
meta:
description = "Detects vulnerable version of afd.sys (pre-patch)"
cve = "CVE-2024-38193"
author = "KernelSight"
severity = "high"
strings:
$mz = { 4D 5A }
$driver_name = "afd.sys" wide ascii nocase
$vuln_build = "10.0.22621.3672" wide ascii
$func_dereg = "AfdRioDeregisterBuffer" ascii
$func_close = "AfdRioCloseCompletion" ascii
$func_reg = "AfdRioRegisterBuffer" ascii
condition:
$mz at 0 and $driver_name and $vuln_build and 2 of ($func_*)
}
ETW Indicators
| Provider | Event / Signal | Relevance |
|---|---|---|
| Microsoft-Windows-WinSock-AFD | RIO buffer registration / deregistration events | Monitors the exact code path where the race condition occurs between buffer deregistration and I/O completion |
| Microsoft-Windows-Kernel-Process | Process token modification events | Detects process token overwrite in kernel memory — the primitive used by Lazarus to escalate to SYSTEM |
| Microsoft-Windows-Security-Auditing | Event 4688 (Process Creation) | Identifies SYSTEM-level child processes spawned from a low-privilege parent after AFD socket operations |
| Microsoft-Windows-Kernel-Audit | Object handle audit events | Tracks rapid creation and destruction of AFD socket handles consistent with race condition exploitation |
Behavioral Indicators
- Rapid sequence of RIO buffer registration (
SIO_REGISTER_BUFFER) followed by immediate deregistration while asynchronous I/O operations are still pending, indicating race condition triggering - Kernel pool spray activity in the NonPagedPoolNx targeting the same slab size as the RIO buffer tracking structure (~0x80-0x100 bytes), using objects like
NtCreateEventor pipe attributes - Process token structure corruption detected via integrity checks — the
_TOKEN.Privilegesfield is modified to includeSeDebugPrivilegeandSeImpersonatePrivilegewithout going through legitimate elevation paths - Deployment of FudModule rootkit artifacts: direct kernel object manipulation (DKOM) disabling ETW providers and security callbacks, detectable through missing or zeroed callback registration entries
- Low-privilege process creating Winsock RIO completion queues and registered buffers at abnormal rates, followed by privilege escalation to SYSTEM within the same process lifetime