14 KiB
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 ou un fast bin attack 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 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 notvictim->fd == av (arena)
, which means that the address where we want to write must have the address of the fake chunk in itsfd
position and that the fake chunkfd
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 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 callingfree(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
andvictim->fd == unsorted_chunks(av)
before unlinking. Because the insertion already wrotevictim
intobck->fd
(ourTARGET
), 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
andvictim->fd == unsorted_chunks(av)
; otherwise it aborts withmalloc(): 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 rewritesbck->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 or to pivot a later write‐what‐where).
- Avoid
__malloc_hook
/__free_hook
on new glibc: they were removed in 2.34. Avoidglobal_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 executesbck->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:
// 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 votreTARGET
contient le pointeur exactvictim
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 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
- 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
- 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 globaleglobal_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 versp64(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 écrasantglobal_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 <list_all_lock+15>: 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 taille0xfc*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 taille0x1f8
est créé pour obtenir un fast bin chunk dans__free_hook
qui est overwrité avec l'adresse de la fonctionsystem
. - 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
- 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
- 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}}