House of Muney¶
Bir mmap'd chunk'ın size'ını corrupt et ki
munmaplibc'nin bir kısmını unmap etsin, o bölgeyi taze birmmapile reclaim et ve dynamic-symbol table'larını (.gnu.hash/.dynsym/.dynstr) forge edip bir sonraki symbol resolution'ı hijack et — leakless ve deterministik olarak.
Mechanism¶
Suistimal edilen invariant
M_MMAP_THRESHOLD (varsayılan 0x20000 / 128 KB) üzerindeki allocation'lar main heap tarafından değil, doğrudan mmap tarafından servis edilir ve munmap_chunk → munmap ile free edilir. Unmap edilen miktar, orijinal mapping uzunluğuna karşı esasen hiçbir cross-check olmadan doğrudan chunk'ın kendi size field'ından alınır. Bir write primitive o size'ı büyütürse (IS_MMAPPED set tutulup prev_size + size page-hizalı kalarak), free() allocate edilenden daha büyük bir aralığı unmap eder — bitişik .gnu.hash, .dynsym ve .dynstr gibi read-only libc section'ları dahil.
Kernel taze unmap edilen düşük address'leri yeniden kullandığı için, aynı boyutta bir sonraki mmap deliğin tepesine geri iner ve attacker'a libc'nin symbol-resolution yapılarının eskiden bulunduğu yerde writable bellek verir. Technique leakless'tir: asla bir libc address'i okumaz. GNU-hash bloom filter'ını, bucket'ları ve chain'i artı st_value'su system/bir one-gadget'a işaret eden bir Elf64_Sym forge eder, böylece henüz çözülmemiş bir libc fonksiyonunun bir sonraki lazy resolution'ı attacker'ın seçtiği kodu döndürür. Tüm offset'ler page-relative'dir, dolayısıyla ASLR'den bağımsızdır.
Walkthrough¶
glibc 2.31'e karşı gösterildi (public PoC o sürümü hedefler; fikir mmap'd chunk'lar ve lazy resolution'ın bir arada bulunduğu her yerde genelleşir).
- Bir mmap'd chunk elde et. ≥
0x20000allocate et ki malloc libc mapping'lerinin bitişiğine yerleştirilmiş birmmapmapping döndürsün:
- libc ile overlap için size'ı corrupt et. Bir heap/overflow write primitive kullanarak chunk size'ını büyüt ki ileriye doğru libc'nin
.gnu.hash/.dynsym'ine uzansın. Yeni sizeIS_MMAPPEDbit'ini set tutmalı veprev_size + sizepage-hizalı kalmalı:
// new_size ~= bytes_to_libc + bytes_to_cover_dynsym, page aligned, IS_MMAPPED set
set_chunk_size(p, new_size | IS_MMAPPED);
- Free → libc bölgesini munmap et. Chunk'ı free etmek
munmap_chunk'ı çağırır ki bu şişirilmiş aralığımunmapeder, hedeflenen libc symbol table'larını unmap eder. O aralığa sonraki herhangi bir erişim fault verir — reclaim edilene kadar.
- Deliği reclaim et. Aynı boyutu tekrar iste; kernel az önce unmap edilen düşük address'i geri verir. Attacker artık symbol resolution'ı destekleyen bölgeye yazar.
- Dynamic-symbol yapılarını forge et. Target fonksiyonun lookup'ı başarılı olacak ve
st_valueyeniden yönlendirilmiş olacak kadarl_gnu_bitmask(bloom filter),l_gnu_buckets,l_gnu_chain_zerove birElf64_Symyeniden inşa et: - pre-filter geçsin diye bloom filter bit'leri set edilir;
((*hasharr ^ hash) >> 1) == 0üzerinden target'ın GNU hash'iyle eşleşen bucket/chain entry'leri;-
symbol'ün
st_value=system'in (veya bir gadget'ın) offset'i. -
Resolution'ı tetikle. Henüz çözülmemiş bir libc fonksiyonunu çağır (lazy binding sadece ilk çağrıda çözer). Corrupt edilen chain onu
system'e çözer ve leak olmadan code execution verir.
Footgun'lar
- Hijack edilen fonksiyon daha önce çağrılmamış olmalı — bir kere çözüldüğünde GOT doldurulur ve forge edilmiş table'lara asla danışılmaz.
- Offset'ler page-relative'dir; yanlışsalar genellikle tam page katları kadar sapmışlardır. Her şeyi
0x1000'e hizala. IS_MMAPPED'i set ve size'ı page-hizalı tutmalısın, yoksamunmap_chunkunmap'i reddeder/yanlış boyutlandırır.
Detection¶
- Size field'ı orijinal mapping uzunluğunu aşan bir mmap'd chunk.
- Önceden map'lenmiş bir libc bölgesinin tam üzerinde beliren ikinci bir mapping.
.dynsymentry'si artık writable, yeni-mmap'lenmiş bellekte bulunan bir fonksiyon için symbol resolution'ın başarılı olması.
Mitigation¶
- Full RELRO /
LD_BIND_NOW: eager binding tüm symbol'leri başlangıçta çözer, dolayısıyla hijack edilecek lazy resolution kalmaz. - Unmap uzunluğunu kaydedilmiş allocation'a karşı validate eden hardened/check'li bir
munmap_chunksize-inflation adımını yener.