# Unsorted Bin Attack {{#include ../../banners/hacktricks-training.md}} ## Basic Information Pour plus d'informations sur ce qu'est un unsorted bin, consultez cette page : {{#ref}} bins-and-memory-allocations.md {{#endref}} Unsorted lists sont capables d'écrire l'adresse de `unsorted_chunks (av)` dans le champ `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 d'**écrire cette adresse à une adresse arbitraire**, ce qui peut être utile pour leak des adresses Glibc ou contourner certaines protections. Donc, essentiellement, cette attaque permet de **poser 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 traditionnelle était **`global_max_fast`** pour permettre de créer des fast bin avec des tailles plus grandes (et passer d'un unsorted bin attack à un fast bin attack). - Modern note (glibc ≥ 2.39): `global_max_fast` est devenu un global 8 bits. Écrire aveuglément un pointeur là via une écriture unsorted-bin corrompra les données libc adjacentes et ne relèvera plus de manière fiable la limite des fastbin. Préférez d'autres cibles ou d'autres primitives contre glibc 2.39+. Voir "Modern constraints" ci‑dessous 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 avez une primitive stable. > [!TIP] > 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. > > 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 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 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 assez grand afin de ne pas finir dans une Tcache vous pouvez voir que l'erreur précédemment mentionnée 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. ## Modern constraints (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. ## Minimal exploitation recipe (modern glibc) 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 vous ne pouvez pas bypasser tcache avec la taille, remplissez le tcache bin pour la taille choisie (7 frees) avant de free le chunk corrompu afin que le free aille dans unsorted. > • Si le programme abort immédiatement sur la prochaine allocation à cause des checks unsorted-bin, re-vérifiez que `victim->fd` vaut toujours la head du bin et que votre `TARGET` contient le pointeur exact `victim` après le premier write. ## Unsorted Bin Infoleak Attack C'est en réalité un concept très basique. Les chunks dans l'unsorted bin vont contenir des pointeurs. Le premier chunk dans l'unsorted bin aura en fait les liens **`fd`** et **`bk`** **pointant vers une partie du main arena (Glibc)**.\ Donc, si vous pouvez **mettre un chunk dans un unsorted bin et le lire** (use after free) ou **le réallouer sans écraser au moins 1 des pointeurs** puis le **lire**, vous pouvez obtenir un **Glibc info leak**. Une attaque similaire [**attack used in this writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html) utilisait une structure de 4 chunks (A, B, C et D - D est juste pour empêcher la consolidation avec le top chunk) : un null byte overflow dans B était utilisé pour faire indiquer à C que B était unused. Aussi, dans B le champ `prev_size` a été modifié pour que la taille au lieu d'être la taille de B soit A+B.\ Ensuite C a été deallocated, et consolidé avec A+B (mais B était toujours in use). Un nouveau chunk de taille A a été alloué puis les adresses libc leakées ont été écrites dans B d'où elles ont été leakées. ## Références & Autres exemples - [**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'overwriter une variable globale avec une valeur supérieure à 4869 pour pouvoir récupérer le flag et PIE n'est pas activé. - Il est possible de générer des chunks de tailles arbitraires et il y a un heap overflow avec la taille désirée. - L'attaque commence en créant 3 chunks : chunk0 pour abuser de l'overflow, chunk1 pour être overflowed et chunk2 pour que le top chunk ne consolide pas les précédents. - Ensuite, chunk1 est freed et chunk0 est overflowed pour que le `bk` pointer de chunk1 pointe vers : `bk = magic - 0x10` - Puis, chunk3 est alloué avec la même taille que chunk1, ce qui déclenchera l'unsorted bin attack et modifiera la valeur de la variable globale, permettant d'obtenir le 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 merge est vulnérable parce que si les deux indexes passés sont identiques elle fera un realloc dessus puis le free mais retournera un pointeur vers cette région freed qui peut être utilisée. - Donc, **2 chunks sont créés** : **chunk0** qui sera merged avec lui-même et chunk1 pour empêcher la consolidation avec le top chunk. Ensuite, la fonction **merge** est appelée avec chunk0 deux fois ce qui provoque 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 va **leak une adresse libc**. - Comme le binaire a des protections qui n'autorisent que des malloc de tailles supérieures à **`global_max_fast`** donc aucun fastbin n'est utilisé, une unsorted bin attack sera utilisée pour overwriter la variable globale `global_max_fast`. - Ensuite, il est possible d'appeler la fonction edit avec l'index 2 (le pointeur use after free) et d'overwrite le pointeur `bk` pour qu'il pointe vers `p64(global_max_fast-0x10)`. Puis, créer un nouveau chunk utilisera l'adresse freed compromise précédemment (0x20) et **déclenchera l'unsorted bin attack** écrasant `global_max_fast` avec une très grande valeur, permettant maintenant de créer des chunks dans les fast bins. - Maintenant une **fast bin attack** est réalisée : - Tout d'abord on découvre qu'il est possible de travailler avec des fast **chunks de taille 200** à l'emplacement de **`__free_hook`** : -
gef➤ p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
0x7ff1e9e6074f: 0x0000000000000000 0x0000000000000200
0x7ff1e9e6075f: 0x0000000000000000 0x0000000000000000
0x7ff1e9e6076f : 0x0000000000000000 0x0000000000000000
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
- Si on parvient à obtenir un fast chunk de taille 0x200 à cet emplacement, il sera possible d'overwrite un pointeur de fonction qui sera exécuté.
- Pour cela, un nouveau chunk de taille `0xfc` est créé et la fonction merged est appelée avec ce pointeur deux fois, ainsi on obtient un pointeur vers un chunk freed de taille `0xfc*2 = 0x1f8` dans le fast bin.
- Ensuite, la fonction edit est appelée sur ce chunk pour modifier l'adresse **`fd`** de ce fast bin pour qu'elle pointe vers l'emplacement de **`__free_hook`**.
- Puis, un chunk de taille `0x1f8` est créé pour récupérer du fast bin le chunk précédent inutile, puis un autre chunk de taille `0x1f8` est créé pour obtenir un fast bin chunk dans **`__free_hook`** qui est overwrité avec l'adresse de la fonction **`system`**.
- Et enfin un chunk contenant la string `/bin/sh\x00` est freed en appelant la fonction delete, déclenchant **`__free_hook`** qui pointe vers system avec `/bin/sh\x00` comme paramètre.
- **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 overflow 1B pour consolider des chunks dans l'unsorted bin et obtenir un libc infoleak puis effectuer une fast bin attack pour overwrite malloc hook avec une one gadget address
- [**Robot Factory. BlackHat MEA CTF 2022**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/)
- On ne peut allouer que des chunks de taille plus grande que `0x100`.
- Overwrite `global_max_fast` en utilisant une Unsorted Bin attack (fonctionne 1/16 fois à cause de l'ASLR, car il faut modifier 12 bits, mais on doit modifier 16 bits).
- Fast Bin attack pour modifier un tableau global de chunks. Cela donne un primitive arbitrary read/write, ce qui permet de modifier la GOT et de faire pointer une fonction vers `system`.
## Références
- 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}}