Skip to content

__run_exit_handlers corruption

glibc'nin __exit_funcs listesini 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):

  1. Sahte bir exit_function_list forge et (tek bir entry yeterlidir): idx = 1, fns[0].flavor = 4 (ef_cxa).
  2. fns[0].func.cxa.fn'i hedefinin mangle edilmiş değerine ayarla. Mangling ROL(target ^ pointer_guard, 0x11)'dir; pointer guard leak'lenmişse veya sıfırlanmışsa fn = ROL(system, 0x11) ve arg = "/bin/sh" adresi.
  3. __exit_funcs'ı sahte listeye işaret edecek şekilde overwrite et, sonra programın exit() 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.fn pointer'ı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.

References