Named pipe (PipeQueueEntry) non-paged pool grooming¶
Bir named pipe'a okunmamış data yazmak, NPFS.SYS'in non-paged pool'da attacker-sized, attacker-filled bir
DATA_ENTRYallocate etmesine yol açar — Windows kernel pool belleğini spray'lemenin ve hole-punch'lamanın kanonik primitive'i.
Mechanism¶
Note
Bir process bir named pipe'a yazdığında ve diğer ucu hiçbir şey
okumadığında, Named Pipe File System (NPFS.SYS) o data'yı kernel içinde
buffer'lamak zorundadır. Inline threshold'u (kabaca bir page) aşan write'lar
için NPFS, bekleyen byte'ları tutmak üzere non-paged pool'dan özel bir
record — tarihsel olarak DATA_ENTRY / DATA_QUEUE_ENTRY ("PipeQueueEntry")
denen — allocate eder.
Bu, attacker'a named pipe'ları Windows kernel pool'larında ilk başvurulan grooming primitive'i yapan üç özellik verir:
- Kontrollü size. Toplam pool allocation'ı
header + payload'dur. NPFS, buffer'ın başına kendiDATA_ENTRYheader'ını ekler (header size'ı Windows build'leri arasında biraz değişir), yani write uzunluğunu seçerek allocation'ınızın hangi pool block / segment'e düştüğünü seçersiniz. 64-bit Windows'ta pool header0x10'dur ve writeup'lar kesin bir bucket'a (örneğin bir0x70block'a) isabet etmek için yaygın olarakpayload = target_alloc_size - header_overheadbiçiminde bir ilişki kullanır. - Kontrollü content.
DATA_ENTRYheader'ından sonraki her şeyWriteFile'a geçirdiğiniz ham byte'lardır — yani free-then-reclaimed object'in gövdesi tamamen attacker-controlled'dır. (Header'ın kendisi değil.) - Kontrollü lifetime. Allocation, data okunana ya da pipe handle
kapanana kadar yaşar.
ReadFileentry'yi drain edip free eder;CloseHandleonu yıkar. O on-demand free, sprayed bir bölgede "delik açmanıza" ve bir victim object'i tam istediğiniz yere yerleştirmenize izin verir.
Alex Ionescu bunu kamuya kernel heap feng shui olarak formalize etti ("Sheep Year Kernel Heap Fengshui: Spraying in the Big Kids' Pool"): named pipe'lar non-paged pool'u arbitrary-size, arbitrary-content chunk'larla spray'lemenize ve sonra onları seçici olarak free etmenize izin verir, bir overflow ya da use-after-free tetiklenmeden önce allocator'ı deterministik biçimde şekillendirir.
Walkthrough¶
Pattern şudur: spray → delik aç → bug'ı bir deliğe tetikle.
- Spray: birçok pipe oluşturun ve her birine okunmamış, boyutlandırılmış bir buffer yazın. Write uzunluğu pool bucket'ını seçer.
// Choose payload so total alloc lands in the target pool bucket.
// e.g. target 0x70-byte block, subtract NPFS DATA_ENTRY + pool header overhead.
#define BUFSIZE 0x28 // tune per Windows build / target bucket
BYTE payload[BUFSIZE];
memset(payload, 0x41, sizeof(payload));
HANDLE rd[N], wr[N];
for (int i = 0; i < N; i++) {
CreatePipe(&rd[i], &wr[i], NULL, sizeof(payload)); // anonymous pipe = NPFS
DWORD done;
WriteFile(wr[i], payload, sizeof(payload), &done, NULL); // allocates DATA_ENTRY
}
Özellikle bir named pipe için CreateNamedPipe /
CreateFile(\\.\pipe\...) kullanırsınız; grooming için anonymous pipe'lar
(CreatePipe, bunlar da NPFS tarafından desteklenir) daha basit ve eşdeğerdir.
- Delik aç: her ikincil allocation'ı free edin, tam olarak bucket size'ında free block'lardan oluşan bir dama tahtası bırakın. O size'daki bir sonraki victim allocation, free edilmiş slot'lardan birine düşer.
for (int i = 0; i < N; i += 2) {
CloseHandle(wr[i]); // frees the DATA_ENTRY for pipe i
CloseHandle(rd[i]);
}
// Now: [USED][free][USED][free]... at the target bucket granularity.
- Trigger: vulnerable driver path'ini çağırın, böylece bug'ın object'i free edilmiş bir slot'a, kontrol ettiğiniz hâlâ sprayed bir pipe buffer'ına komşu (ya da onunla overlapping) biçimde allocate edilsin. Bir overflow için victim'i kontrollü bir pipe buffer'ının önüne yerleştirirsiniz; bir UAF için free edilmiş victim slot'unu kontrollü bir pipe write ile reclaim edersiniz.
Spray'i geri okuma / doğrulama
Spray, NPFS pool tag'i sayesinde bir kernel debugger'da gözlemlenebilir. NPFS
data entry'leri tag'lenir (örneğin build'e bağlı olarak NpFr / pipe ile
ilgili tag'ler), yani spray'ledikten sonra yerleşimi !poolfind/!poolused
ile doğrulayabilir ve bir chunk'ı inceleyerek DATA_ENTRY header'ından hemen
sonra 0x41 filler'ınızı görebilirsiniz:
kd> !poolfind NpFr 0 // locate non-paged pool data-entry chunks
kd> dd <chunk+headerlen> // your attacker bytes (0x41414141...)
Write handle'ı kapatıp yeniden kontrol etmek, chunk'ın free list'e geri döndüğünü gösterir — victim object ile reclaim edeceğiniz delik.
Detection¶
- Teknik bir building block'tur, bir vulnerability değil, dolayısıyla detection
anormal hacime odaklanır: tek bir low-privileged process'in binlerce pipe
oluşturup okunmamış, aynı boyutta buffer'lar yazması güçlü bir grooming
sinyalidir. ETW (
Microsoft-Windows-Kernel-File/ NPFS create + write event'leri) ve handle-count telemetrisi bunu ortaya çıkarır. - Bir driver crash'ettiği sıralarda (pool corruption bugcheck
0x19/0xC2) NPFS pool tag'lerine atfedilebilen non-paged pool kullanımındaki ani büyüme, bir pool-overflow exploit'inin pipe'larla groom edildiğini düşündürür.
Mitigation¶
- Savunma çoğunlukla pipe'ları bloklamak yerine exploit-mitigation'dır (pipe'lar
çekirdek bir IPC primitive'idir). Modern Windows kernel pool hardening
determinism'i azaltır: pool allocation'ları randomize edilir, segment heap /
kLFH per-bucket randomize edilmiş free list'ler ekler ve
NXnon-paged pool eski executable spray target'ını ortadan kaldırır. - Driver yazarları size'ları valide etmeli ve güvenli allocation pattern'leri kullanmalıdır, böylece komşu kontrollü bir pipe buffer'ına bir overflow ile ulaşılamasın.
- Bu groom'ların etkinleştirdiği overflow'u shipping öncesi yakalamak için
test'te kernel pool corruption detection'ı (
verifier/ special pool) enable edin.