Translated ['src/binary-exploitation/libc-heap/unsorted-bin-attack.md']

This commit is contained in:
Translator 2025-08-28 16:55:35 +00:00
parent e2a24909ff
commit ae1de7fd4d

View File

@ -2,54 +2,111 @@
{{#include ../../banners/hacktricks-training.md}}
## Basic Information
## Informações Básicas
For more information about what is an unsorted bin check this page:
Para mais informações sobre o que é um unsorted bin, consulte esta página:
{{#ref}}
bins-and-memory-allocations.md
{{#endref}}
Listas não ordenadas podem escrever o endereço em `unsorted_chunks (av)` no endereço `bk` do chunk. Portanto, se um atacante puder **modificar o endereço do ponteiro `bk`** em um chunk dentro do unsorted bin, ele poderá **escrever esse endereço em um endereço arbitrário**, o que pode ser útil para vazar endereços do Glibc ou contornar algumas defesas.
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.
Assim, basicamente, esse ataque permite **definir um grande número em um endereço arbitrário**. Esse grande número é um endereço, que pode ser um endereço de heap ou um endereço do Glibc. Um alvo típico é **`global_max_fast`** para permitir a criação de bins de fast bin com tamanhos maiores (e passar de um ataque de unsorted bin para um 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 8bit 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]
> D> ar uma olhada no exemplo fornecido em [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) e usar 0x4000 e 0x5000 em vez de 0x400 e 0x500 como tamanhos de chunk (para evitar Tcache), é possível ver que **atualmente** o erro **`malloc(): unsorted double linked list corrupted`** é acionado.
> 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.
>
> Portanto, esse ataque de unsorted bin agora (entre outras verificações) também requer ser capaz de corrigir a lista duplamente encadeada, para que isso seja contornado `victim->bk->fd == victim` ou não `victim->fd == av (arena)`, o que significa que o endereço onde queremos escrever deve ter o endereço do chunk falso em sua posição `fd` e que o `fd` do chunk falso está apontando para a 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]
> Note que este ataque corrompe o unsorted bin (portanto, pequeno e grande também). Portanto, só podemos **usar alocações do fast bin agora** (um programa mais complexo pode fazer outras alocações e travar), e para acionar isso devemos **alocar o mesmo tamanho ou o programa irá travar.**
> 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.**
>
> Note que sobrescrever **`global_max_fast`** pode ajudar neste caso, confiando que o fast bin será capaz de cuidar de todas as outras alocações até que a exploração seja concluída.
> 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.
O código de [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) explica isso muito bem, embora se você modificar os mallocs para alocar memória grande o suficiente para não acabar em um Tcache, você pode ver que o erro mencionado anteriormente aparece, impedindo essa 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`**
### Como a escrita realmente acontece
- 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.
## Restrições modernas (glibc ≥ 2.33)
To use unsortedbin writes reliably on current glibc:
- Tcache interference: for sizes that fall into tcache, frees are diverted there and wont touch the unsorted bin. Either
- make requests with sizes > MAX_TCACHE_SIZE (≥ 0x410 on 64bit 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 freetime; 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 nonzero 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 writewhatwhere).
- 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 8bit 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.
## Receita mínima de exploração (glibc moderno)
Goal: achieve a single arbitrary write of a heap pointer to an arbitrary address using the unsortedbin 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 Bs 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.352.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 Bs 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]
> • Se você não consegue bypassar o tcache por tamanho, preencha o tcache bin para o tamanho escolhido (7 frees) antes de dar free no chunk corrompido para que o free vá para unsorted.
> • Se o programa aborta imediatamente na próxima alocação devido às checagens do unsorted-bin, reexamine se `victim->fd` ainda é igual à cabeça do bin e se o seu `TARGET` mantém o ponteiro exato `victim` após a primeira escrita.
## Unsorted Bin Infoleak Attack
Este é, na verdade, um conceito muito básico. Os chunks no unsorted bin terão ponteiros. O primeiro chunk no unsorted bin terá, na verdade, os links **`fd`** e **`bk`** **apontando para uma parte da arena principal (Glibc)**.\
Portanto, se você puder **colocar um chunk dentro de um unsorted bin e lê-lo** (use after free) ou **alocá-lo novamente sem sobrescrever pelo menos 1 dos ponteiros** para então **lê-lo**, você pode ter um **vazamento de informações do Glibc**.
Isto é, na verdade, um conceito muito básico. Os chunks no unsorted bin vão ter pointers. O primeiro chunk no unsorted bin terá na verdade os links **`fd`** e **`bk`** **apontando para uma parte do main arena (Glibc)**.\
Portanto, se você conseguir **colocar um chunk dentro de um unsorted bin e lê-lo** (use after free) ou **alocá-lo novamente sem sobrescrever pelo menos 1 dos ponteiros** para então **lê-lo**, você pode ter um **Glibc info leak**.
Um [**ataque semelhante usado neste writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html) foi abusar de uma estrutura de 4 chunks (A, B, C e D - D é apenas para evitar a consolidação com o chunk superior), então um overflow de byte nulo em B foi usado para fazer C indicar que B estava não utilizado. Além disso, em B, os dados `prev_size` foram modificados para que o tamanho, em vez de ser o tamanho de B, fosse A+B.\
Então C foi desalocado e consolidado com A+B (mas B ainda estava em uso). Um novo chunk de tamanho A foi alocado e, em seguida, os endereços vazados da libc foram escritos em B, de onde foram vazados.
A similar [**attack used in this writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html), foi abusar de uma estrutura de 4 chunks (A, B, C e D - D existe apenas para prevenir consolidação com o top chunk) de modo que um null byte overflow em B foi usado para fazer C indicar que B estava unused. Também, em B o campo `prev_size` foi modificado de modo que o tamanho em vez de ser o tamanho de B era A+B.\
Então C foi desalocado e consolidado com A+B (mas B ainda estava em uso). Um novo chunk do tamanho de A foi alocado e então os endereços libc leakados foram escritos em B de onde foram leakados.
## References & Other examples
- [**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)
- O objetivo é sobrescrever uma variável global com um valor maior que 4869 para que seja possível obter a flag e o PIE não está habilitado.
- É possível gerar chunks de tamanhos arbitrários e há um overflow de heap com o tamanho desejado.
- O ataque começa criando 3 chunks: chunk0 para abusar do overflow, chunk1 para ser transbordado e chunk2 para que o chunk superior não consolide os anteriores.
- Então, chunk1 é liberado e chunk0 é transbordado para que o ponteiro `bk` de chunk1 aponte para: `bk = magic - 0x10`
- Em seguida, chunk3 é alocado com o mesmo tamanho que chunk1, o que acionará o ataque de unsorted bin e modificará o valor da variável global, tornando possível obter a flag.
- É possível gerar chunks de tamanhos arbitrários e existe um heap overflow com o tamanho desejado.
- O ataque começa criando 3 chunks: chunk0 para abusar do overflow, chunk1 para ser overflowado e chunk2 para que o top chunk não consolide os anteriores.
- Então, chunk1 é freeado e chunk0 é overflowado para que o ponteiro `bk` de chunk1 aponte para: `bk = magic - 0x10`
- Então, chunk3 é alocado com o mesmo tamanho que chunk1, o que irá disparar o unsorted bin attack e modificará o valor da variável global, tornando possível obter a flag.
- [**https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html**](https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html)
- A função de mesclagem é vulnerável porque se ambos os índices passados forem o mesmo, ela irá realocar nele e depois liberá-lo, mas retornando um ponteiro para essa região liberada que pode ser usada.
- Portanto, **2 chunks são criados**: **chunk0** que será mesclado consigo mesmo e chunk1 para evitar a consolidação com o chunk superior. Então, a **função de mesclagem é chamada com chunk0** duas vezes, o que causará um uso após a liberação.
- Em seguida, a função **`view`** é chamada com o índice 2 (que é o índice do chunk usado após a liberação), o que irá **vazar um endereço da libc**.
- Como o binário tem proteções para alocar apenas tamanhos maiores que **`global_max_fast`**, então nenhum fastbin é usado, um ataque de unsorted bin será usado para sobrescrever a variável global `global_max_fast`.
- Em seguida, é possível chamar a função de edição com o índice 2 (o ponteiro usado após a liberação) e sobrescrever o ponteiro `bk` para apontar para `p64(global_max_fast-0x10)`. Então, criando um novo chunk, o endereço livre anteriormente comprometido (0x20) irá **acionar o ataque de unsorted bin** sobrescrevendo o `global_max_fast` com um valor muito grande, permitindo agora criar chunks em fast bins.
- Agora um **ataque de fast bin** é realizado:
- Primeiro de tudo, descobre-se que é possível trabalhar com fast **chunks de tamanho 200** na localização de **`__free_hook`**:
- A função merge é vulnerável porque, se ambos os índices passados forem o mesmo, ela fará realloc nele e então o freeará, mas retornará um ponteiro para aquela região liberada que pode ser usada.
- Portanto, **2 chunks são criados**: **chunk0** que será merged com ele mesmo e chunk1 para prevenir a consolidação com o top chunk. Depois, a **merge function é chamada com chunk0** duas vezes, o que causará um use after free.
- Então, a função **`view`** é chamada com o índice 2 (que é o índice do chunk com use after free), o que irá leakar um endereço libc.
- Como o binário tem proteções que só permitem malloc de tamanhos maiores que **`global_max_fast`**, então nenhum fastbin é usado; um unsorted bin attack será usado para sobrescrever a variável global `global_max_fast`.
- Depois, é possível chamar a função edit com o índice 2 (o ponteiro do use after free) e sobrescrever o ponteiro `bk` para apontar para `p64(global_max_fast-0x10)`. Então, criar um novo chunk que usará o endereço previamente comprometido (0x20) irá **trigger the unsorted bin attack**, sobrescrevendo o `global_max_fast` com um valor muito grande, permitindo agora criar chunks em fast bins.
- Now a **fast bin attack** is performed:
- Primeiro, é descoberto que é possível trabalhar com fast **chunks de tamanho 200** na localização **`__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>
- Se conseguirmos obter um chunk rápido de tamanho 0x200 nesta localização, será possível sobrescrever um ponteiro de função que será executado.
- Para isso, um novo chunk de tamanho `0xfc` é criado e a função mesclada é chamada com esse ponteiro duas vezes, assim obtemos um ponteiro para um chunk liberado de tamanho `0xfc*2 = 0x1f8` no fast bin.
- Em seguida, a função de edição é chamada neste chunk para modificar o endereço **`fd`** deste fast bin para apontar para a função anterior **`__free_hook`**.
- Então, um chunk com tamanho `0x1f8` é criado para recuperar do fast bin o chunk anterior inútil, então outro chunk de tamanho `0x1f8` é criado para obter um chunk de fast bin na **`__free_hook`** que é sobrescrito com o endereço da função **`system`**.
- E finalmente, um chunk contendo a string `/bin/sh\x00` é liberado chamando a função de delete, acionando a função **`__free_hook`** que aponta para system com `/bin/sh\x00` como parâmetro.
- Se conseguirmos obter um fast chunk de tamanho 0x200 nessa localização, será possível sobrescrever um function pointer que será executado.
- Para isso, um novo chunk de tamanho `0xfc` é criado e a função merged é chamada com esse ponteiro duas vezes; dessa forma obtemos um ponteiro para um chunk freeado de tamanho `0xfc*2 = 0x1f8` no fast bin.
- Então, a função edit é chamada nesse chunk para modificar o endereço **`fd`** desse fast bin para apontar para o anterior **`__free_hook`**.
- Depois, um chunk com tamanho `0x1f8` é criado para recuperar do fast bin o chunk inútil anterior; então outro chunk de tamanho `0x1f8` é criado para obter um fast bin chunk em **`__free_hook`**, que é sobrescrito com o endereço da função **`system`**.
- E finalmente um chunk contendo a string `/bin/sh\x00` é freeado chamando a função delete, disparando a função **`__free_hook`** que aponta para system com `/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)
- Outro exemplo de abuso de um overflow de 1B para consolidar chunks no unsorted bin e obter um vazamento de informações da libc e, em seguida, realizar um ataque de fast bin para sobrescrever o malloc hook com um endereço de one gadget.
- Outro exemplo de abusar de um overflow de 1B para consolidar chunks no unsorted bin e conseguir um libc infoleak e então realizar um fast bin attack para sobrescrever malloc hook com um one gadget address
- [**Robot Factory. BlackHat MEA CTF 2022**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/)
- Só podemos alocar chunks de tamanho maior que `0x100`.
- Sobrescrever `global_max_fast` usando um ataque de Unsorted Bin (funciona 1/16 vezes devido ao ASLR, porque precisamos modificar 12 bits, mas devemos modificar 16 bits).
- Ataque de Fast Bin para modificar um array global de chunks. Isso fornece uma primitiva de leitura/escrita arbitrária, que permite modificar o GOT e definir algumas funções para apontar para `system`.
- Sobrescrever `global_max_fast` usando um Unsorted Bin attack (funciona 1/16 vezes devido ao ASLR, porque precisamos modificar 12 bits, mas devemos modificar 16 bits).
- Fast Bin attack para modificar um array global de chunks. Isso dá uma primitive de leitura/escrita arbitrária, que permite modificar a GOT e apontar alguma função para `system`.
## References
- Checagens de integridade do unsorted-bin do Glibc malloc (exemplo no source 2.33): https://elixir.bootlin.com/glibc/glibc-2.33/source/malloc/malloc.c
- `global_max_fast` e definições relacionadas no glibc moderno (2.39): https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c
{{#include ../../banners/hacktricks-training.md}}