mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/libc-heap/unsorted-bin-attack.md']
This commit is contained in:
parent
a4c2165443
commit
327334a773
@ -2,54 +2,111 @@
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## Basic Information
|
||||
## Información básica
|
||||
|
||||
For more information about what is an unsorted bin check this page:
|
||||
|
||||
Para más información sobre qué es un unsorted bin, consulta esta página:
|
||||
|
||||
{{#ref}}
|
||||
bins-and-memory-allocations.md
|
||||
{{#endref}}
|
||||
|
||||
Las listas no ordenadas pueden escribir la dirección en `unsorted_chunks (av)` en la dirección `bk` del chunk. Por lo tanto, si un atacante puede **modificar la dirección del puntero `bk`** en un chunk dentro del unsorted bin, podría **escribir esa dirección en una dirección arbitraria**, lo que podría ser útil para filtrar direcciones de Glibc o eludir alguna defensa.
|
||||
Unsorted lists are able to write the address to `unsorted_chunks (av)` in the `bk` address of the chunk. Therefore, if an attacker can **modify the address of the `bk` pointer** in a chunk inside the unsorted bin, he could be able to **write that address in an arbitrary address** which could be helpful to leak a Glibc addresses or bypass some defense.
|
||||
|
||||
Así que, básicamente, este ataque permite **establecer un número grande en una dirección arbitraria**. Este número grande es una dirección, que podría ser una dirección de heap o una dirección de Glibc. Un objetivo típico es **`global_max_fast`** para permitir crear bins de fast bin con tamaños más grandes (y pasar de un ataque de unsorted bin a un ataque de fast bin).
|
||||
So, basically, this attack allows to **set a big number at an arbitrary address**. This big number is an address, which could be a heap address or a Glibc address. A traditional target was **`global_max_fast`** to allow to create fast bin bins with bigger sizes (and pass from an unsorted bin attack to a fast bin attack).
|
||||
|
||||
- Modern note (glibc ≥ 2.39): `global_max_fast` became an 8‑bit global. Blindly writing a pointer there via an unsorted-bin write will clobber adjacent libc data and will not reliably raise the fastbin limit anymore. Prefer other targets or other primitives when running against glibc 2.39+. See "Modern constraints" below and consider combining with other techniques like a [large bin attack](large-bin-attack.md) or a [fast bin attack](fast-bin-attack.md) once you have a stable primitive.
|
||||
|
||||
> [!TIP]
|
||||
> E> chando un vistazo al ejemplo proporcionado en [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle) y usando 0x4000 y 0x5000 en lugar de 0x400 y 0x500 como tamaños de chunk (para evitar Tcache), es posible ver que **hoy en día** se activa el error **`malloc(): unsorted double linked list corrupted`**.
|
||||
> T> aking a look to the example provided in [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle) and using 0x4000 and 0x5000 instead of 0x400 and 0x500 as chunk sizes (to avoid Tcache) it's possible to see that **nowadays** the error **`malloc(): unsorted double linked list corrupted`** is triggered.
|
||||
>
|
||||
> Por lo tanto, este ataque de unsorted bin ahora (entre otras comprobaciones) también requiere poder arreglar la lista doblemente enlazada para que esto se eluda `victim->bk->fd == victim` o no `victim->fd == av (arena)`, lo que significa que la dirección donde queremos escribir debe tener la dirección del chunk falso en su posición `fd` y que el `fd` del chunk falso apunta a la arena.
|
||||
> Therefore, this unsorted bin attack now (among other checks) also requires to be able to fix the doubled linked list so this is bypassed `victim->bk->fd == victim` or not `victim->fd == av (arena)`, which means that the address where we want to write must have the address of the fake chunk in its `fd` position and that the fake chunk `fd` is pointing to the arena.
|
||||
|
||||
> [!CAUTION]
|
||||
> Ten en cuenta que este ataque corrompe el unsorted bin (por lo tanto, también el pequeño y el grande). Así que solo podemos **usar asignaciones del fast bin ahora** (un programa más complejo podría hacer otras asignaciones y fallar), y para activar esto debemos **asignar el mismo tamaño o el programa fallará.**
|
||||
> Note that this attack corrupts the unsorted bin (hence small and large too). So we can only **use allocations from the fast bin now** (a more complex program might do other allocations and crash), and to trigger this we must **allocate the same size or the program will crash.**
|
||||
>
|
||||
> Ten en cuenta que sobrescribir **`global_max_fast`** podría ayudar en este caso confiando en que el fast bin podrá encargarse de todas las otras asignaciones hasta que se complete el exploit.
|
||||
> Note that overwriting **`global_max_fast`** might help in this case trusting that the fast bin will be able to take care of all the other allocations until the exploit is completed.
|
||||
|
||||
El código de [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) lo explica muy bien, aunque si modificas los mallocs para asignar memoria lo suficientemente grande para no terminar en un Tcache, puedes ver que el error mencionado anteriormente aparece impidiendo esta técnica: **`malloc(): unsorted double linked list corrupted`**
|
||||
The code from [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) explains it very well, although if you modify the mallocs to allocate memory big enough so don't end in a Tcache you can see that the previously mentioned error appears preventing this technique: **`malloc(): unsorted double linked list corrupted`**
|
||||
|
||||
### Cómo ocurre la escritura
|
||||
|
||||
- The unsorted-bin write is triggered on `free` when the freed chunk is inserted at the head of the unsorted list.
|
||||
- During insertion, the allocator performs `bck = unsorted_chunks(av); fwd = bck->fd; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;`
|
||||
- If you can set `victim->bk` to `(mchunkptr)(TARGET - 0x10)` before calling `free(victim)`, the final statement will perform the write: `*(TARGET) = victim`.
|
||||
- Later, when the allocator processes the unsorted bin, integrity checks will verify (among other things) that `bck->fd == victim` and `victim->fd == unsorted_chunks(av)` before unlinking. Because the insertion already wrote `victim` into `bck->fd` (our `TARGET`), these checks can be satisfied if the write succeeded.
|
||||
|
||||
## Restricciones modernas (glibc ≥ 2.33)
|
||||
|
||||
To use unsorted‑bin writes reliably on current glibc:
|
||||
|
||||
- Tcache interference: for sizes that fall into tcache, frees are diverted there and won’t touch the unsorted bin. Either
|
||||
- make requests with sizes > MAX_TCACHE_SIZE (≥ 0x410 on 64‑bit by default), or
|
||||
- fill the corresponding tcache bin (7 entries) so that additional frees reach the global bins, or
|
||||
- if the environment is controllable, disable tcache (e.g., GLIBC_TUNABLES glibc.malloc.tcache_count=0).
|
||||
- Integrity checks on the unsorted list: on the next allocation path that examines the unsorted bin, glibc checks (simplified):
|
||||
- `bck->fd == victim` and `victim->fd == unsorted_chunks(av)`; otherwise it aborts with `malloc(): unsorted double linked list corrupted`.
|
||||
- This means the address you target must tolerate two writes: first `*(TARGET) = victim` at free‑time; later, as the chunk is removed, `*(TARGET) = unsorted_chunks(av)` (the allocator rewrites `bck->fd` back to the bin head). Choose targets where simply forcing a large non‑zero value is useful.
|
||||
- Typical stable targets in modern exploits
|
||||
- Application or global state that treats "large" values as flags/limits.
|
||||
- Indirect primitives (e.g., set up for a subsequent [fast bin attack]({{#ref}}fast-bin-attack.md{{#endref}}) or to pivot a later write‐what‐where).
|
||||
- Avoid `__malloc_hook`/`__free_hook` on new glibc: they were removed in 2.34. Avoid `global_max_fast` on ≥ 2.39 (see next note).
|
||||
- About `global_max_fast` on recent glibc
|
||||
- On glibc 2.39+, `global_max_fast` is an 8‑bit global. The classic trick of writing a heap pointer into it (to enlarge fastbins) no longer works cleanly and is likely to corrupt adjacent allocator state. Prefer other strategies.
|
||||
|
||||
## Receta mínima de explotación (glibc moderno)
|
||||
|
||||
Goal: achieve a single arbitrary write of a heap pointer to an arbitrary address using the unsorted‑bin insertion primitive, without crashing.
|
||||
|
||||
- Layout/grooming
|
||||
- Allocate A, B, C with sizes large enough to bypass tcache (e.g., 0x5000). C prevents consolidation with the top chunk.
|
||||
- Corruption
|
||||
- Overflow from A into B’s chunk header to set `B->bk = (mchunkptr)(TARGET - 0x10)`.
|
||||
- Trigger
|
||||
- `free(B)`. At insertion time the allocator executes `bck->fd = B`, therefore `*(TARGET) = B`.
|
||||
- Continuation
|
||||
- If you plan to continue allocating and the program uses the unsorted bin, expect the allocator to later set `*(TARGET) = unsorted_chunks(av)`. Both values are typically large and may be enough to change size/limit semantics in targets that only check for "big".
|
||||
|
||||
Pseudocode skeleton:
|
||||
```c
|
||||
// 64-bit glibc 2.35–2.38 style layout (tcache bypass via large sizes)
|
||||
void *A = malloc(0x5000);
|
||||
void *B = malloc(0x5000);
|
||||
void *C = malloc(0x5000); // guard
|
||||
|
||||
// overflow from A into B’s metadata (prev_size/size/.../bk). You must control B->bk.
|
||||
*(size_t *)((char*)B - 0x8) = (size_t)(TARGET - 0x10); // write fake bk
|
||||
|
||||
free(B); // triggers *(TARGET) = B (unsorted-bin insertion write)
|
||||
```
|
||||
> [!NOTE]
|
||||
> • Si no puedes bypassear tcache por tamaño, llena el tcache bin para el tamaño elegido (7 frees) antes de liberar el chunk corrompido para que el free vaya a unsorted.
|
||||
> • Si el programa aborta inmediatamente en la siguiente allocation debido a unsorted-bin checks, reexamina que `victim->fd` siga siendo igual al bin head y que tu `TARGET` contenga el puntero exacto a `victim` tras la primera escritura.
|
||||
|
||||
## Unsorted Bin Infoleak Attack
|
||||
|
||||
Este es en realidad un concepto muy básico. Los chunks en el unsorted bin van a tener punteros. El primer chunk en el unsorted bin tendrá en realidad los enlaces **`fd`** y **`bk`** **apuntando a una parte de la arena principal (Glibc)**.\
|
||||
Por lo tanto, si puedes **poner un chunk dentro de un unsorted bin y leerlo** (uso después de liberar) o **asignarlo de nuevo sin sobrescribir al menos 1 de los punteros** para luego **leerlo**, puedes tener una **fuga de información de Glibc**.
|
||||
Este es en realidad un concepto muy básico. Los chunks en el unsorted bin van a tener punteros. El primer chunk en el unsorted bin tendrá en realidad los enlaces **`fd`** y **`bk`** **apuntando a una parte del main arena (Glibc)**.\
|
||||
Por lo tanto, si puedes **colocar un chunk dentro de un unsorted bin y leerlo** (use after free) o **alocarlo de nuevo sin sobrescribir al menos 1 de los punteros** para luego **leerlo**, puedes obtener un **Glibc info leak**.
|
||||
|
||||
Un [**ataque similar utilizado en este informe**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html) fue abusar de una estructura de 4 chunks (A, B, C y D - D es solo para evitar la consolidación con el chunk superior) así que se utilizó un desbordamiento de byte nulo en B para hacer que C indicara que B no estaba en uso. Además, en B se modificaron los datos de `prev_size` para que el tamaño en lugar de ser el tamaño de B fuera A+B.\
|
||||
Luego, C fue desalojado y consolidado con A+B (pero B aún estaba en uso). Se asignó un nuevo chunk de tamaño A y luego se escribieron las direcciones de libc filtradas en B desde donde fueron filtradas.
|
||||
Un [**ataque similar usado en este writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html) consistió en abusar de una estructura de 4 chunks (A, B, C y D - D solo para evitar consolidación con el top chunk) de modo que un null byte overflow en B se usó para hacer que C indicara que B estaba libre. Además, en B se modificó el dato `prev_size` para que el tamaño en lugar de ser el de B fuera A+B.\
|
||||
Luego C fue liberado y consolidado con A+B (pero B seguía in use). Se alocó un nuevo chunk de tamaño A y entonces las direcciones libc filtradas se escribieron en B desde donde fueron leakadas.
|
||||
|
||||
## References & Other examples
|
||||
## Referencias y otros ejemplos
|
||||
|
||||
- [**https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap)
|
||||
- El objetivo es sobrescribir una variable global con un valor mayor que 4869 para que sea posible obtener la bandera y PIE no está habilitado.
|
||||
- Es posible generar chunks de tamaños arbitrarios y hay un desbordamiento de heap con el tamaño deseado.
|
||||
- El ataque comienza creando 3 chunks: chunk0 para abusar del desbordamiento, chunk1 para ser desbordado y chunk2 para que el chunk superior no consolide los anteriores.
|
||||
- Luego, chunk1 se libera y chunk0 se desborda para que el puntero `bk` de chunk1 apunte a: `bk = magic - 0x10`
|
||||
- Luego, chunk3 se asigna con el mismo tamaño que chunk1, lo que activará el ataque de unsorted bin y modificará el valor de la variable global, haciendo posible obtener la bandera.
|
||||
- El objetivo es sobrescribir una variable global con un valor mayor a 4869 para poder obtener la flag y PIE no está habilitado.
|
||||
- Es posible generar chunks de tamaños arbitrarios y hay un heap overflow con el tamaño deseado.
|
||||
- El ataque empieza creando 3 chunks: chunk0 para abusar del overflow, chunk1 para ser overflowed y chunk2 para que el top chunk no consolide los anteriores.
|
||||
- Luego, chunk1 se libera y chunk0 se desborda hasta donde apunta el `bk` de chunk1: `bk = magic - 0x10`
|
||||
- Después, se aloca chunk3 con el mismo tamaño que chunk1, lo que disparará el unsorted bin attack y modificará el valor de la variable global, haciendo posible obtener la flag.
|
||||
- [**https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html**](https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html)
|
||||
- La función de fusión es vulnerable porque si ambos índices pasados son el mismo, se reasignará sobre él y luego se liberará, pero devolviendo un puntero a esa región liberada que se puede usar.
|
||||
- Por lo tanto, **se crean 2 chunks**: **chunk0** que se fusionará consigo mismo y chunk1 para evitar la consolidación con el chunk superior. Luego, se **llama a la función de fusión con chunk0** dos veces, lo que causará un uso después de liberar.
|
||||
- Luego, se llama a la función **`view`** con el índice 2 (que es el índice del chunk de uso después de liberar), lo que **filtrará una dirección de libc**.
|
||||
- Como el binario tiene protecciones para solo malloc tamaños mayores que **`global_max_fast`**, por lo que no se usa fastbin, se va a utilizar un ataque de unsorted bin para sobrescribir la variable global `global_max_fast`.
|
||||
- Luego, es posible llamar a la función de edición con el índice 2 (el puntero de uso después de liberar) y sobrescribir el puntero `bk` para que apunte a `p64(global_max_fast-0x10)`. Luego, crear un nuevo chunk utilizará la dirección liberada previamente comprometida (0x20) que **activará el ataque de unsorted bin** sobrescribiendo el `global_max_fast` con un valor muy grande, permitiendo ahora crear chunks en fast bins.
|
||||
- Ahora se realiza un **ataque de fast bin**:
|
||||
- Primero, se descubre que es posible trabajar con fast **chunks de tamaño 200** en la ubicación de **`__free_hook`**:
|
||||
- La función merge es vulnerable porque si ambos índices pasados son el mismo, hará realloc sobre él y luego lo freeará devolviendo un puntero a esa región liberada que puede ser usada.
|
||||
- Por tanto, **se crean 2 chunks**: **chunk0** que será merged consigo mismo y chunk1 para prevenir la consolidación con el top chunk. Luego, se llama a la función **merge con chunk0** dos veces, lo que causará un use after free.
|
||||
- Después, se llama a la función **view** con el índice 2 (que es el índice del chunk con use after free), lo que **leakeará una dirección libc**.
|
||||
- Como el binario tiene protecciones que solo permiten malloc de tamaños mayores a **`global_max_fast`** por lo que no se usan fastbin, se usa un unsorted bin attack para sobrescribir la variable global `global_max_fast`.
|
||||
- Luego, es posible llamar a edit con el índice 2 (el puntero del use after free) y sobrescribir el puntero `bk` para que apunte a `p64(global_max_fast-0x10)`. Posteriormente, crear un nuevo chunk usará la dirección previamente comprometida (0x20) que **disparará el unsorted bin attack** sobrescribiendo `global_max_fast` con un valor muy grande, permitiendo ahora crear chunks en fast bins.
|
||||
- Ahora se realiza un **fast bin attack**:
|
||||
- Primero se descubre que es posible trabajar con fast **chunks de tamaño 200** en la ubicación de **`__free_hook`**:
|
||||
- <pre class="language-c"><code class="lang-c">gef➤ p &__free_hook
|
||||
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
|
||||
gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
|
||||
@ -58,16 +115,20 @@ gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
|
||||
0x7ff1e9e6076f <list_all_lock+15>: 0x0000000000000000 0x0000000000000000
|
||||
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
|
||||
</code></pre>
|
||||
- Si logramos obtener un chunk rápido de tamaño 0x200 en esta ubicación, será posible sobrescribir un puntero de función que se ejecutará.
|
||||
- Para esto, se crea un nuevo chunk de tamaño `0xfc` y se llama a la función de fusión con ese puntero dos veces, de esta manera obtenemos un puntero a un chunk liberado de tamaño `0xfc*2 = 0x1f8` en el fast bin.
|
||||
- Luego, se llama a la función de edición en este chunk para modificar la dirección **`fd`** de este fast bin para que apunte a la anterior función **`__free_hook`**.
|
||||
- Luego, se crea un chunk de tamaño `0x1f8` para recuperar del fast bin el chunk inútil anterior, por lo que se crea otro chunk de tamaño `0x1f8` para obtener un chunk de fast bin en el **`__free_hook`** que se sobrescribe con la dirección de la función **`system`**.
|
||||
- Y finalmente, se libera un chunk que contiene la cadena `/bin/sh\x00` llamando a la función de eliminación, activando la función **`__free_hook`** que apunta a system con `/bin/sh\x00` como parámetro.
|
||||
- Si conseguimos un fast chunk de tamaño 0x200 en esa ubicación, será posible sobrescribir un puntero a función que se ejecutará.
|
||||
- Para ello, se crea un nuevo chunk de tamaño `0xfc` y se llama a merged con ese puntero dos veces; de este modo se obtiene un puntero a un chunk liberado de tamaño `0xfc*2 = 0x1f8` en el fast bin.
|
||||
- Después, se llama a edit en ese chunk para modificar la dirección **`fd`** de ese fast bin para que apunte al previo **`__free_hook`**.
|
||||
- A continuación, se crea un chunk de tamaño `0x1f8` para recuperar del fast bin el chunk inútil anterior y luego se crea otro chunk de tamaño `0x1f8` para obtener un fast bin chunk en **`__free_hook`** que se sobrescribe con la dirección de la función **`system`**.
|
||||
- Y finalmente, un chunk que contiene la cadena `/bin/sh\x00` es liberado llamando a la función delete, disparando **`__free_hook`** que apunta a system con `/bin/sh\x00` como parámetro.
|
||||
- **CTF** [**https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html)
|
||||
- Otro ejemplo de abusar de un desbordamiento de 1B para consolidar chunks en el unsorted bin y obtener una fuga de información de libc y luego realizar un ataque de fast bin para sobrescribir el malloc hook con una dirección de one gadget.
|
||||
- Otro ejemplo de abuso de un overflow de 1B para consolidar chunks en el unsorted bin y obtener un libc infoleak y luego realizar un fast bin attack para sobrescribir malloc hook con una one gadget address
|
||||
- [**Robot Factory. BlackHat MEA CTF 2022**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/)
|
||||
- Solo podemos asignar chunks de tamaño mayor que `0x100`.
|
||||
- Sobrescribir `global_max_fast` usando un ataque de Unsorted Bin (funciona 1/16 veces debido a ASLR, porque necesitamos modificar 12 bits, pero debemos modificar 16 bits).
|
||||
- Ataque de Fast Bin para modificar un arreglo global de chunks. Esto proporciona una primitiva de lectura/escritura arbitraria, que permite modificar el GOT y hacer que algunas funciones apunten a `system`.
|
||||
- Solo podemos alocar chunks de tamaño mayor a `0x100`.
|
||||
- Sobrescribir `global_max_fast` usando un Unsorted Bin attack (funciona 1/16 veces debido a ASLR, porque necesitamos modificar 12 bits, pero debemos modificar 16 bits).
|
||||
- Fast Bin attack para modificar un arreglo global de chunks. Esto da un primitive de lectura/escritura arbitraria, lo que permite modificar la GOT y apuntar alguna función a `system`.
|
||||
|
||||
## Referencias
|
||||
|
||||
- Glibc malloc unsorted-bin integrity checks (example in 2.33 source): https://elixir.bootlin.com/glibc/glibc-2.33/source/malloc/malloc.c
|
||||
- `global_max_fast` and related definitions in modern glibc (2.39): https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user