Unsorted Bin Attack
Reading time: 12 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Grundlegende Informationen
Für mehr Informationen darüber, was ein unsorted bin ist, siehe diese Seite:
Unsorted lists sind in der Lage, die Adresse von unsorted_chunks (av)
in das bk
-Feld des Chunks zu schreiben. Daher, wenn ein Angreifer die Adresse des bk
-Pointers in einem Chunk innerhalb des unsorted bin ändern kann, könnte er diese Adresse an eine beliebige Adresse schreiben, was hilfreich sein kann, um Glibc-Adressen zu leaken oder einige Schutzmechanismen zu umgehen.
Kurz gesagt erlaubt dieser Angriff, eine große Zahl an einer beliebigen Adresse zu setzen. Diese große Zahl ist eine Adresse, die eine Heap-Adresse oder eine Glibc-Adresse sein kann. Ein traditionelles Ziel war global_max_fast
, um fast bin bins mit größeren Größen zu erlauben (und vom unsorted bin attack zu einem fast bin attack überzugehen).
- Moderne Anmerkung (glibc ≥ 2.39):
global_max_fast
wurde zu einer 8‑Bit-Globalvariablen. Das blinde Schreiben eines Pointers dorthin via unsorted‑bin write überschreibt angrenzende libc-Daten und erhöht das fastbin-Limit nicht mehr zuverlässig. Bevorzuge andere Ziele oder andere Primitive gegen glibc 2.39+. Siehe "Moderne Einschränkungen" weiter unten und erwäge die Kombination mit anderen Techniken wie einem large bin attack oder einem fast bin attack, sobald du ein stabiles Primitive hast.
tip
Ein Blick auf das Beispiel in https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle und die Verwendung von 0x4000 und 0x5000 statt 0x400 und 0x500 als Chunk-Größen (um Tcache zu vermeiden) zeigt, dass heutzutage der Fehler malloc(): unsorted double linked list corrupted
ausgelöst wird.
Daher erfordert dieser unsorted bin attack jetzt (neben anderen Checks) auch, die doppelt verkettete Liste so zu reparieren, dass die Überprüfungen victim->bk->fd == victim
und victim->fd == av (arena)
erfüllt sind, was bedeutet, dass die Adresse, an die wir schreiben wollen, in ihrer fd
-Position die Adresse des gefälschten Chunks haben muss und dass das fd
des gefälschten Chunks auf die arena zeigt.
caution
Beachte, dass dieser Angriff das unsorted bin korruptiert (und damit auch small und large). Daher können wir jetzt nur noch Allocations aus den fast bin verwenden (ein komplexeres Programm könnte andere Allocations durchführen und abstürzen), und um dies auszulösen, müssen wir die gleiche Größe allozieren, sonst stürzt das Programm ab.
Beachte, dass das Überschreiben von global_max_fast
in diesem Fall helfen kann, sofern das fast bin alle anderen Allocations bis zum Abschluss des Exploits übernehmen kann.
Der Code von guyinatuxedo erklärt das sehr gut; wenn man jedoch die mallocs so ändert, dass sie Speicher groß genug allozieren, um nicht in einen Tcache zu fallen, sieht man, dass der zuvor erwähnte Fehler auftritt und diese Technik verhindert: malloc(): unsorted double linked list corrupted
Wie der Schreibvorgang tatsächlich abläuft
- Der unsorted‑bin write wird bei
free
ausgelöst, wenn der freigegebene Chunk am Kopf der unsorted list eingefügt wird. - Während der Einfügung führt der Allocator aus: bck = unsorted_chunks(av); fwd = bck->fd; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
- Wenn du
victim->bk
auf(mchunkptr)(TARGET - 0x10)
setzen kannst bevor dufree(victim)
aufrufst, wird die letzte Anweisung die Schreiboperation ausführen:*(TARGET) = victim
. - Später, wenn der Allocator das unsorted bin verarbeitet, werden Integritätsprüfungen (unter anderem) verifizieren, dass
bck->fd == victim
undvictim->fd == unsorted_chunks(av)
bevor unlinking erfolgt. Da die Einfügung bereitsvictim
inbck->fd
(unserTARGET
) geschrieben hat, können diese Checks erfüllt sein, wenn der Write erfolgreich war.
Moderne Einschränkungen (glibc ≥ 2.33)
Um unsorted‑bin Writes auf aktuellen glibc zuverlässig zu verwenden:
- Tcache-Interferenz: Für Größen, die in den tcache fallen, werden frees dorthin umgeleitet und berühren nicht das unsorted bin. Entweder
- mache Anfragen mit Größen > MAX_TCACHE_SIZE (≥ 0x410 auf 64‑Bit standardmäßig), oder
- fülle die entsprechende tcache-Bin (7 Einträge), sodass zusätzliche frees die global bins erreichen, oder
- wenn die Umgebung kontrollierbar ist, deaktiviere tcache (z. B. GLIBC_TUNABLES glibc.malloc.tcache_count=0).
- Integritätsprüfungen auf der unsorted list: auf dem nächsten Allocations-Pfad, der das unsorted bin prüft, kontrolliert glibc (vereinfacht):
bck->fd == victim
undvictim->fd == unsorted_chunks(av)
; andernfalls bricht es mitmalloc(): unsorted double linked list corrupted
ab.- Das bedeutet, dass die Adresse, die du targetest, zwei Writes tolerieren muss: zuerst
*(TARGET) = victim
zur Free‑Zeit; später, wenn der Chunk entfernt wird,*(TARGET) = unsorted_chunks(av)
(der Allocator schreibtbck->fd
zurück auf den Bin‑Kopf). Wähle Ziele, bei denen das Erzwingen eines großen Nicht‑Null‑Wertes nützlich ist. - Typische stabile Ziele in modernen Exploits:
- Applikations- oder globaler Zustand, der "große" Werte als Flags/Limits behandelt.
- Indirekte Primitive (z. B. Vorbereitung für einen nachfolgenden [fast bin attack](Fast Bin Attack) oder um einen späteren write‑what‑where zu pivotieren).
- Vermeide
__malloc_hook
/__free_hook
in neuem glibc: sie wurden in 2.34 entfernt. Vermeideglobal_max_fast
auf ≥ 2.39 (siehe vorherige Anmerkung). - Zu
global_max_fast
auf neueren glibc: - Auf glibc 2.39+ ist
global_max_fast
ein 8‑Bit‑Global. Der klassische Trick, einen Heap‑Pointer dorthin zu schreiben (um fastbins zu vergrößern), funktioniert nicht mehr sauber und wird wahrscheinlich angrenzenden Allocator‑Zustand korruptieren. Bevorzuge andere Strategien.
Minimales Exploit‑Rezept (moderner glibc)
Ziel: einen einzelnen arbiträren Write eines Heap‑Pointers an eine beliebige Adresse mittels des unsorted‑bin Insertions‑Primitives erreichen, ohne das Programm abstürzen zu lassen.
- Layout/Grooming
- Alloziere A, B, C mit Größen, die groß genug sind, um tcache zu umgehen (z. B. 0x5000). C verhindert Konsolidierung mit dem top chunk.
- Korruption
- Overflow von A in Bs Chunk‑Header, um
B->bk = (mchunkptr)(TARGET - 0x10)
zu setzen. - Auslösen
free(B)
. Zur Einfügezeit führt der Allocatorbck->fd = B
aus; daher wird*(TARGET) = B
.- Fortsetzung
- Wenn du weiter alloziieren willst und das Programm das unsorted bin verwendet, erwarte, dass der Allocator später
*(TARGET) = unsorted_chunks(av)
setzt. Beide Werte sind typischerweise groß und können ausreichen, um Größen-/Limit‑Semantiken in Zielen zu ändern, die nur auf "groß" prüfen.
Pseudocode‑Skelett:
// 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
• Wenn du tcache nicht über die Größe umgehen kannst, fülle das tcache-Bin für die gewählte Größe (7 frees), bevor du den korrupten Chunk freigibst, damit das free in das unsorted geht.
• Wenn das Programm beim nächsten Allocation sofort aufgrund der unsorted-bin‑Checks abbricht, überprüfe nochmal, dass victim->fd
noch dem Bin-Head entspricht und dass dein TARGET
nach dem ersten Schreibvorgang genau den victim
-Pointer enthält.
Unsorted Bin Infoleak Attack
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.
A similar attack used in this writeup, 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.
Referenzen & Weitere Beispiele
- https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap
- Das Ziel ist, eine globale Variable mit einem Wert größer als 4869 zu überschreiben, damit es möglich ist, die Flag zu bekommen, und PIE ist nicht aktiviert.
- Es ist möglich, Chunks beliebiger Größen zu erzeugen und es gibt einen Heap-Overflow mit der gewünschten Größe.
- Der Angriff beginnt mit dem Erstellen von 3 Chunks: chunk0 zum Ausnutzen des Overflows, chunk1, das überlaufen wird, und chunk2, damit der top chunk nicht die vorherigen konsolidiert.
- Danach wird chunk1 freed und chunk0 so überlaufen, dass das
bk
-Pointer von chunk1 zeigt auf:bk = magic - 0x10
- Dann wird chunk3 mit derselben Größe wie chunk1 alloziiert, was den unsorted bin attack auslöst und den Wert der globalen Variable verändert, wodurch es möglich wird, die Flag zu bekommen.
- https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html
- Die merge-Funktion ist verwundbar, weil, wenn beide übergebenen Indizes gleich sind, sie ein realloc darauf macht und es dann freed, aber dabei einen Pointer auf diesen freed Bereich zurückgibt, der weiterverwendet werden kann.
- Daher werden 2 Chunks erstellt: chunk0, das mit sich selbst gemerged wird, und chunk1, um eine Konsolidierung mit dem top chunk zu verhindern. Dann wird die merge-Funktion mit chunk0 zweimal aufgerufen, was zu einem use after free führt.
- Danach wird die
view
-Funktion mit Index 2 aufgerufen (das ist der Index des use after free Chunks), welche eine libc Adresse leaked. - Da das Binary Protections hat, nur mallocs größer als
global_max_fast
zu erlauben, sodass kein fastbin verwendet wird, wird ein unsorted bin attack benutzt, um die globale Variableglobal_max_fast
zu überschreiben. - Danach ist es möglich, die edit-Funktion mit Index 2 (dem use after free Pointer) aufzurufen und den
bk
-Pointer zu überschreiben, sodass er aufp64(global_max_fast-0x10)
zeigt. Dann erzeugt man einen neuen Chunk, der die zuvor kompromittierte free-Adresse (0x20) verwendet und so den unsorted bin attack auslöst, derglobal_max_fast
mit einem sehr großen Wert überschreibt, wodurch es nun möglich ist, Chunks in fast bins zu erstellen. - Jetzt wird ein fast bin attack durchgeführt:
- Zuerst wird entdeckt, dass es möglich ist, mit fast Chunks der Größe 200 an der Stelle von
__free_hook
zu arbeiten: 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
- Wenn es gelingt, einen fast Chunk der Größe 0x200 an dieser Stelle zu bekommen, wird es möglich sein, einen Funktionspointer zu überschreiben, der ausgeführt wird.
- Dafür wird ein neuer Chunk der Größe
0xfc
erstellt und die merge-Funktion mit diesem Pointer zweimal aufgerufen; so erhält man einen Pointer auf einen freed Chunk der Größe0xfc*2 = 0x1f8
im fast bin. - Dann wird die edit-Funktion auf diesem Chunk aufgerufen, um die
fd
-Adresse dieses fast bins zu ändern, sodass sie auf das vorherige__free_hook
zeigt. - Danach wird ein Chunk mit Größe
0x1f8
alloziiert, um aus dem fast bin den vorherigen nutzlosen Chunk zu bekommen; ein weiterer Chunk der Größe0x1f8
wird erstellt, um einen fast bin Chunk in__free_hook
zu erhalten, der mit der Adresse dersystem
-Funktion überschrieben wird. - Und schließlich wird ein Chunk, das den String
/bin/sh\x00
enthält, per delete freed, wodurch die__free_hook
-Funktion aufgerufen wird, die nun auf system zeigt und/bin/sh\x00
als Parameter übergeben bekommt. - CTF https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html
- Ein weiteres Beispiel, wie ein 1B-Overflow genutzt wird, um Chunks im unsorted bin zu konsolidieren und einen libc info leak zu bekommen und anschließend einen fast bin attack durchzuführen, um malloc hook mit einem one gadget zu überschreiben.
- Robot Factory. BlackHat MEA CTF 2022
- Wir können nur Chunks der Größe größer als
0x100
alloziieren. - Überschreibe
global_max_fast
mittels eines Unsorted Bin attack (funktioniert 1/16 mal wegen ASLR, weil wir 12 Bits ändern müssen, aber 16 Bits ändern). - Fast Bin attack, um ein globales Array von Chunks zu modifizieren. Das gibt ein beliebiges read/write-Primitive, was erlaubt, die GOT zu ändern und eine Funktion auf
system
zu setzen.
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
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.