# Root Cause Analysis — CVE-2024-38193

## Summary

| Field | Value |
|---|---|
| **CVE** | CVE-2024-38193 |
| **Binary** | afd.sys (Ancillary Function Driver for WinSock) |
| **Component** | Registered I/O (RIO) buffer cache |
| **Bug Class** | Use-After-Free (CWE-416) via race condition (CWE-362) |
| **Impact** | Elevation of Privilege → SYSTEM |
| **CVSS 3.1** | 7.8 (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| **Exploited ITW** | Yes — Lazarus Group (Gen Digital attribution) |
| **Patch** | August 2024 Patch Tuesday (KB5041585 Win11 23H2, KB5041580 Win10 22H2) |
| **Pre-patch version** | 10.0.22621.3672 |
| **Post-patch version** | 10.0.22621.4036 |

## Vulnerability Overview

A use-after-free exists in the afd.sys Registered I/O (RIO) subsystem due to a
race condition between buffer cache population and buffer deregistration. Two
functions operate on the same `RIOBuffer` structure without adequate
synchronisation:

- **`AfdRioGetAndCacheBuffer()`** — called on the send/receive path; increments
  `RIOBuffer.RefCount` with `_InterlockedIncrement()` (atomic increment only,
  no lock) and stores the pointer in the per-socket `CachedBufferArrays[]`.
- **`AfdRioDereferenceBuffer()`** — called on `RIODeregisterBuffer()`; checks
  `RefCount == 1`, and if true, frees the `RIOBuffer` via
  `AfdRioCleanupBuffer()`.

If a thread calling `RIODeregisterBuffer()` checks `RefCount == 1` and proceeds
to free the buffer **between** the moment `AfdRioGetAndCacheBuffer()` reads the
`ArrayBuffers[]` pointer and the moment it executes
`_InterlockedIncrement(&v5->RefCount)`, the cache entry's `BufferPtr` points to
freed pool memory.

## Affected Structures

### RIOBuffer (0x20 bytes, non-paged pool, tag `RIOb`)

```c
struct RIOBuffer {
    PMDL   AssociatedMDL;        // +0x00  MDL for user buffer
    QWORD  VirtualAddressBuffer; // +0x08  kernel VA of user buffer
    DWORD  LengthBuffer;         // +0x10  buffer size
    DWORD  RefCount;             // +0x14  reference count
    DWORD  IsInvalid;            // +0x18  0=active, 1=freed, 2=pending-free
    DWORD  Unknown;              // +0x1C  reserved
};
```

### CachedBuffer

```c
struct CachedBuffer {
    DWORD      IdBuffer;   // registered buffer ID
    DWORD      RefCount;   // cache-local refcount
    RIOBuffer *BufferPtr;  // → RIOBuffer (dangling after UAF)
};
```

## Root Cause — Detailed Call Flow

### Registration (normal path)

```
User: RIORegisterBuffer(buf, len)
  → afd!AfdRioCreateRegisteredBuffer
      Acquires write spinlock
      Finds free slot in ArrayBuffers[]
      ExAllocatePool2(NonPaged, 0x20, 'RIOb') → new RIOBuffer
      RIOBuffer.RefCount = 1
      Releases spinlock
```

### Usage (send/receive path)

```
User: RIOSend(rq) or RIOReceive(rq)
  → afd!AfdRioValidateRequestBuffer
      → afd!AfdRioGetCachedBuffer(id)
          Cache hit? → return CachedBufferArrays[idx].BufferPtr
          Cache miss:
            → afd!AfdRioGetAndCacheBuffer(id)
                [A] v5 = ArrayBuffers[id]           // read pointer
                [B] if (!v5 || v5->IsInvalid) return NULL
                [C] _InterlockedIncrement(&v5->RefCount)  // ← RACE WINDOW
                    AfdRioEvictCachedBuffer(slot)
                    slot->BufferPtr = v5
                    slot->IdBuffer = id
                    return v5
```

### Deregistration (free path)

```
User: RIODeregisterBuffer(id)
  → afd!AfdRioDereferenceBuffer(fsctx, riobuf, id)
      [D] if (riobuf->RefCount == 1 ||
              _InterlockedExchangeAdd(&riobuf->RefCount, -1) == 1)
          {
              AcquireWriteLock
              ArrayBuffers[id] = NULL
              ReleaseWriteLock
      [E]     AfdRioCleanupBuffer(riobuf, 1)   // ExFreePool
          }
```

### The Race

```
Thread 1 (send)              Thread 2 (deregister)
─────────────────            ──────────────────────
[A] v5 = ArrayBuffers[id]
    (v5 is valid, RefCount=1)
                             [D] RefCount == 1 → TRUE
                             [E] AfdRioCleanupBuffer → ExFreePool(v5)
                                 ArrayBuffers[id] = NULL
[C] _InterlockedIncrement(
      &v5->RefCount)          ← WRITES TO FREED MEMORY
    slot->BufferPtr = v5      ← DANGLING POINTER IN CACHE
```

After the race, `CachedBufferArrays[slot].BufferPtr` points to freed 0x20-byte
non-paged pool. Subsequent `RIOSend()`/`RIOReceive()` calls will use the
`AssociatedMDL` field from whatever now occupies that memory.

## Exploitation Path (from Exodus Intel writeup)

1. **Heap spray**: Fill non-paged pool with 0x20-byte named-pipe unbuffered
   entries (no header, exact size match for `RIOBuffer`).
2. **Create holes**: Close selected pipes to leave gaps adjacent to spray blocks.
3. **Register RIO buffers**: `RIORegisterBuffer()` allocations fill the holes.
4. **Trigger race**: Two threads — one loops `RIOSend()`/`RIOReceive()`, the
   other loops `RIODeregisterBuffer()` across all registered buffer IDs.
5. **Reclaim freed RIOBuffer**: Spray more unbuffered pipe entries to land in
   the freed slot. Attacker controls `AssociatedMDL` field.
6. **Arbitrary R/W**: Craft a fake MDL in user-mode with `MappedSystemVa`
   pointing to `_SEP_TOKEN_PRIVILEGES` of the exploit process. `RIOSend()`
   copies FROM that address (arb read), `RIOReceive()` copies TO it (arb write).
7. **Privilege escalation**: Overwrite `_SEP_TOKEN_PRIVILEGES` to grant
   `SeDebugPrivilege` + all privileges → SYSTEM.

## Patch Analysis (ghidriff-verified)

The patch is gated behind two WIL CFR feature flags:
`Feature_3168083257` and `Feature_3695514937` (via
`Feature_AfdRioCachedBuffer_IsEnabled`). Three functions are changed:

### 1. AfdRioDereferenceBuffer (ratio 0.48 → significant rewrite)

**Pre-patch** (vulnerable):
```c
// Non-atomic check enables TOCTOU race
if (a2->RefCount == 1 || _InterlockedExchangeAdd(&a2->RefCount, -1u) == 1)
{
    AfdRioCleanupBuffer(a2, 1);   // Free
}
```

**Post-patch** (fixed, when feature flag enabled):
```c
// Atomic decrement with underflow guard
LOCK();
lVar5 = *plVar1;          // read RefCount (now 64-bit)
*plVar1 = *plVar1 - 1;    // atomic decrement
UNLOCK();
if (1 < lVar5) return;    // >1 means other refs exist, safe to return
if (lVar5 != 1) {
    swi(0x29);             // BugCheck — RefCount went below 1 (underflow)
    return;
}
// lVar5 == 1: we held the last ref, safe to free
AfdRioCleanupBuffer(a2, 1);
```

### 2. AfdRioGetAndCacheBuffer (ratio 0.48 → significant rewrite)

**Pre-patch**: Raw `_InterlockedIncrement(&v5->RefCount)` — no check if buffer
is being freed concurrently.

**Post-patch** (when feature flag enabled):
```c
// New: call AfdRioReferenceBuffer instead of raw increment
cVar1 = AfdRioReferenceBuffer(lVar2);
if (cVar1 == '\0') goto LAB_0;  // Buffer already freed → return NULL
// else: ref acquired safely, proceed to cache
```

### 3. AfdRioReferenceBuffer (NEW function — added in patch)

This is the atomic reference-acquire that replaces the raw
`_InterlockedIncrement`. It checks `IsInvalid` and atomically increments
`RefCount` only if the buffer is still alive, returning 0 (fail) if the buffer
has been freed or is being freed. This eliminates the TOCTOU window entirely.

### RIOBuffer structure change

The `RIOBuffer` structure grew — `IsInvalid` moved from offset `+0x18` to
`+0x24`, and `RefCount` from `+0x14` to a 64-bit field at `+0x18` (offset `+3`
in qword terms). The wider RefCount allows the underflow guard (`swi(0x29)` on
`RefCount < 1`).

## Reachability

- **Attack vector**: Local — requires code execution on the target
- **Privileges required**: Low — any user can create Winsock RIO sockets
- **User interaction**: None
- **API surface**:
  - `WSASocketA(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_REGISTERED_IO)`
  - `WSAIoctl(SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER)` → RIO function table
  - `RIORegisterBuffer()` / `RIODeregisterBuffer()` (the race pair)
  - `RIOSend()` / `RIOReceive()` (post-UAF primitive)
- **Device**: `\Device\Afd` (opened implicitly by Winsock)
- **No special capabilities needed** — standard user socket permissions suffice

## Detection

### YARA — Patched afd.sys Detection (CAS pattern)

```yara
rule CVE_2024_38193_Patched_AFD
{
    meta:
        description = "Detects patched afd.sys with CAS fix in AfdRioDereferenceBuffer"
        cve         = "CVE-2024-38193"
        author      = "OnlyFm252"

    strings:
        // _InterlockedCompareExchange(&RefCount, 0, 1) pattern
        // lock cmpxchg [reg+14h], ecx  where eax=1, ecx=0
        $cas_fix = { F0 0F B1 ?? 14 }  // lock cmpxchg [reg+0x14], reg

        // AfdRioCleanupBuffer call after successful CAS
        $cleanup = "AfdRioCleanupBuffer"

        // Pool tag
        $tag = "RIOb"

    condition:
        uint16(0) == 0x5A4D and
        filesize > 500KB and filesize < 1MB and
        $cas_fix and $tag and $cleanup
}
```

### YARA — Vulnerable afd.sys Detection

```yara
rule CVE_2024_38193_Vulnerable_AFD
{
    meta:
        description = "Detects pre-patch afd.sys with non-atomic RefCount check"
        cve         = "CVE-2024-38193"
        author      = "OnlyFm252"

    strings:
        // cmp dword ptr [reg+14h], 1  (non-atomic RefCount==1 check)
        $vuln_cmp = { 83 ?? 14 01 }

        // Followed by _InterlockedExchangeAdd(&RefCount, -1)
        // lock xadd [reg+14h], reg
        $xadd = { F0 0F C1 ?? 14 }

        $tag = "RIOb"

    condition:
        uint16(0) == 0x5A4D and
        filesize > 500KB and filesize < 1MB and
        $vuln_cmp and $xadd and $tag and
        for any i in (1..#vuln_cmp): (@xadd[1] - @vuln_cmp[i] < 0x30)
}
```

### Sigma — RIO UAF Exploitation Attempt

```yaml
title: CVE-2024-38193 afd.sys RIO UAF Exploitation Indicators
id: f8a2c3d1-5e7b-4a9c-b6d0-8e3f1a2c4b5d
status: experimental
description: >
    Detects behavioral indicators of CVE-2024-38193 exploitation:
    rapid named pipe creation (heap spray) followed by socket activity.
author: OnlyFm252
date: 2026/07/25
references:
    - https://blog.exodusintel.com/2024/12/02/windows-sockets-from-registered-i-o-to-system-privileges/
    - https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2024-38193
logsource:
    product: windows
    category: pipe_created
detection:
    selection_pipe_burst:
        EventType: CreatePipe
    filter_system:
        User|contains: 'SYSTEM'
    timeframe: 5s
    condition: selection_pipe_burst | count() > 500 and not filter_system
level: high
tags:
    - attack.privilege_escalation
    - attack.t1068
    - cve.2024.38193
```

### Sigma — Suspicious RIO Socket + Named Pipe Pattern

```yaml
title: CVE-2024-38193 Pre-exploitation Pattern
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: experimental
description: >
    Detects a process creating many named pipes AND loading afd.sys
    Winsock RIO functionality in a short window — consistent with
    heap spray + RIO race exploitation.
author: OnlyFm252
date: 2026/07/25
logsource:
    product: windows
    category: image_load
detection:
    selection_winsock:
        ImageLoaded|endswith:
            - '\ws2_32.dll'
            - '\mswsock.dll'
    selection_afd:
        ImageLoaded|endswith: '\afd.sys'
    condition: selection_winsock and selection_afd
level: medium
tags:
    - attack.privilege_escalation
    - attack.t1068
```

### Sysmon Configuration

```xml
<Sysmon schemaversion="4.90">
  <EventFiltering>
    <!-- Event 7: Detect Winsock RIO DLL loads by non-standard processes -->
    <ImageLoad onmatch="include">
      <ImageLoaded condition="end with">ws2_32.dll</ImageLoaded>
      <ImageLoaded condition="end with">mswsock.dll</ImageLoaded>
    </ImageLoad>

    <!-- Event 17/18: Named pipe burst detection (heap spray indicator) -->
    <PipeEvent onmatch="include">
      <EventType>CreatePipe</EventType>
    </PipeEvent>

    <!-- Event 1: Process creation with suspicious socket tool patterns -->
    <ProcessCreate onmatch="include">
      <CommandLine condition="contains">WSA_FLAG_REGISTERED_IO</CommandLine>
    </ProcessCreate>
  </EventFiltering>
</Sysmon>
```

## References

- [Exodus Intelligence — Windows Sockets: From Registered I/O to SYSTEM Privileges](https://blog.exodusintel.com/2024/12/02/windows-sockets-from-registered-i-o-to-system-privileges/)
- [MSRC Advisory — CVE-2024-38193](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2024-38193)
- [Gen Digital — Lazarus Group exploitation](https://decoded.avast.io/janvojtesek/lazarus-and-the-fudmodule-rootkit-beyond-byovd-with-an-admin-to-kernel-zero-day/)
