Skip to content

AFD.sys AfdNotifyRemoveIoCompletion write-where (CVE-2023-21768)

afd!AfdNotifyRemoveIoCompletion'da eksik bir ProbeForWrite, user-mode bir caller'ın driver'ın yazacağı bir kernel pointer'ı geçmesine izin verir; bu da I/O Ring primitive'i üzerinden tam read/write'a yükseltilen constrained bir arbitrary write verir.

Mechanism

Root cause: user-supplied bir pointer'a, user-mode olduğu doğrulanmadan yapılan kernel write

AFD.sys (WinSock için Ancillary Function Driver), AfdNotifyRemoveIoCompletion tarafından servis edilen bir notification IOCTL'i expose eder. Handler, 0x18 offset'indeki field'ı bir pointer olan user-controlled bir input structure alır; driver, bir I/O completion dequeue edildiğinde bu pointer'a yazar — kaldırılan entry sayısını (efektif olarak sabit 1 değerini) o adrese store eder.

Bir kernel driver'ın, user mode'dan gelen herhangi bir output pointer üzerinde enforce etmesi gereken invariant şudur: dereference etmeden önce ProbeForWrite ile (ya da PreviousMode kontrol ederek) doğrula; böylece bir caller, kernel'a bir kernel adresine yazdıramaz. Vulnerable build, write'ı koşulsuz olarak gerçekleştirip PreviousMode != 0 (user-mode caller) path'inde ProbeForWrite'ı atlıyordu. Bu, bir untrusted-pointer dereference'tır (CWE-822): low-privileged bir process bir kernel adresi sağlar ve kernel uysalca oraya yazar — fixed'a yakın bir değerle yapılan bir write-what-where.

Walkthrough

Gabriel Landau'nun public root-cause analizi, patch'in standart PreviousMode/ProbeForWrite gate'ini eklediğini gösteriyor. Kavramsal olarak, patch'li mantık şöyle:

/* patched: validate the user pointer before writing to it */
if (PreviousMode == 0) {                 /* kernel-mode caller: trusted */
        *out_ptr = entries_removed;      /* field_0x18 */
} else {                                 /* user-mode caller: must probe */
        ProbeForWrite(out_ptr, sizeof(*out_ptr), 1);
        *out_ptr = entries_removed;
}

Pre-patch binary'de else/ProbeForWrite branch'i yoktur, dolayısıyla write, out_ptr nereyi gösterirse göstersin user-mode caller'lar için tetiklenir.

Write'ı sürmek ve I/O Ring ile yükseltmek

Write yalnızca bir completion gerçekten dequeue edildiğinde tetiklenir, dolayısıyla exploit şunları yapar:

  1. AFD socket'ine bir IoCompletion (completion port) object'i associate eder ve NtSetIoCompletion ile bir record queue'lar; böylece internal KeRemoveQueueEx/remove'un başarılı olmasını ve vulnerable store'u tetiklemesini sağlar.
  2. 0x18 field'ını seçilen bir kernel adresine point eder, böylece oraya 1 değeri yazdırılır. Bu constrained bir primitive'dir (küçük, fixed bir değer yazar), bu yüzden bir kernel _IORING_OBJECT'ine yöneltilir:
  3. Önce object'in RegBuffersCount'unu 1 yapar.
  4. Sonra RegBuffers pointer'ını, forge edilmiş nt!_IOP_MC_BUFFER_ENTRY descriptor'larını tutan bir user-mode adrese set eder.
  5. Attacker'ın istediği yere point eden bir registered buffer table ile, dokümante edilmiş I/O Ring API'leri (BuildIoRingReadFile / BuildIoRingWriteFile) arbitrary kernel read ve write gerçekleştirir.

Ardından arbitrary R/W, SYSTEM process (PID 4) token'ını locate etmek ve onu attacker'ın process'ine swap etmek için kullanılır; bu da NT AUTHORITY\SYSTEM'e yükseltir — Linux'taki commit_creds tarzı token replacement'a analog.

PreviousMode meselenin kalbi

Bug, field'ın writable olması değil; driver'ın bir kernel caller'ı (PreviousMode == 0) bir user caller'dan hiç ayırt etmemesidir. Fix, eklenen tek bir validation'dan ibaret; handler'ın geri kalan her şeyi değişmemiştir. Etkilenen: Windows 11 21H2/22H2 ve Windows Server 2022; Ocak 2023 cumulative update'inde düzeltildi.

Detection

  • EDR / kernel telemetry: low-integrity ya da standard-user bir process'in \Device\Afd'ye bir handle açıp, kernel-space bir adrese (>= 0x8000000000000000, x64'te) resolve olan bir output pointer ile notify IOCTL'i issue etmesi temel anomalidir.
  • I/O Ring abuse: AFD notify aktivitesini NtCreateIoRing / NtSubmitIoRing kullanımıyla ve şüpheli user adreslerine point eden registered buffer'larla korele et — public PoC'ler (örneğin yaygın olarak paylaşılan GitHub release'leri) tam olarak bu sırayı izler, dolayısıyla AFD-IOCTL → IoRing zinciri üzerindeki behavioral signature'lar etkilidir.
  • Token swap: post-exploitation'da, primary token'ı artık SYSTEM (PID 4) token'ıyla eşleşen beklenmedik bir process, EDR'lerin yakaladığı privilege escalation göstergesidir.
  • Crash signature: başarısız (misfired) denemeler, afd.sys içinde geçersiz bir kernel adresine yazarken bir KMODE_EXCEPTION_NOT_HANDLED / bugcheck üretir.

Mitigation

  • Vendor patch: AfdNotifyRemoveIoCompletion'a ProbeForWrite kontrolünü ekleyen Microsoft Ocak 2023 security update'ini kur (CVE-2023-21768 için MSRC advisory). Bu, otoriter (authoritative) fix'tir.
  • Hardening: HVCI / Kernel-mode Hardware-enforced Stack Protection'ı enable et ve KASLR'yi intact tut; böylece arbitrary-R/W → token-theft adımının maliyetini yükseltirsin; local logon'u kısıtla ve standart LPE-reduction policy uygula.
  • Exploit-chain mitigation: destekleniyorsa, I/O Ring registered-buffer primitive'inin abuse'unu monitor/block etmek, bu constrained write'ı tam kernel R/W'ye dönüştüren post-bug capability'yi sınırlar.

References