Skip to content

House of Muney

Bir mmap'd chunk'ın size'ını corrupt et ki munmap libc'nin bir kısmını unmap etsin, o bölgeyi taze bir mmap ile 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_chunkmunmap 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).

  1. Bir mmap'd chunk elde et.0x20000 allocate et ki malloc libc mapping'lerinin bitişiğine yerleştirilmiş bir mmap mapping döndürsün:
void *p = malloc(0x21000); // served by mmap, near libc
  1. 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 size IS_MMAPPED bit'ini set tutmalı ve prev_size + size page-hizalı kalmalı:
// new_size ~= bytes_to_libc + bytes_to_cover_dynsym, page aligned, IS_MMAPPED set
set_chunk_size(p, new_size | IS_MMAPPED);
  1. Free → libc bölgesini munmap et. Chunk'ı free etmek munmap_chunk'ı çağırır ki bu şişirilmiş aralığı munmap eder, hedeflenen libc symbol table'larını unmap eder. O aralığa sonraki herhangi bir erişim fault verir — reclaim edilene kadar.
free(p); // munmap() removes .gnu.hash / .dynsym of libc
  1. 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.
void *q = malloc(0x21000); // remaps over the old libc symbol-table pages
  1. Dynamic-symbol yapılarını forge et. Target fonksiyonun lookup'ı başarılı olacak ve st_value yeniden yönlendirilmiş olacak kadar l_gnu_bitmask (bloom filter), l_gnu_buckets, l_gnu_chain_zero ve bir Elf64_Sym yeniden inşa et:
  2. pre-filter geçsin diye bloom filter bit'leri set edilir;
  3. ((*hasharr ^ hash) >> 1) == 0 üzerinden target'ın GNU hash'iyle eşleşen bucket/chain entry'leri;
  4. symbol'ün st_value = system'in (veya bir gadget'ın) offset'i.

  5. 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, yoksa munmap_chunk unmap'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.
  • .dynsym entry'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_chunk size-inflation adımını yener.

References