mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-26 06:30:37 +00:00
239 lines
12 KiB
Markdown
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. 
|
|
* [**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).
|