__run_exit_handlers corruption¶
glibc'nin
__exit_funcslistesini forge et ya da overwrite et ki normal program exit'i attacker'ın kontrol ettiği (mangle edilmiş) bir function pointer'ı çağırsın.
Mechanism¶
Note
glibc, process-exit destructor'larını (atexit, __cxa_atexit, DSO finalizer'ları)
head'i global __exit_funcs sembolü olan, singly-linked bir struct
exit_function_list block listesine kaydeder. Normal sonlanmada exit(),
__run_exit_handlers()'ı çağırır (ayrıca dlclose üzerinde __cxa_finalize
yoluyla da ulaşılır); bu da bu listeyi dolaşır. ef_cxa flavor'lı her
exit_function için saklanan function pointer'ı okur, ona PTR_DEMANGLE uygular
ve onu func.cxa.arg ile çağırır. Attacker'ın kırdığı invariant: list head'i
ve entry'ler yazılabilir libc data'sıdır ve pointer üzerindeki tek integrity
koruması pointer-guard mangling'idir (PTR_DEMANGLE = 17 bit sağa rotate (ROR),
ardından fs:0x30'daki TLS cookie ile XOR). __exit_funcs'ı (veya bir entry'yi) kontrol et, exit'te bir çağrıyı
kontrol edersin.
Walkthrough¶
İlgili glibc yapıları (stdlib/exit.h):
struct exit_function {
long int flavor; /* ef_free=0, ef_us=1, ef_on=2, ef_at=3, ef_cxa=4 */
union {
void (*at)(void);
struct { void (*fn)(int, void*); void *arg; } on;
struct { void (*fn)(void*, int); void *arg; void *dso_handle; } cxa;
} func;
};
struct exit_function_list {
struct exit_function_list *next;
size_t idx;
struct exit_function fns[32];
};
__run_exit_handlers içinde ef_cxa branch'i esasen şudur:
case ef_cxa:
/* glibc demangles before the call */
cxafct = f->func.cxa.fn;
PTR_DEMANGLE (cxafct);
cxafct (f->func.cxa.arg, status);
break;
Bir arbitrary write ile saldır (örneğin libc-leak'li, tcache-poison'lı bir chunk):
- Sahte bir
exit_function_listforge et (tek bir entry yeterlidir):idx = 1,fns[0].flavor = 4(ef_cxa). fns[0].func.cxa.fn'i hedefinin mangle edilmiş değerine ayarla. ManglingROL(target ^ pointer_guard, 0x11)'dir; pointer guard leak'lenmişse veya sıfırlanmışsafn = ROL(system, 0x11)vearg = "/bin/sh" adresi.__exit_funcs'ı sahte listeye işaret edecek şekilde overwrite et, sonra programınexit()etmesine izin ver.
Warning
Pointer guard olmadan geçerli bir mangle edilmiş pointer forge edemezsin; modern
zincirler ya fs:0x30'u leak'ler, ya guard'ı sıfırlar, ya da mangling'e karşı
dayanıklı bir hedefe pivot eder (örneğin system şeklindeki değerlerin hayatta
kaldığı eski __run_exit_handlers okumaları). Tam layout'un ayrıntılı bir
örneği nobodyisnobody writeup'ında var.
Detection¶
- Exit'te kayıtlı DSO finalizer kümesi dışındaki adreslere yapılan indirect call'lar;
bir CFI/forward-edge denetleyicisi (örneğin return'lerde SafeStack/CET shadow-stack)
bu forward call'u durdurmaz ama
__run_exit_handlersüzerinde allow-list CFI durdurur. - Öncül write primitive'ini yakalayan heap canary'leri / hardened malloc'lar.
Mitigation¶
- glibc'nin
cxa.fnpointer'ına uyguladığıPTR_MANGLE/PTR_DEMANGLE, yerinde savunmadır; bir pointer-guard leak'ini zorunlu kılar. - Yeni glibc, bazı konfigürasyonlarda (
PROTECT_*) exit handler listesini read-only-after-init korumasına taşıdı ve full-RELRO komşu overwrite hedeflerini ortadan kaldırır.