Skip to content

I/O Ring (_IORING_OBJECT) RegBuffers arbitrary R/W primitive

Bir _IORING_OBJECT'in RegBuffers pointer'ını (ve RegBuffersCount'unu) sahte bir IOP_MC_BUFFER_ENTRY structure array'i ile overwrite etmek, bir kernel write'ı tam bir arbitrary read/write primitive'ine dönüştürür, çünkü registered I/O-ring buffer'larına index ile güvenilir ve asla tekrar probe edilmezler.

Mechanism

Windows 11'in I/O ring'i (NtCreateIoRing / CreateIoRing API'si) bir process'in buffer'ları bir kez önceden register etmesine ve sonra daha sonraki submission-queue entry'lerinde onlara index ile referans vermesine izin verir. Kernel object'i _IORING_OBJECT, RegBuffers (kernel tarafı buffer-descriptor array'ine giden pointer) ve RegBuffersCount'u tutar.

Note

Exploit edilebilir invariant probe-once, trust-forever'dır. BuildIoRingRegisterBuffers buffer'ları register ettiğinde, kernel user array'ini doğrular ve onu RegBuffers'ın işaret ettiği bir kernel pool allocation'a kopyalar. Daha sonraki her read/write submission'ında, kernel caller'ın buffer index'ini alır, RegBuffers[index]'e yürür ve orada saklı Address/Length'i tekrar probe etmeden kullanır — RegBuffers'tan erişilebilen her şeyin register zamanında zaten incelendiğini varsayar. Dolayısıyla bir attacker RegBuffers'ı sahte bir array'e işaret edecek şekilde swap edebilirse (ve index'i geçerli kılacak kadar yüksek RegBuffersCount ayarlayabilirse), kernel attacker'ın sahte IOP_MC_BUFFER_ENTRY descriptor'larına koyduğu arbitrary Address değerlerini okur veya yazar. RegBuffers field'ına yapılan bir kontrollü write, sınırsız R/W'ye amplifiye edilir.

Her descriptor bir IOP_MC_BUFFER_ENTRY'dir (Win11 22H2+, build 22610+) ve field'ları arasında Address (target), Length, Type (0xC02), Size (0x80), AccessMode (1) ve ReferenceCount (1) vardır. Sahte entry'ye karşı bir read I/O kernel verisini dışarı yazar; bir write I/O attacker verisini kernel'in içine okur — yani işlem yönü "read/write"a göre terstir.

Walkthrough

  1. Ring'i oluştur ve adresini leak et. CreateIoRing(...), ardından object'in kernel adresini geri elde etmek için NtQuerySystemInformation(SystemHandleInformation) üzerinden _IORING_OBJECT'i enumerate et.
  2. Usermode'da sahte bir descriptor array'i oluştur. Bir page'lik IOP_MC_BUFFER_ENTRY entry'si allocate et; her Address'i bir kernel target'a, Length'i transfer size'a, Type=0xC02, Size=0x80, AccessMode=1, ReferenceCount=1 olarak ayarla.
  3. Pointer'ı hijack et. Bug'ın arbitrary/limited write'ını kullanarak IoRing->RegBuffers'ı usermode array adresi ile overwrite et ve IoRing->RegBuffersCount'u yükselt ki seçilen index aralıkta olsun.
  4. I/O sür. BuildIoRingReadFile / BuildIoRingWriteFile ile sahte buffer'a index ile referans veren submission entry'lerini sıraya al, sonra NtSubmitIoRing (SubmitIoRing). Bir write-file op'u file'dan Address'e okur -> arbitrary kernel write; bir read-file op'u kernel Address'ini file'a yazar -> arbitrary kernel read. Named pipe'lar her zamanki file backend'idir, böylece hiçbir şey disk'e dokunmaz.
Fake IOP_MC_BUFFER_ENTRY (conceptual)
typedef struct _IOP_MC_BUFFER_ENTRY {
    ULONG  Size;          // 0x80
    ULONG  Type;          // 0xC02
    PVOID  Address;       // <-- arbitrary kernel target
    SIZE_T Length;        // transfer size
    ULONG  AccessMode;    // 1
    ULONG  ReferenceCount;// 1
} IOP_MC_BUFFER_ENTRY;

// After RegBuffers -> &fakeArray and RegBuffersCount = N:
BuildIoRingWriteFile(ring, fileRef, /*bufferIndex=*/0, len, 0, 0, flags);
SubmitIoRing(ring, 1, 0, NULL);   // file bytes land at fakeArray[0].Address

Warning

Sahte array ve adresleri yalnızca RegBuffers'ın arkasındaki varlıklarıyla doğrulanır; RegBuffersCount çok küçük bırakılırsa index check başarısız olur ve op reddedilir. Teknik _IORING_OBJECT kernel adresini gerektirir, dolayısıyla onu write'tan önce bir handle-table leak ile (NtQuerySystemInformation) eşle.

Tipik post-exploitation kullanımı: mevcut ve SYSTEM process'lerin _KPROCESS/_EPROCESS'ini oku, sonra SYSTEM _EX_FAST_REF token'ını mevcut process'in Token'ı üzerine arbitrary-write et, böylece SYSTEM elde et.

Detection

Davranışsal imza, buffer'ları register eden, sonra effective target adresleri kernel space'e düşen read/write submission'ları yapan bir I/O ring'dir. HVCI/kCFG bu data-only primitive'i durdurmaz. EDR, hemen ardından backing file'ı anonim bir named pipe olan I/O-ring submission'ları gelen NtQuerySystemInformation(SystemHandleInformation)'ı işaretleyebilir.

Mitigation

Microsoft, sonraki 22H2/23H2 servicing'inde I/O ring path'ini sertleştirdi (örn. RegBuffers handling'ini taşıyarak/doğrulayarak ve registered-buffer güvenini kısıtlayarak). Single-entry variant'ı, tüm-array swap'inin engellendiği yerde hayatta kalır. Defense-in-depth: çok sayıda I/O ring'in unprivileged oluşturulmasını kısıtla ve leak+submit pattern'ini izle.

References