Tcache Metadata Hijacking¶
tcache_perthread_structkontrol yapısını —counts[]veentries[]dizilerini ya da işaret ettikleri chunk-içie->nexthead'lerini — corrupt ederek, allocator'ın kendi metadata'sının sonrakimalloc()'u attacker'ın seçtiği bir adrese yönlendirmesini sağlamak.
Mechanism¶
Suistimal edilen invariant: allocator per-thread defter-tutma struct'ına koşulsuz güvenir
glibc, her thread'in tcache state'ini, thread'in arena'sındaki ilk chunk olarak yaşayan tek bir heap-yerleşik kontrol yapısında tutar:
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS]; /* TCACHE_MAX_BINS == 64 */
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
İki alan her fast-path allocation'ı kapılar: counts[i]'nin bin i'deki
hazır chunk'ların sayısı olduğuna inanılır ve entries[i]'nin o bin'in
singly linked free list'inin head'i olduğuna inanılır. tcache_get(),
entries[i]'yi pop edip counts[i]'yi azaltmadan önce yalnızca minimal bir
tc_idx aralık kontrolü yapar; bu değerleri herhangi bir güvenilir kaynaktan
yeniden türetmez.
Metadata hijacking, tek bir free edilmiş chunk yerine bu YAPIYI hedefler.
Sade tcache-poisoning, bir free edilmiş chunk'ın next link'ini forge
eder ki ikinci allocation yönlendirilsin. Metadata hijacking ise bunun
yerine kontrol struct'ının entries[] head'lerini ve counts[]'unu doğrudan
yeniden yazar — list head'leri ham olarak saklanır (yalnızca chunk-içi
next link'leri Safe-Linking'in PROTECT_PTR'ı ile mangle edilir),
dolayısıyla entries[i]'yi overwrite etmek, o size class'ın sonraki
pop'unu herhangi bir adrese, attacker'ın seçtiği kadar çok size class için
aynı anda yöneltir. counts[i]'yi artırmak, allocator'ı bir bin'in boş
olmadığına ikna eder ki daha yavaş, daha iyi kontrol edilen koda düşmek
yerine tcache yolunu alsın.
İlişki: tcache-metadata-poisoning ile aynı struct, farklı çerçeve
Bu not ile tcache-metadata-poisoning, aynı
kök tekniği — tcache_perthread_struct'ın entries[]/counts[] alanlarının
struct-seviyesi corruption'ı — kapsar; ayrı bir zafiyet sınıfı değildir.
Poisoning notu tek bir hedeflenmiş entries[] head overwrite'ına (bir
sonraki malloc'u tek bir adrese yöneltmek) odaklanır; bu hijacking notu
ise aynı primitive'in birden fazla bin'i aynı anda ele geçirip malloc'u
genel bir arbitrary-address allocator'a dönüştüren genelleştirilmiş biçimini
vurgular. İkisini birlikte oku.
Walkthrough¶
Üst düzey yeniden üretim; herkese açık CTF-Wiki tcache materyalini takip eder.
tcache_perthread_struct heap'teki ilk chunk'tır — struct'ın kendisi
64*2 + 64*8 = 0x280 byte'tır, onu tutan chunk ise SIZE_SZ header +
16-byte alignment (request2size) ile 64-bit'te 0x290'a yuvarlanır —
dolayısıyla herhangi bir erken UAF, lineer overflow ya da struct'a bir pointer
döndüren bir poisoning, metadata üzerinde bir write handle verir.
// 1. Obtain a write handle to the control struct (conceptual):
// e.g. reclaim the very first allocation, or poison a freed 0x290
// chunk's next to return the heap base.
uint16_t *counts = (uint16_t*)tcache_struct; /* counts[64] */
void **entries = (void**)(tcache_struct + 0x80); /* entries[64] at offset 0x80 */
// 2. Forge a bin: claim one ready chunk and set its head.
counts[idx] = 1; // pretend bin `idx` is non-empty
entries[idx] = (void*)target; // head -> arbitrary address (UNMANGLED)
// 3. Allocate: tcache_get pops `target` directly.
void *p = malloc(size_for(idx)); // returns `target`
Yönlendirilen pointer, o ilk pop için hiçbir heap leak gerektirmeden döndürülür;
çünkü entries[]'deki list head'i PROTECT_PTR'dan geçirilmez.
Neden global bir primitive'e genelleşir
Birkaç i için counts[i]/entries[i]'yi ayarlamak, kontrol struct'ının
tek bir corruption'ının her biri seçilmiş bir adreste olmak üzere birçok
farklı malloc(size) isteğini servis etmesine olanak tanır — malloc'u tek
seferlik bir yönlendirme yerine etkili biçimde bir arbitrary-address
allocator'a dönüştürür. Bu, sade poisoning'e göre kavramsal üstünlüktür ve
devam eden bir arbitrary-write primitive ile
doğal olarak eşleşir.
Kalıntı kontroller hâlâ geçerli
tcache_get(), pop edilen head üzerinde aligned_OK'i zorlar; hizasız bir
forge edilmiş adres malloc(): unaligned tcache chunk detected çıkarır.
Forge edilmiş head'ler 16-byte aligned olmalı ve sonraki glibc'de eklenen
tcache key/counts tutarlılık kontrolleri beceriksiz corruption'ın maliyetini
yükseltir.
Detection¶
- Bilinen herhangi bir eşlenmiş heap aralığı dışında pointer döndüren heap
allocator'lar ya da aynı adresi alias'layan iki canlı allocation,
ASan/heap-debug araçlarında (
AddressSanitizer, glibcMALLOC_CHECK_, electric-fence) ortaya çıkar. - Process log'larında/core'larında abort string'leri:
malloc(): unaligned tcache chunk detected,free(): invalid pointerya da tcache-key abort mesajları runtime'da metadata kurcalamasına işaret eder. - EDR/telemetri:
.bss, GOT,stackya da__free_hook/__malloc_hookregion'ına düşen ve hemen ardından üzerinden bir write gelen birmalloc()türevli pointer güçlü bir anomalidir. Toplanan backtrace'lerdekitcache_get/_int_mallociçindeki çökmeleri ilişkilendir. - Fuzzing/CI: hedefleri ASan altında çalıştır; ilk heap chunk'ına ulaşan UAF ve overflow, bir primitive haline gelmeden önce güvenilir biçimde yakalanır.
Mitigation¶
- Safe-Linking (glibc ≥ 2.32) chunk-içi
nextpointer'larını mangle eder ama kontrol struct'ındakientries[]head'lerini korumaz — savunmacılar onun bu yolu durdurduğunu varsaymamalıdır. aligned_OKve tcache key alanı (glibc ≥ 2.29) bir sınıf forge-edilmiş/double-free edilmiş entry'yi yakalar; glibc'yi güncel tut.-D_FORTIFY_SOURCE=2ile derle, testte ASan/MTE'yi etkinleştir ve kök neden UAF/overflow'u düzelt — metadata struct'ına yalnızca onun üzerinden ulaşılır.- Allocator metadata'sını izole eden heap hardening (guard page'ler, hardened allocator'lardaki gibi out-of-line metadata) maliyeti yükseltir; upstream glibc perthread struct'ı izole etmez.