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

Basic Information

Pour plus d'informations sur ce qu'est un unsorted bin, consultez cette page :

Bins & Memory Allocations

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 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](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. 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 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 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 <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 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
  • 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