Skip to content

__run_exit_handlers abuse

glibc'nin exit-handler listesinde bir entry forge et ki exit() demangle edip system("/bin/sh") çağırsın — kaldırılan malloc/free hook'larının modern alternatifi.

Mechanism

Note

exit(), __run_exit_handlers'ı çağırır; bu da __exit_funcs linked list'ini (başlangıçta statik initial block'una işaret eder) dolaşır ve kayıtlı her handler'ı çağırır. İlgili struct'lar:

struct exit_function_list {
    struct exit_function_list *next;
    size_t idx;
    struct exit_function fns[32];
};
struct exit_function {
    long int flavor;            // ef_free=0, ef_cxa=4, ...
    union { void (*at)(void);
            struct { void (*fn)(int,void*); void *arg; } on;
            struct { void (*fn)(void*,int); void *arg; void *dso; } cxa;
    } func;
};

Hedef flavor = ef_cxa (4): çağrı cxa.fn(cxa.arg, status) haline gelir. fn = system, arg = "/bin/sh" ayarla.

Warning

Saklanan fn, PTR_MANGLE ile korunur. Çağırmadan önce glibc şunu yapar: ror rax, 0x11 ; xor rax, fs:0x30 ; call rax — yani demangle(p) = ror(p, 0x11) ^ cookie; burada cookie, TCB içinde fs:0x30'da duran pointer_guard'dır. Geçerli bir entry forge etmek için bu guard'ı sağlaman gerekir.

Bu modern path'tir: __malloc_hook / __free_hook'a (glibc 2.34'te kaldırıldı) bağlı değildir, dolayısıyla glibc 2.35–2.39 boyunca çalışır.

Walkthrough

Pointer guard'ı sağlamanın iki yolu:

(a) Cookie'yi leak et. fns[0].fn genellikle _dl_fini'yi mangle eder (bilinen offset). Cookie'yi bilinen plaintext'ten kurtar:

uint64_t cookie = ror(stored_fn_enc, 0x11) ^ (uint64_t)&_dl_fini;
// mangle(p) = rol(p ^ cookie, 0x11)
exit_funcs->fns[0].flavor       = 4;                       // ef_cxa
exit_funcs->fns[0].func.cxa.fn  = (void*)mangle(system, cookie);
exit_funcs->fns[0].func.cxa.arg = "/bin/sh";
exit(0);                                                   // -> system("/bin/sh")

(b) Cookie'yi sıfırla. fs:0x30'u (__pointer_chk_guard) 0x00 ile overwrite et. Demangle saf bir rotate'e indirgenir, dolayısıyla XOR olmadan sadece rol(system, 0x11)'i sakla.

Beklenen davranış: main'den return etmek veya exit() çağırmak __exit_funcs'ı iter eder, forge edilmiş pointer'ı system'e geri demangle eder ve onu "/bin/sh" ile çağırır — hiçbir malloc hook'u devrede olmadan bir shell.

Rotate miktarı ve guard konumu

x86-64'te rotate 0x11 (17)'dir ve cookie fs:0x30'da (__pointer_chk_guard) durur. TLS-dtor listesi (tls_dtor_list, exit handler'lardan önce __call_tls_dtors() tarafından çalıştırılır) aynı guard gereksinimine sahip paralel bir hedeftir ve genellikle doğrudan yazması daha kolaydır — bkz. tls-dtor-list-hijack.

Mitigation

  • PTR_MANGLE birincil bariyerdir; bir cookie leak'ini veya TCB guard'ına bir yazmayı zorunlu kılar.
  • Bazı hardened libc varyantları daha az handler kaydeder veya __exit_funcs'ı taşır, bu da statik initial-block varsayımını zorlaştırır.
  • Yine de libc'ye bir arbitrary write artı bir address leak gerektirir.

References