# WWW2Exec - atexit(), Almacenamiento TLS y Otros Punteros Dañados {{#include ../../banners/hacktricks-training.md}} ## **\_\_atexit Estructuras** > [!CAUTION] > Hoy en día es muy **raro explotar esto!** **`atexit()`** es una función a la que se le **pasan otras funciones como parámetros.** Estas **funciones** serán **ejecutadas** al ejecutar un **`exit()`** o el **retorno** del **main**.\ Si puedes **modificar** la **dirección** de cualquiera de estas **funciones** para que apunte a un shellcode, por ejemplo, **ganarás control** del **proceso**, pero esto es actualmente más complicado.\ Actualmente, las **direcciones a las funciones** que se van a ejecutar están **ocultas** detrás de varias estructuras y, finalmente, la dirección a la que apuntan no son las direcciones de las funciones, sino que están **encriptadas con XOR** y desplazamientos con una **clave aleatoria**. Así que actualmente este vector de ataque **no es muy útil al menos en x86** y **x64_86**.\ La **función de encriptación** es **`PTR_MANGLE`**. **Otras arquitecturas** como m68k, mips32, mips64, aarch64, arm, hppa... **no implementan la función de encriptación** porque **devuelve lo mismo** que recibió como entrada. Así que estas arquitecturas serían atacables por este vector. Puedes encontrar una explicación detallada sobre cómo funciona esto en [https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html](https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html) ## link_map Como se explicó [**en esta publicación**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link_map-structure), si el programa sale usando `return` o `exit()`, ejecutará `__run_exit_handlers()` que llamará a los destructores registrados. > [!CAUTION] > Si el programa sale a través de la función **`_exit()`**, llamará a la **`exit` syscall** y los manejadores de salida no se ejecutarán. Así que, para confirmar que `__run_exit_handlers()` se ejecuta, puedes establecer un punto de interrupción en él. El código importante es ([source](https://elixir.bootlin.com/glibc/glibc-2.32/source/elf/dl-fini.c#L131)): ```c ElfW(Dyn) *fini_array = map->l_info[DT_FINI_ARRAY]; if (fini_array != NULL) { ElfW(Addr) *array = (ElfW(Addr) *) (map->l_addr + fini_array->d_un.d_ptr); size_t sz = (map->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (sz-- > 0) ((fini_t) array[sz]) (); } [...] // This is the d_un structure ptype l->l_info[DT_FINI_ARRAY]->d_un type = union { Elf64_Xword d_val; // address of function that will be called, we put our onegadget here Elf64_Addr d_ptr; // offset from l->l_addr of our structure } ``` Nota cómo `map -> l_addr + fini_array -> d_un.d_ptr` se utiliza para **calcular** la posición del **array de funciones a llamar**. Hay un **par de opciones**: - Sobrescribir el valor de `map->l_addr` para que apunte a un **falso `fini_array`** con instrucciones para ejecutar código arbitrario. - Sobrescribir las entradas `l_info[DT_FINI_ARRAY]` y `l_info[DT_FINI_ARRAYSZ]` (que son más o menos consecutivas en memoria), para hacer que **apunten a una estructura `Elf64_Dyn` forjada** que hará que nuevamente **`array` apunte a una zona de memoria** controlada por el atacante. - [**Este informe**](https://github.com/nobodyisnobody/write-ups/tree/main/DanteCTF.2023/pwn/Sentence.To.Hell) sobrescribe `l_info[DT_FINI_ARRAY]` con la dirección de una memoria controlada en `.bss` que contiene un falso `fini_array`. Este array falso contiene **primero un** [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) **dirección** que se ejecutará y luego la **diferencia** entre la dirección de este **array falso** y el **valor de `map->l_addr`** para que `*array` apunte al array falso. - Según la publicación principal de esta técnica y [**este informe**](https://activities.tjhsst.edu/csc/writeups/angstromctf-2021-wallstreet), ld.so deja un puntero en la pila que apunta al `link_map` binario en ld.so. Con una escritura arbitraria es posible sobrescribirlo y hacer que apunte a un falso `fini_array` controlado por el atacante con la dirección a un [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) por ejemplo. Siguiendo el código anterior, puedes encontrar otra sección interesante con el código: ```c /* Next try the old-style destructor. */ ElfW(Dyn) *fini = map->l_info[DT_FINI]; if (fini != NULL) DL_CALL_DT_FINI (map, ((void *) map->l_addr + fini->d_un.d_ptr)); } ``` En este caso, sería posible sobrescribir el valor de `map->l_info[DT_FINI]` apuntando a una estructura `ElfW(Dyn)` forjada. Encuentra [**más información aquí**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link_map-structure). ## Sobrescritura de dtor_list de TLS-Storage en **`__run_exit_handlers`** Como [**se explica aquí**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite), si un programa sale a través de `return` o `exit()`, ejecutará **`__run_exit_handlers()`** que llamará a cualquier función destructora registrada. Código de `_run_exit_handlers()`: ```c /* Call all functions registered with `atexit' and `on_exit', in the reverse of the order in which they were registered perform stdio cleanup, and terminate program execution with STATUS. */ void attribute_hidden __run_exit_handlers (int status, struct exit_function_list **listp, bool run_list_atexit, bool run_dtors) { /* First, call the TLS destructors. */ #ifndef SHARED if (&__call_tls_dtors != NULL) #endif if (run_dtors) __call_tls_dtors (); ``` Código de **`__call_tls_dtors()`**: ```c typedef void (*dtor_func) (void *); struct dtor_list //struct added { dtor_func func; void *obj; struct link_map *map; struct dtor_list *next; }; [...] /* Call the destructors. This is called either when a thread returns from the initial function or when the process exits via the exit function. */ void __call_tls_dtors (void) { while (tls_dtor_list) // parse the dtor_list chained structures { struct dtor_list *cur = tls_dtor_list; // cur point to tls-storage dtor_list dtor_func func = cur->func; PTR_DEMANGLE (func); // demangle the function ptr tls_dtor_list = tls_dtor_list->next; // next dtor_list structure func (cur->obj); [...] } } ``` Para cada función registrada en **`tls_dtor_list`**, se descompone el puntero de **`cur->func`** y se llama con el argumento **`cur->obj`**. Usando la función **`tls`** de este [**fork de GEF**](https://github.com/bata24/gef), es posible ver que en realidad la **`dtor_list`** está muy **cerca** del **stack canary** y la **cookie PTR_MANGLE**. Así, con un desbordamiento en ella sería posible **sobrescribir** la **cookie** y el **stack canary**.\ Sobrescribiendo la cookie PTR_MANGLE, sería posible **eludir la función `PTR_DEMANLE`** configurándola a 0x00, lo que significará que el **`xor`** utilizado para obtener la dirección real es solo la dirección configurada. Luego, al escribir en la **`dtor_list`** es posible **encadenar varias funciones** con la **dirección** de la función y su **argumento**. Finalmente, nota que el puntero almacenado no solo se va a xorear con la cookie, sino que también se rotará 17 bits: ```armasm 0x00007fc390444dd4 <+36>: mov rax,QWORD PTR [rbx] --> mangled ptr 0x00007fc390444dd7 <+39>: ror rax,0x11 --> rotate of 17 bits 0x00007fc390444ddb <+43>: xor rax,QWORD PTR fs:0x30 --> xor with PTR_MANGLE ``` Así que necesitas tener esto en cuenta antes de agregar una nueva dirección. Encuentra un ejemplo en el [**post original**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite). ## Otros punteros dañados en **`__run_exit_handlers`** Esta técnica está [**explicada aquí**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite) y depende nuevamente de que el programa **salga llamando a `return` o `exit()`** para que se llame a **`__run_exit_handlers()`**. Veamos más código de esta función: ```c while (true) { struct exit_function_list *cur; restart: cur = *listp; if (cur == NULL) { /* Exit processing complete. We will not allow any more atexit/on_exit registrations. */ __exit_funcs_done = true; break; } while (cur->idx > 0) { struct exit_function *const f = &cur->fns[--cur->idx]; const uint64_t new_exitfn_called = __new_exitfn_called; switch (f->flavor) { void (*atfct) (void); void (*onfct) (int status, void *arg); void (*cxafct) (void *arg, int status); void *arg; case ef_free: case ef_us: break; case ef_on: onfct = f->func.on.fn; arg = f->func.on.arg; PTR_DEMANGLE (onfct); /* Unlock the list while we call a foreign function. */ __libc_lock_unlock (__exit_funcs_lock); onfct (status, arg); __libc_lock_lock (__exit_funcs_lock); break; case ef_at: atfct = f->func.at; PTR_DEMANGLE (atfct); /* Unlock the list while we call a foreign function. */ __libc_lock_unlock (__exit_funcs_lock); atfct (); __libc_lock_lock (__exit_funcs_lock); break; case ef_cxa: /* To avoid dlclose/exit race calling cxafct twice (BZ 22180), we must mark this function as ef_free. */ f->flavor = ef_free; cxafct = f->func.cxa.fn; arg = f->func.cxa.arg; PTR_DEMANGLE (cxafct); /* Unlock the list while we call a foreign function. */ __libc_lock_unlock (__exit_funcs_lock); cxafct (arg, status); __libc_lock_lock (__exit_funcs_lock); break; } if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called)) /* The last exit function, or another thread, has registered more exit functions. Start the loop over. */ goto restart; } *listp = cur->next; if (*listp != NULL) /* Don't free the last element in the chain, this is the statically allocate element. */ free (cur); } __libc_lock_unlock (__exit_funcs_lock); ``` La variable `f` apunta a la **`estructura initial`** y dependiendo del valor de `f->flavor` se llamarán diferentes funciones.\ Dependiendo del valor, la dirección de la función a llamar estará en un lugar diferente, pero siempre estará **demangled**. Además, en las opciones **`ef_on`** y **`ef_cxa`** también es posible controlar un **argumento**. Es posible verificar la **`estructura initial`** en una sesión de depuración con GEF ejecutando **`gef> p initial`**. Para abusar de esto, necesitas **leak o borrar la cookie `PTR_MANGLE`** y luego sobrescribir una entrada `cxa` en initial con `system('/bin/sh')`.\ Puedes encontrar un ejemplo de esto en el [**blog original sobre la técnica**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#6---code-execution-via-other-mangled-pointers-in-initial-structure). {{#include ../../banners/hacktricks-training.md}}