House of Water¶
Heap üzerindeki
tcache_perthread_struct'ın üstünde sahte bir chunk forge edip onu geri al; böylece — safe-linking ile korunmayan — tüm tcache count'ları/entry'leri üzerinde doğrudan kontrol elde et ve arbitrary allocation'a leakless bir yol aç.
Mechanism¶
Invariant: tcache metadata'sı heap'te yaşar ve safe-linking ile korunmaz
glibc, per-thread tcache defterini libc'de değil heap'te bulunan bir
tcache_perthread_struct'ta saklar (arena'daki ilk allocation olan bir
0x290 chunk):
typedef struct tcache_perthread_struct {
uint16_t counts[TCACHE_MAX_BINS]; // 64 bin counts
tcache_entry *entries[TCACHE_MAX_BINS]; // 64 head pointers
} tcache_perthread_struct;
İki özellik bunu House of Water'ın (udp / Blue Water, PotluckCTF 2023 "Tamagoyaki") kalbi yapar:
-
Safe-linking struct'ı kapsamaz. Safe-linking, freelist'teki bir chunk'ın içindeki
fdlink'ini XOR-obfuscate eder (e->next = ptr ^ (chunk_addr >> 12)), amatcache_perthread_structiçindekientries[]pointer'ları raw (çıplak) saklanır. Bu struct'ı kontrol eden, her tcache bin head'ini düz pointer'larla kontrol eder — onları forge etmek için heap leak gerekmez. -
counts/entries sınırı bir chunk header gibi görünecek hale getirilebilir. Seçilen bin'lere chunk free etmek belirli
counts[]değerleri yazar; ör. bir0x3e0ve bir0x3f0chunk free etmekcounts'un sonunda0x0001_0001bırakır, bu sırada bitişikentries[]pointer'larıfd/bksağlar. Doğru offset'te birlikte okunduğunda bu, tcache struct'ı ile çakışan,PREV_INUSEset'li~0x10000boyutunda bir sahte unsorted-bin chunk header'ı sentezler.
Attacker bu sahte chunk'ı, bir bin pointer'ının partial (LSB / 2-byte)
overwrite'ı aracılığıyla unsorted bin'e link'ler — yalnızca düşük bitler
değişir, dolayısıyla sadece birkaç ASLR biti tahmin edilmeli (challenge bunu
deterministik yapmak için ~8 bit leak eder). Eşleşen boyuttaki bir sonraki
malloc sahte chunk'ı, yani tüm tcache_perthread_struct'ı döndürür.
Oradan attacker tcache bin'lerini stdout/libc'ye işaret ettirmek için raw
entries[] yazar, leak edip pivot eder (genellikle House of Apple
2 / FSOP'a). Tam da safe-linking'i atlattığı için glibc
2.32–2.39+ üzerinde etkilidir.
Walkthrough¶
Bu, mimari-biçimli (architecture-shaped) bir tekniktir (tam chunk size'ları
target'ın tcache_perthread_struct layout'una bağlıdır). Aşağıdaki fazlar
corgi.rip ve Axura writeup'larını takip eder.
Phase 1 — forge the fake header over tcache metadata
* malloc/free chunks of sizes (e.g. 0x3e0, 0x3f0, ...) so the written
counts[] + entries[] bytes read as: size=0x10000 | PREV_INUSE, sane fd/bk.
Phase 2 — get a chunk into the unsorted bin overlapping the struct
* Fill the relevant tcache bin (7 frees) so a further free spills to the
unsorted bin, placing a real unsorted chunk adjacent to the fake header.
Phase 3 — LSB / 2-byte partial overwrite
* Overwrite only the low 2 bytes of an unsorted bin fd/bk so it links the
fake (tcache-struct) chunk. ~4 bits of bruteforce per write; the program's
"tiny leak" (8 bits of ASLR) makes this reliable.
Phase 4 — reclaim and own tcache
* malloc() of the fake chunk's size returns the tcache_perthread_struct.
Now write raw entries[] (no safe-linking!) to point a bin at &stdout / libc.
Phase 5 — leak + RCE
* Allocate over stdout, corrupt _IO_FILE buffering fields to leak libc,
then file-stream-oriented programming (House of Apple 2) -> control flow.
Footgun'lar
- Layout hassasiyeti. Sahte header
counts[]değerlerinden sentezlenir;0x0001_0001'i (ve aklı başında birfd/bk'yı) üretmek için free ettiğin tam size'lar senin glibc'in içinTCACHE_MAX_BINS'e ve struct offset'lerine bağlıdır. gdb'de gerçektcache_perthread_struct'a karşı doğrula. - Partial overwrite ASLR bitleri. 1-byte overwrite deterministiktir ama fazla kaba; 2-byte overwrite ~4 bit tahmin gerektirir. Teknik, bruteforce'u önlemek için küçük (≈8-bit) bir leak ile eşleşir; dolayısıyla "leakless" sıfır bilgiyi değil, libc/heap pointer leak'inin olmamasını ifade eder.
- Entry'ler raw, chunk-içi fd değil.
entries[]'a yazdığın değerleri safe-link etme — onlar doğrudan pointer olarak tüketilir. Obfuscation yalnızca bir chunk free edildiğinde onun kendi in-bandfd'sine uygulanır.
tcache struct overlap in gdb
pwndbg> p *(struct tcache_perthread_struct*)(heap_base+0x10)
counts = {..., [0x3e]=1, [0x3f]=1, ...} # 0x0001_0001 at the boundary
entries = {..., 0x...560, 0x...570, ...} # raw, unprotected head ptrs
pwndbg> x/4gx heap_base+<fake_hdr_off>
0x...: 0x0000000000010001 0x0000... (fd) # reads as size|PREV_INUSE
Detection¶
- En ilk arena chunk'ına (
tcache_perthread_structbölgesine) link'lenen bir unsorted-binfd/bk'sı anormaldir. - Heap chunk'ları yerine libc yapılarına (
_IO_2_1_stdout_) işaret eden tcacheentries[]head'leri, struct'ın post-exploitation kontrolüne işaret eder.
Mitigation¶
- Yapısal düzeltme
tcache_perthread_structmetadata'sını korumak olurdu (ör.entries[]üzerinde guard/encoding); upstream glibc onu hâlâ raw saklar, dolayısıyla tek başına safe-linking bunu durdurmaz. - FSOP hardening'i (
_IO_FILEvtable doğrulaması,_IO_obstack/wide_datakontrolleri) Phase 5 pivot'unun maliyetini yükseltir. - Out-of-line / guarded allocator metadata'sı, struct üstünde sahte bir header forge etmeyi etkisizleştirir.