Skip to content

tls_dtor_list hijack

glibc'nin thread-local tls_dtor_list'ini overwrite ederek thread/process exit'inde __call_tls_dtors'un, attacker-controlled bir function pointer'ı demangle edip attacker-controlled bir argument ile çağırmasını sağlamak.

Mechanism

Note

Destructor'ı olan C++11 thread_local object'leri, cleanup'larını __cxa_thread_atexit_impl (glibc stdlib/nptl, örn. cxa_thread_atexit_impl.c) üzerinden register eder. Her registration bir node allocate eder ve onu per-thread linked list tls_dtor_list'in başına ekler. Node şudur:

struct dtor_list {
    dtor_func func;          /* destructor, stored POINTER-MANGLED */
    void *obj;               /* argument passed to func            */
    struct link_map *map;    /* owning DSO                          */
    struct dtor_list *next;
};

Exit'te, exit()__run_exit_handlers(), tls_dtor_list != NULL olduğu sürece __call_tls_dtors()'u çağırır. List'i dolaşır ve her node için func'u demangle eder ve func(obj)'i invoke eder. Yani bir attacker bir dtor_list node'u yazabilirse (veya tls_dtor_list'i sahte bir node'a yöneltebilirse), exit call(controlled_func, controlled_obj)'i tetikler.

İşin püf noktası pointer guard'dır (PTR_MANGLE): func raw saklanmaz. Registration'da:

encoded = ROL(func ^ pointer_guard, 0x11)     /* PTR_MANGLE: XOR then rotate-left 17 */

ve call zamanında __call_tls_dtors bunu tersine çevirir:

func = ROR(encoded, 0x11) ^ pointer_guard     /* PTR_DEMANGLE: rotate-right 17 then XOR */

pointer_guard, TCB içinde fs:0x30'da yaşar. Çalışan bir func yerleştirmek için attacker'ın ROL(target ^ guard, 0x11) sağlaması gerekir, yani guard'ı yenmesi gerekir.

Walkthrough

İki malzeme: sahte bir dtor_list node'u ve doğru şekilde mangle edilmiş bir func yaz.

1. Pointer guard'ı kurtar / etkisizleştir. Guard per-thread'dir ve (aynı çalıştırma için) sabittir. Yaygın yenme yolları:

  • Bilinen mangle edilmiş bir pointer leak'le. Bilinen bir fonksiyona (örn. genellikle exit-handler düzeneğinde register edilen _dl_fini) ait bir pointer mangle edilmiş hâliyle leak edilebilirse, guard'ı cebirsel olarak kurtar:
/* mangled = ROL(known_func ^ guard, 0x11)  =>  guard = ROR(mangled, 0x11) ^ known_func */
guard = ROR(leaked_mangled, 0x11) ^ known_func_addr;
  • Guard'ı sıfırla. Bir arbitrary write fs:0x30'u (veya TCB kopyasını) 0'a ayarlayabilirse, mangling func = ROR(encoded, 0x11)'e çöker, yani saklanması gereken değer düz bir ROL(target, 0x11)'dir — secret gerekmez.

2. Node'u forge et ve exit'i ona pivot et.

struct dtor_list fake;
fake.func = (dtor_func) ROL( (uintptr_t)system ^ guard, 0x11 );  /* mangled */
fake.obj  = (void *) addr_of_binsh;   /* argument == first arg => system("/bin/sh") */
fake.map  = 0;
fake.next = 0;

/* arbitrary write: make tls_dtor_list point at &fake */
*tls_dtor_list_ptr = &fake;

exit(0);   /* __run_exit_handlers -> __call_tls_dtors -> system("/bin/sh") */
Exit'teki call sırası

exit__run_exit_handlers → (tls_dtor_list varsa) __call_tls_dtors → her node için: cur->func PTR_DEMANGLE edilir → cur->func(cur->obj). List, dolaşırken tüketilir (tls_dtor_list = cur->next), dolayısıyla next = NULL olan tek bir forge edilmiş node tam olarak bir kez ateşlenir ve temiz bir şekilde durur.

Warning

PTR_MANGLE shift değil, rotate kullanır: 64-bit bir değer üzerinde 0x11 (17) bit ROL/ROR — logical shift kullanmak yanlış bir mangle üretir ve call çöpe atlar. Ayrıca fs:0x30'daki guard, stack canary'den ve başka yerde kullanılan __pointer_chk_guard'tan bağımsızdır; doğru olanı leak/recover et. Statically-linked binary'ler tarihsel olarak zayıf/sıfır bir guard'a sahipti (CVE-2013-4788), bu da orada bunu zahmetsizce exploit edilebilir kılar.

Detection

  • Programın hiç register etmediği bir konumda heap/BSS içine işaret eden bir tls_dtor_list head'i veya func'u non-text bir adrese demangle olan bir node, kurcalamaya işaret eder.
  • RELRO + CFI build'leri ve abort-on-demangle-failure yüzeyi azaltır.

Mitigation

  • Pointer guard'ı randomize tut (static-binary zero-guard bug'ından kaçın; CVE-2013-4788).
  • tls_dtor_list'in / bir node'un forge edilmesini sağlayan arbitrary-write primitive'ini hardening'e tabi tut — bu teknik yalnızca bir finalizer'dır, kendisi bir memory-corruption bug'ı değildir.

References