Low Fragmentation Heap (LFH) grooming¶
Windows LFH front-end allocator'ını öyle şekillendir ki aynı boyuttaki allocation'lar bir LFH subsegment içinde tahmin edilebilir, attacker-chosen pozisyonlara düşsün; bir corruption hedefini controllable bir object'in yanına yerleştirmek için bucket randomization'ı etkisizleştir (ya da etrafından dolaş).
Mechanism¶
Bu not vs. LFH internals
Bu kayıt LFH'yi deterministik adjacency için şekillendirme primitive'ini anlatır. Allocator'ın yapısal tanımı (NT Heap vs Segment Heap, UserBlocks, bucket/subsegment organizasyonu) ayrı bir not'tadır: bkz. Low Fragmentation Heap (LFH).
Suistimal edilen invariant
LFH, sık ve eşit boyutlu request'lere hizmet eden Windows front-end allocator'ıdır. Bir size class, "ısıtılana" kadar LFH tarafından servis edilmez: kabaca aynı boyutta 18 ardışık allocation o size class'ı aktive eder (bucket-enable), ardından request'ler LFH subsegment'lerinden — subsegment başına bir bitmap ile takip edilen sabit boyutlu userblock dizilerinden — kesilir.
Deterministik grooming'e direnmek için modern LFH, bir request'in hangi free slot'u alacağını randomize eder. Allocator RtlpLowFragHeapRandomData'yı (0x100 byte'lık rastgele byte tablosu) ve devam eden bir index tutar; free chunk için yapılan bitmap araması o random veriden türetilen bir pozisyondan başlar. RS3 öncesi build'ler index'i tahmin edilebilir şekilde ilerletiyordu (yalnızca wrap / MSB==LSB durumunda RtlpHeapGenerateRandomValue32() ile yeniden seed'leyerek), yani bir alloc → free → alloc döngüsündeki sonraki slot aynı slot oluyordu. Grooming'in tam da suistimal ettiği determinizm budur: bir size class LFH-aktif olduğunda ve index bilinen bir state'te olduğunda, saldırgan bir sonraki allocation'ın daha önce yerleştirilmiş bir object'ten önce mi sonra mı düşeceğini tahmin edebilir ve böylece adjacency kurar.
Walkthrough¶
Hemen ardından controllable bir object gelen bir victim object elde etmek için bir LFH bucket'ını groom'lamak:
// 1. Heat the bucket: force LFH activation for size S (~18 allocs).
for (int i = 0; i < 24; i++) spray[i] = HeapAlloc(h, 0, S);
// 2. Defragment: free a long run so the subsegment is mostly free,
// leaving the bitmap/index in a known state.
for (int i = 0; i < 24; i++) HeapFree(h, 0, spray[i]);
// 3. Re-spray to fill the subsegment densely with placeholder objects.
for (int i = 0; i < N; i++) holes[i] = HeapAlloc(h, 0, S);
// 4. Punch a hole and immediately reclaim it with the victim, then
// the adjacent slot with the controllable object.
HeapFree(h, 0, holes[k]);
victim = HeapAlloc(h, 0, S); // lands in the freed slot
HeapFree(h, 0, holes[k+1]);
attacker = HeapAlloc(h, 0, S); // lands adjacent on vulnerable builds
Deterministic_LFH araştırması temel gözlemi gösterir: etkilenen build'lerde aynı boyuttaki bir malloc → free → malloc tam olarak aynı chunk'ı döndürür, çünkü rastgele arama pozisyonu özdeş bitmap offset'inden devam eder.
WinDbg'de LFH aktivasyonunu gözlemlemek
0:000> !heap -s
0:000> dt _HEAP_SUBSEGMENT // UserBlocks, BlockSize, BlockCount
0:000> dt _HEAP_USERDATA_HEADER // BusyBitmap (the per-subsegment free map)
_HEAP_ENTRY chunk'ları) servis edilir; aktivasyondan sonra allocation'lar bir
_HEAP_SUBSEGMENT'in userblocks dizisinden gelir ve BusyBitmap bit'leri çevirir.
Tuzaklar
- LFH yalnızca backend'in churn gördüğü boyutlar için devreye girer; one-shot allocation'lar onu asla kullanmaz. Yaklaşık bir boyutu değil, tam byte boyutunu (LFH granularity bucket'ına yuvarlanmış) ısıtmalısın.
- Windows 10 build 16179+'da arama pozisyonu her allocation'da
RtlpHeapGenerateRandomValue32()ile yeniden randomize edilir, yani naif "aynı chunk geri" determinizmi gitmiştir — grooming garantili olmaktan çıkıp probabilistik hale gelir (yoğun spray yap, bir başarı oranını kabul et). - LFH ayrıca block header'larını subsegment başına bir cookie ile XOR-encode eder; kör header corruption'ı free sırasında encoding kontrolünde başarısız olur.
- Subsegment'ler dolar ve yeni bir subsegment taze (rastgele) bir base'de allocate edilir; cross-subsegment adjacency kontrol edilebilir değildir, dolayısıyla spray'i tek bir subsegment'in
BlockCount'u içinde tut.
Detection¶
Page-heap / Application Verifier (gflags), LFH'yi guard-page allocation'larıyla değiştirir ve altta yatan overflow'u yakalar. ETW heap event'leri ve WinDbg'deki !heap -p, grooming spray'lerine özgü anormal allocation/free churn'ünü açığa çıkarır. EMET/Win10 heap integrity kontrolleri encoded-header corruption'ında tetiklenir.
Mitigation¶
Randomize edilmiş LFH (RtlpLowFragHeapRandomData'dan seed'lenen ve RS3+'ta allocation başına yeniden randomize edilen bitmap-offset randomizasyonu), subsegment başına header XOR cookie'leri ve Segment Heap front-end'i (kendi randomize edilmiş backend'i ile) — hepsi deterministik yerleşimi bozar. Savunma yapanlar Segment Heap'i tercih etmeli, heap-termination-on-corruption'ı etkinleştirmeli ve güvenlik açısından hassas object pool'ları için guard allocation kullanmalıdır.
References¶
- saaramar/Deterministic_LFH — LFH internals and deterministic grooming
- Low Fragmentation Heap (LFH) Exploitation – Windows 10 Userspace (Saar Amar)
- Microsoft Learn — Low-fragmentation Heap (Win32)
- Chris Valasek — Understanding the Low Fragmentation Heap (Black Hat USA 2010)
- Mark Vincent Yason — Windows 10 Segment Heap Internals (Black Hat USA 2016)