diff --git a/src/binary-exploitation/libc-heap/unsorted-bin-attack.md b/src/binary-exploitation/libc-heap/unsorted-bin-attack.md index e71d9804a..c9a5aff86 100644 --- a/src/binary-exploitation/libc-heap/unsorted-bin-attack.md +++ b/src/binary-exploitation/libc-heap/unsorted-bin-attack.md @@ -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 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] -> 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 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. + +## Receita mínima de exploração (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] +> • 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`**: -
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 : 0x0000000000000000 0x0000000000000000
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
-- 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}}