hacktricks/binary-exploitation/arbitrary-write-2-exec/www2exec-atexit.md

239 lines
12 KiB
Markdown

# WWW2Exec - atexit(), Almacenamiento TLS y Otros Punteros Manipulados
{% hint style="success" %}
Aprende y practica Hacking en AWS: <img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
Aprende y practica Hacking en GCP: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
<details>
<summary>Apoya a HackTricks</summary>
* Revisa los [**planes de suscripción**](https://github.com/sponsors/carlospolop)!
* **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **síguenos** en **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Comparte trucos de hacking enviando PRs a los** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositorios de github.
</details>
{% endhint %}
## **Estructuras \_\_atexit**
{% hint style="danger" %}
Hoy en día es muy **raro explotar esto!**
{% endhint %}
**`atexit()`** es una función a la que se le pasan **otras funciones como parámetros.** Estas **funciones** se **ejecutarán** 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, **obtendrás control** del **proceso**, pero actualmente esto es más complicado.\
Actualmente las **direcciones de las funciones** 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**. Por lo tanto, 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. Por lo tanto, estas arquitecturas serían atacables por este vector.
Puedes encontrar una explicación detallada de 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 explica [**en este post**](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()` se ejecutará `__run_exit_handlers()` que llamará a los destructores registrados.
{% hint style="danger" %}
Si el programa sale a través de la función **`_exit()`**, llamará a la **llamada al sistema `exit`** y los manejadores de salida no se ejecutarán. Por lo tanto, para confirmar que se ejecuta `__run_exit_handlers()` puedes establecer un punto de interrupción en él.
{% endhint %}
El código importante es ([fuente](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
}
```
Observa 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 **`fini_array` falso** 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 que apunten a una estructura `Elf64_Dyn` falsificada que hará que nuevamente **`array` apunte a una zona de memoria** controlada por el atacante.&#x20;
* [**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 `fini_array` falso. Este array falso contiene **primero una** [**dirección de one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) 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 `fini_array` falso controlado por el atacante con la dirección de 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)` falsificada. 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 almacenamiento TLS 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 desmagnetizará el puntero de **`cur->func`** y se llamará con el argumento **`cur->obj`**.
Utilizando la función **`tls`** de este [**fork de GEF**](https://github.com/bata24/gef), es posible ver que en realidad la lista **`dtor_list`** está muy **cerca** del **canario de pila** y la **cookie PTR\_MANGLE**. Por lo tanto, con un desbordamiento en ella sería posible **sobrescribir** la **cookie** y el **canario de pila**.\
Al sobrescribir la cookie PTR\_MANGLE, sería posible **burlar la función `PTR_DEMANLE`** estableciéndola en 0x00, lo que significaría que el **`xor`** utilizado para obtener la dirección real es simplemente la dirección configurada. Luego, escribiendo en la lista **`dtor_list`** es posible **encadenar varias funciones** con la dirección de la función y su **argumento.**
Finalmente, hay que tener en cuenta que el puntero almacenado no solo se xorará 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
```
Por lo tanto, 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 manipulados en **`__run_exit_handlers`**
Esta técnica 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) 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.\
Según el valor, la dirección de la función a llamar estará en un lugar diferente, pero siempre estará **desenmascarada**.
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 **filtrar 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 [**post original del blog 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).