Unsorted Bin Attack
Reading time: 12 minutes
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le đŹ groupe Discord ou le groupe telegram ou suivez-nous sur Twitter đŠ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépÎts github.
Basic Information
Pour plus d'informations sur ce qu'est un unsorted bin, consultez cette page :
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 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 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](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 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 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
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le đŹ groupe Discord ou le groupe telegram ou suivez-nous sur Twitter đŠ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépÎts github.