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

This commit is contained in:
Translator 2025-08-28 16:56:57 +00:00
parent a5346e5606
commit f3a2454cfc

View File

@ -2,54 +2,111 @@
{{#include ../../banners/hacktricks-training.md}}
## Basic Information
## Informations de base
Pour plus d'informations sur ce qu'est un unsorted bin, consultez cette page :
Pour plus d'informations sur ce qu'est un unsorted bin, consultez cette page :
{{#ref}}
bins-and-memory-allocations.md
{{#endref}}
Les listes non triées peuvent écrire l'adresse dans `unsorted_chunks (av)` à l'adresse `bk` du chunk. Par conséquent, si un attaquant peut **modifier l'adresse du pointeur `bk`** dans un chunk à l'intérieur de l'unsorted bin, il pourrait être capable de **écrire cette adresse à une adresse arbitraire** qui pourrait être utile pour divulguer des adresses Glibc ou contourner certaines protections.
Les unsorted lists peuvent écrire l'adresse de `unsorted_chunks (av)` dans l'adresse `bk` du chunk. Ainsi, si un attaquant peut **modifier l'adresse du pointeur `bk`** dans un chunk présent dans l'unsorted bin, il pourrait **écrire cette adresse à une adresse arbitraire**, ce qui peut être utile pour leak des adresses Glibc ou contourner certaines protections.
Donc, fondamentalement, cette attaque permet de **définir un grand nombre à une adresse arbitraire**. Ce grand nombre est une adresse, qui pourrait être une adresse de heap ou une adresse Glibc. Une cible typique est **`global_max_fast`** pour permettre de créer des bins de fast bin avec des tailles plus grandes (et passer d'une attaque d'unsorted bin à une attaque de fast bin).
Donc, essentiellement, cette attaque permet de **placer un grand nombre à une adresse arbitraire**. Ce grand nombre est une adresse, qui peut être une adresse du heap ou une adresse Glibc. Une cible classique était **`global_max_fast`** pour permettre de créer des fast bin bins avec des tailles plus grandes (et passer d'une unsorted bin attack à une fast bin attack).
- Note moderne (glibc ≥ 2.39) : `global_max_fast` est devenu un global sur 8 bits. Écrire aveuglément un pointeur làdessus via un unsortedbin write corrompra les données libc adjacentes et n'augmentera plus de manière fiable la limite des fastbin. Préférez d'autres cibles ou primitives contre glibc 2.39+. Voir "Modern constraints" cidessous et envisagez de combiner avec d'autres techniques comme un [large bin attack](large-bin-attack.md) ou un [fast bin attack](fast-bin-attack.md) une fois que vous disposez d'une primitive stable.
> [!TIP]
> J> etant un coup d'œil à l'exemple fourni dans [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) et en utilisant 0x4000 et 0x5000 au lieu de 0x400 et 0x500 comme tailles de chunk (pour éviter Tcache), il est possible de voir que **de nos jours** l'erreur **`malloc(): unsorted double linked list corrupted`** est déclenchée.
> 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.
>
> Par conséquent, cette attaque d'unsorted bin nécessite maintenant (entre autres vérifications) également d'être capable de corriger la liste doublement chaînée afin que cela soit contourné `victim->bk->fd == victim` ou non `victim->fd == av (arena)`, ce qui signifie que l'adresse où nous voulons écrire doit avoir l'adresse du faux chunk dans sa position `fd` et que le faux chunk `fd` pointe vers l'arène.
> 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]
> Notez que cette attaque corrompt l'unsorted bin (d'où les petits et grands aussi). Donc, nous ne pouvons utiliser que **des allocations à partir du fast bin maintenant** (un programme plus complexe pourrait faire d'autres allocations et planter), et pour déclencher cela, nous devons **allouer la même taille ou le programme plantera.**
> 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.**
>
> Notez que l'écrasement de **`global_max_fast`** pourrait aider dans ce cas en faisant confiance au fast bin pour s'occuper de toutes les autres allocations jusqu'à ce que l'exploitation soit terminée.
> 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.
Le code de [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) l'explique très bien, bien que si vous modifiez les mallocs pour allouer une mémoire suffisamment grande pour ne pas se retrouver dans un Tcache, vous pouvez voir que l'erreur mentionnée précédemment apparaît, empêchant cette technique : **`malloc(): unsorted double linked list corrupted`**
Le code de [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) l'explique très bien, bien que si vous modifiez les mallocs pour allouer de la mémoire suffisamment grande afin de ne pas tomber dans une Tcache, vous pouvez voir que l'erreur mentionnée précédemment apparaît, empêchant cette technique : **`malloc(): unsorted double linked list corrupted`**
### How the write actually happens
- 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.
## Contraintes modernes (glibc ≥ 2.33)
Pour utiliser de manière fiable les unsortedbin writes sur les glibc actuels :
- Tcache interference : pour les tailles qui tombent dans tcache, les frees sont détournés vers celuici et natteindront pas l'unsorted bin. Soit
- effectuez des requêtes avec des tailles > MAX_TCACHE_SIZE (≥ 0x410 sur 64bit par défaut), ou
- remplissez le bin tcache correspondant (7 entrées) afin que les frees supplémentaires atteignent les global bins, ou
- si l'environnement est contrôlable, désactivez tcache (par exemple, GLIBC_TUNABLES glibc.malloc.tcache_count=0).
- Integrity checks on the unsorted list : sur le prochain chemin d'allocation qui examine l'unsorted bin, glibc vérifie (simplifié) :
- `bck->fd == victim` et `victim->fd == unsorted_chunks(av)` ; sinon il abort avec `malloc(): unsorted double linked list corrupted`.
- Cela signifie que l'adresse que vous ciblez doit tolérer deux écritures : d'abord `*(TARGET) = victim` au moment du free ; ensuite, lorsque le chunk est retiré, `*(TARGET) = unsorted_chunks(av)` (l'allocator réécrit `bck->fd` vers la tête du bin). Choisissez des cibles où forcer simplement une grande valeur nonnulle est utile.
- Cibles typiques stables dans les exploits modernes
- État applicatif ou global qui traite des valeurs "grandes" comme des flags/limites.
- Primitives indirectes (par ex., préparer un [fast bin attack]({{#ref}}fast-bin-attack.md{{#endref}}) ultérieur ou pivoter vers un writewhatwhere ultérieur).
- Évitez `__malloc_hook`/`__free_hook` sur les nouvelles glibc : ils ont été supprimés en 2.34. Évitez `global_max_fast` sur ≥ 2.39 (voir note suivante).
- À propos de `global_max_fast` sur les glibc récents
- Sur glibc 2.39+, `global_max_fast` est un global sur 8 bits. L'astuce classique consistant à y écrire un pointeur heap (pour agrandir les fastbins) ne fonctionne plus proprement et risque de corrompre l'état de l'allocator adjacent. Préférez d'autres stratégies.
## Minimal exploitation recipe (modern glibc)
Objectif : obtenir une écriture arbitraire unique d'un pointeur heap à une adresse arbitraire en utilisant la primitive d'insertion unsortedbin, sans provoquer de crash.
- Layout/grooming
- Allouer A, B, C avec des tailles suffisamment grandes pour éviter tcache (par ex., 0x5000). C empêche la consolidation avec le top chunk.
- Corruption
- Overflow depuis A dans le header de B pour définir `B->bk = (mchunkptr)(TARGET - 0x10)`.
- Trigger
- `free(B)`. Au moment de l'insertion, l'allocator exécute `bck->fd = B`, donc `*(TARGET) = B`.
- Continuation
- Si vous prévoyez de continuer à allouer et que le programme utilise l'unsorted bin, attendezvous à ce que l'allocator écrive ensuite `*(TARGET) = unsorted_chunks(av)`. Les deux valeurs sont typiquement grandes et peuvent suffire à modifier la sémantique de taille/limite dans des cibles qui ne testent que la "grandeur".
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]
> • If you cannot bypass tcache with size, fill the tcache bin for the chosen size (7 frees) before freeing the corrupted chunk so the free goes to unsorted.
> • If the program immediately aborts on the next allocation due to unsorted-bin checks, reexamine that `victim->fd` still equals the bin head and that your `TARGET` holds the exact `victim` pointer after the first write.
## Unsorted Bin Infoleak Attack
C'est en fait un concept très basique. Les chunks dans l'unsorted bin vont avoir des pointeurs. Le premier chunk dans l'unsorted bin aura en fait les liens **`fd`** et **`bk`** **pointant vers une partie de l'arène principale (Glibc)**.\
Par conséquent, si vous pouvez **mettre un chunk à l'intérieur d'un unsorted bin et le lire** (use after free) ou **le réallouer sans écraser au moins 1 des pointeurs** pour ensuite **le lire**, vous pouvez avoir une **divulgation d'informations Glibc**.
This is actually a very basic concept. The chunks in the unsorted bin are going to have pointers. The first chunk in the unsorted bin will actually have the **`fd`** and the **`bk`** links **pointing to a part of the main arena (Glibc)**.\
Therefore, if you can **put a chunk inside a unsorted bin and read it** (use after free) or **allocate it again without overwriting at least 1 of the pointers** to then **read** it, you can have a **Glibc info leak**.
Une [**attaque similaire utilisée dans ce rapport**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html) consistait à abuser d'une structure de 4 chunks (A, B, C et D - D est uniquement pour empêcher la consolidation avec le chunk supérieur) donc un débordement de byte nul dans B a été utilisé pour faire indiquer à C que B était inutilisé. De plus, dans B, les données `prev_size` ont été modifiées pour que la taille, au lieu d'être la taille de B, soit A+B.\
Ensuite, C a été désalloué et consolidé avec A+B (mais B était toujours utilisé). Un nouveau chunk de taille A a été alloué et ensuite les adresses de libc divulguées ont été écrites dans B d'où elles ont été divulguées.
A similar [**attack used in this writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html), was to abuse a 4 chunks structure (A, B, C and D - D is only to prevent consolidation with top chunk) so a null byte overflow in B was used to make C indicate that B was unused. Also, in B the `prev_size` data was modified so the size instead of being the size of B was A+B.\
Then C was deallocated, and consolidated with A+B (but B was still in used). A new chunk of size A was allocated and then the libc leaked addresses was written into B from where they were leaked.
## 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)
- L'objectif est d'écraser une variable globale avec une valeur supérieure à 4869 afin qu'il soit possible d'obtenir le flag et que PIE ne soit pas activé.
- Il est possible de générer des chunks de tailles arbitraires et il y a un débordement de heap avec la taille souhaitée.
- L'attaque commence par créer 3 chunks : chunk0 pour abuser du débordement, chunk1 pour être débordé et chunk2 afin que le chunk supérieur ne consolide pas les précédents.
- Ensuite, chunk1 est libéré et chunk0 est débordé pour que le pointeur `bk` de chunk1 pointe vers : `bk = magic - 0x10`
- Ensuite, chunk3 est alloué avec la même taille que chunk1, ce qui déclenchera l'attaque d'unsorted bin et modifiera la valeur de la variable globale, rendant possible d'obtenir le flag.
- The goal is to overwrite a global variable with a value greater than 4869 so it's possible to get the flag and PIE is not enabled.
- It's possible to generate chunks of arbitrary sizes and there is a heap overflow with the desired size.
- The attack starts creating 3 chunks: chunk0 to abuse the overflow, chunk1 to be overflowed and chunk2 so top chunk doesn't consolidate the previous ones.
- Then, chunk1 is freed and chunk0 is overflowed to the `bk` pointer of chunk1 points to: `bk = magic - 0x10`
- Then, chunk3 is allocated with the same size as chunk1, which will trigger the unsorted bin attack and will modify the value of the global variable, making possible to get the flag.
- [**https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html**](https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html)
- La fonction de fusion est vulnérable car si les deux index passés sont le même, elle le réalloue et ensuite le libère mais renvoie un pointeur vers cette région libérée qui peut être utilisée.
- Par conséquent, **2 chunks sont créés** : **chunk0** qui sera fusionné avec lui-même et chunk1 pour empêcher la consolidation avec le chunk supérieur. Ensuite, la **fonction de fusion est appelée avec chunk0** deux fois, ce qui provoquera un use after free.
- Ensuite, la **fonction `view`** est appelée avec l'index 2 (qui est l'index du chunk use after free), ce qui **divulguera une adresse libc**.
- Comme le binaire a des protections pour n'allouer que des tailles supérieures à **`global_max_fast`**, aucun fastbin n'est utilisé, une attaque d'unsorted bin sera utilisée pour écraser la variable globale `global_max_fast`.
- Ensuite, il est possible d'appeler la fonction d'édition avec l'index 2 (le pointeur use after free) et d'écraser le pointeur `bk` pour pointer vers `p64(global_max_fast-0x10)`. Ensuite, la création d'un nouveau chunk utilisera l'adresse libre précédemment compromise (0x20) qui **déclenchera l'attaque d'unsorted bin** écrasant le `global_max_fast` avec une très grande valeur, permettant maintenant de créer des chunks dans les fast bins.
- Maintenant, une **attaque de fast bin** est effectuée :
- Tout d'abord, il est découvert qu'il est possible de travailler avec des fast **chunks de taille 200** dans l'emplacement **`__free_hook`** :
- The merge function is vulnerable because if both indexes passed are the same one it'll realloc on it and then free it but returning a pointer to that freed region that can be used.
- Therefore, **2 chunks are created**: **chunk0** which will be merged with itself and chunk1 to prevent consolidating with the top chunk. Then, the **merge function is called with chunk0** twice which will cause a use after free.
- Then, the **`view`** function is called with index 2 (which the index of the use after free chunk), which will **leak a libc address**.
- As the binary has protections to only malloc sizes bigger than **`global_max_fast`** so no fastbin is used, an unsorted bin attack is going to be used to overwrite the global variable `global_max_fast`.
- Then, it's possible to call the edit function with the index 2 (the use after free pointer) and overwrite the `bk` pointer to point to `p64(global_max_fast-0x10)`. Then, creating a new chunk will use the previously compromised free address (0x20) will **trigger the unsorted bin attack** overwriting the `global_max_fast` which a very big value, allowing now to create chunks in fast bins.
- Now a **fast bin attack** is performed:
- First of all it's discovered that it's possible to work with fast **chunks of size 200** in the **`__free_hook`** location:
- <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 nous parvenons à obtenir un fast chunk de taille 0x200 à cet emplacement, il sera possible d'écraser un pointeur de fonction qui sera exécuté.
- Pour cela, un nouveau chunk de taille `0xfc` est créé et la fonction de fusion est appelée avec ce pointeur deux fois, de cette façon nous obtenons un pointeur vers un chunk libéré de taille `0xfc*2 = 0x1f8` dans le fast bin.
- Ensuite, la fonction d'édition est appelée dans ce chunk pour modifier l'adresse **`fd`** de ce fast bin pour pointer vers la fonction précédente **`__free_hook`**.
- Ensuite, un chunk de taille `0x1f8` est créé pour récupérer du fast bin le chunk inutile précédent, donc un autre chunk de taille `0x1f8` est créé pour obtenir un fast bin chunk dans le **`__free_hook`** qui est écrasé avec l'adresse de la fonction **`system`**.
- Et enfin, un chunk contenant la chaîne `/bin/sh\x00` est libéré en appelant la fonction de suppression, déclenchant la fonction **`__free_hook`** qui pointe vers system avec `/bin/sh\x00` comme paramètre.
- If we manage to get a fast chunk of size 0x200 in this location, it'll be possible to overwrite a function pointer that will be executed
- For this, a new chunk of size `0xfc` is created and the merged function is called with that pointer twice, this way we obtain a pointer to a freed chunk of size `0xfc*2 = 0x1f8` in the fast bin.
- Then, the edit function is called in this chunk to modify the **`fd`** address of this fast bin to point to the previous **`__free_hook`** function.
- Then, a chunk with size `0x1f8` is created to retrieve from the fast bin the previous useless chunk so another chunk of size `0x1f8` is created to get a fast bin chunk in the **`__free_hook`** which is overwritten with the address of **`system`** function.
- And finally a chunk containing the string `/bin/sh\x00` is freed calling the delete function, triggering the **`__free_hook`** function which points to system with `/bin/sh\x00` as parameter.
- **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)
- Un autre exemple d'abus d'un débordement de 1B pour consolider des chunks dans l'unsorted bin et obtenir une divulgation d'informations libc, puis effectuer une attaque de fast bin pour écraser le malloc hook avec une adresse de gadget unique.
- Another example of abusing a 1B overflow to consolidate chunks in the unsorted bin and get a libc infoleak and then perform a fast bin attack to overwrite malloc hook with a one gadget address
- [**Robot Factory. BlackHat MEA CTF 2022**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/)
- Nous ne pouvons allouer que des chunks de taille supérieure à `0x100`.
- Écraser `global_max_fast` en utilisant une attaque d'unsorted bin (fonctionne 1/16 fois en raison de l'ASLR, car nous devons modifier 12 bits, mais nous devons modifier 16 bits).
- Attaque de Fast Bin pour modifier un tableau global de chunks. Cela donne une primitive de lecture/écriture arbitraire, ce qui permet de modifier le GOT et de faire pointer certaines fonctions vers `system`.
- We can only allocate chunks of size greater than `0x100`.
- Overwrite `global_max_fast` using an Unsorted Bin attack (works 1/16 times due to ASLR, because we need to modify 12 bits, but we must modify 16 bits).
- Fast Bin attack to modify the a global array of chunks. This gives an arbitrary read/write primitive, which allows to modify the GOT and set some function to point to `system`.
## References
- 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}}