Unsorted Bin Attack

Reading time: 12 minutes

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Βασικές Πληροφορίες

Για περισσότερες πληροφορίες σχετικά με το τι είναι ένα unsorted bin δες αυτή τη σελίδα:

Bins & Memory Allocations

Τα unsorted lists μπορούν να γράψουν τη διεύθυνση των unsorted_chunks (av) στο πεδίο bk του chunk. Επομένως, αν ένας attacker μπορεί να τροποποιήσει τη διεύθυνση του pointer bk σε ένα chunk μέσα στο unsorted bin, θα μπορούσε να γράψει αυτή τη διεύθυνση σε μια arbitrary διεύθυνση, κάτι που μπορεί να είναι χρήσιμο για να leak Glibc addresses ή να παρακαμφθούν κάποιες άμυνες.

Ουσιαστικά, αυτή η επίθεση επιτρέπει να τοποθετηθεί ένας μεγάλος αριθμός σε μια arbitrary διεύθυνση. Αυτός ο μεγάλος αριθμός είναι μια διεύθυνση, που μπορεί να είναι διεύθυνση του heap ή διεύθυνση της Glibc. Παραδοσιακός στόχος ήταν το global_max_fast για να επιτρέπεται η δημιουργία fast bin bins με μεγαλύτερα μεγέθη (και να περάσει κανείς από unsorted bin attack σε fast bin attack).

  • Σημείωση για νεότερες εκδόσεις (glibc ≥ 2.39): global_max_fast έγινε global 8‑bit. To να γράψεις τυφλά ένα pointer εκεί μέσω unsorted‑bin write θα αλλοιώσει δεδομένα της libc που βρίσκονται δίπλα και δεν θα αυξήσει αξιόπιστα το όριο των fastbin πια. Προτίμησε άλλους στόχους ή άλλες primitives όταν τρέχεις ενάντια σε glibc 2.39+. Δες τα "Modern constraints" παρακάτω και σκέψου συνδυασμό με τεχνικές όπως ένα large bin attack ή ένα fast bin attack μόλις έχεις ένα σταθερό primitive.

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

Σημείωσε ότι αυτή η επίθεση αλλοιώνει το unsorted bin (κατά συνέπεια και small/large). Έτσι πλέον μπορούμε να χρησιμοποιήσουμε μόνο allocations από το fast bin (ένα πιο πολύπλοκο πρόγραμμα μπορεί να κάνει άλλες allocations και να crashάρει), και για να το triggerάρουμε πρέπει να κάνουμε allocation του ίδιου μεγέθους αλλιώς το πρόγραμμα θα καταρρεύσει.

Σημείωσε ότι το overwrite του global_max_fast μπορεί να βοηθήσει σε αυτή την περίπτωση με την προσδοκία ότι το fast bin θα διαχειριστεί όλες τις υπόλοιπες allocations μέχρι να ολοκληρωθεί το exploit.

Ο κώδικας από τον guyinatuxedo το εξηγεί πολύ καλά, αν και αν τροποποιήσεις τα mallocs ώστε να δεσμεύουν μνήμη αρκετά μεγάλη ώστε να μην καταλήξουν σε Tcache μπορείς να δεις ότι εμφανίζεται το προαναφερθέν σφάλμα που αποτρέπει την τεχνική: malloc(): unsorted double linked list corrupted

Πώς γίνεται πραγματικά το write

  • Το unsorted‑bin write ενεργοποιείται στο free όταν το freed chunk εισάγεται στην κεφαλή του unsorted list.
  • Κατά την εισαγωγή, ο allocator εκτελεί bck = unsorted_chunks(av); fwd = bck->fd; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
  • Αν μπορείς να ορίσεις victim->bk σε (mchunkptr)(TARGET - 0x10) πριν καλέσεις free(victim), η τελική εντολή θα κάνει το write: *(TARGET) = victim.
  • Αργότερα, όταν ο allocator επεξεργαστεί το unsorted bin, οι έλεγχοι ακεραιότητας θα επαληθεύσουν (μεταξύ άλλων) ότι bck->fd == victim και victim->fd == unsorted_chunks(av) πριν το unlink. Επειδή η εισαγωγή ήδη έγραψε το victim στο bck->fd (τον δικό μας TARGET), αυτοί οι έλεγχοι μπορούν να ικανοποιηθούν αν το write πέτυχε.

Σύγχρονοι περιορισμοί (glibc ≥ 2.33)

Για να χρησιμοποιήσεις unsorted‑bin writes αξιόπιστα σε τρέχουσες glibc:

  • Παρεμβολή από Tcache: για μεγέθη που εμπίπτουν στο tcache, τα frees κατευθύνονται εκεί και δεν αγγίζουν το unsorted bin. Είτε
    • κάνε αιτήσεις με μεγέθη > MAX_TCACHE_SIZE (≥ 0x410 σε 64‑bit από προεπιλογή), ή
    • γέμισε την αντίστοιχη tcache bin (7 entries) ώστε τα επόμενα frees να φτάνουν στους global bins, ή
    • αν μπορείς να ελέγξεις το περιβάλλον, απενεργοποίησε το tcache (π.χ., GLIBC_TUNABLES glibc.malloc.tcache_count=0).
  • Έλεγχοι ακεραιότητας στο unsorted list: στην επόμενη διαδρομή allocation που εξετάζει το unsorted bin, η glibc ελέγχει (απλοποιημένα):
    • bck->fd == victim και victim->fd == unsorted_chunks(av); αλλιώς abort με malloc(): unsorted double linked list corrupted.
  • Αυτό σημαίνει ότι η διεύθυνση που στοχεύεις πρέπει να αντέχει δύο writes: πρώτα *(TARGET) = victim στο free‑time; αργότερα, καθώς το chunk αφαιρείται, *(TARGET) = unsorted_chunks(av) (ο allocator ξαναγράφει το bck->fd στην κεφαλή του bin). Επίλεξε στόχους όπου το να επιβάλεις απλώς μια μεγάλη μη‑μηδενική τιμή είναι χρήσιμο.
  • Τυπικοί σταθεροί στόχοι σε σύγχρονα exploits
    • Κατάσταση εφαρμογής ή global state που θεωρεί "μεγάλες" τιμές ως flags/όρια.
    • Έμμεσες primitives (π.χ., προετοιμασία για επόμενο [fast bin attack](Fast Bin Attack) ή για να pivot‑άρεις ένα μετέπειτα write‑what‑where).
    • Απόφυγε __malloc_hook/__free_hook σε νέες glibc: αφαιρέθηκαν στην 2.34. Απόφυγε global_max_fast σε ≥ 2.39 (βλ. παραπάνω σημείωση).
  • Σχετικά με το global_max_fast στις πρόσφατες glibc
    • Σε glibc 2.39+, global_max_fast είναι global 8‑bit. Το κλασικό κόλπο να γράψεις έναν heap pointer εκεί (για να μεγαλώσεις τα fastbins) δεν δουλεύει πλέον καθαρά και πιθανόν να κατεστρέψει το γειτονικό state του allocator. Προτίμησε άλλες στρατηγικές.

Ελάχιστη συνταγή εκμετάλλευσης (σύγχρονη glibc)

Στόχος: επίτευξη ενός single arbitrary write ενός heap pointer σε μια arbitrary διεύθυνση χρησιμοποιώντας το unsorted‑bin insertion primitive, χωρίς crash.

  • Layout/grooming
    • Δέσμευσε A, B, C με μεγέθη αρκετά μεγάλα ώστε να παρακάμπτουν το tcache (π.χ., 0x5000). Το C αποτρέπει consolidation με το top chunk.
  • Corruption
    • Overflow από το A στο header του B για να ορίσεις B->bk = (mchunkptr)(TARGET - 0x10).
  • Trigger
    • free(B). Κατά την εισαγωγή ο allocator εκτελεί bck->fd = B, επομένως *(TARGET) = B.
  • Συνέχεια
    • Αν σκοπεύεις να συνεχίσεις να κάνεις allocations και το πρόγραμμα χρησιμοποιεί το unsorted bin, περίμενε ότι ο allocator αργότερα θα θέσει *(TARGET) = unsorted_chunks(av). Και οι δύο τιμές είναι συνήθως μεγάλες και μπορεί να είναι αρκετές για να αλλάξουν semantics μεγέθους/ορίου σε στόχους που απλά ελέγχουν για "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

• Αν δεν μπορείς να παρακάμψεις το tcache με το μέγεθος, γέμισε το tcache bin για το επιλεγμένο μέγεθος (7 frees) πριν απελευθερώσεις το corrupted chunk έτσι ώστε το free να πάει στο unsorted. • Αν το πρόγραμμα τερματίζει αμέσως στην επόμενη allocation λόγω των unsorted-bin checks, εξέτασε ξανά ότι victim->fd εξακολουθεί να ισούται με το bin head και ότι το TARGET σου κρατάει τον ακριβή pointer του victim μετά το πρώτο write.

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.

References & Other examples

  • https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap
  • Ο στόχος είναι να αντικατασταθεί μια global μεταβλητή με μια τιμή μεγαλύτερη από 4869 έτσι ώστε να είναι δυνατή η λήψη του flag και το PIE να μην είναι ενεργοποιημένο.
  • Είναι δυνατό να δημιουργηθούν chunks με αυθαίρετα μεγέθη και υπάρχει ένα heap overflow με το επιθυμητό μέγεθος.
  • Η επίθεση ξεκινά δημιουργώντας 3 chunks: chunk0 για να εκμεταλλευτεί το overflow, chunk1 που θα υπερχείλιζε και chunk2 ώστε το top chunk να μην συγχωνεύσει τα προηγούμενα.
  • Έπειτα, το chunk1 απελευθερώνεται και το chunk0 υπερχείλιζε έτσι ώστε ο bk pointer του chunk1 να δείχνει σε: bk = magic - 0x10
  • Έπειτα, το chunk3 δεσμεύεται με το ίδιο μέγεθος όπως το chunk1, το οποίο θα ενεργοποιήσει το unsorted bin attack και θα τροποποιήσει την τιμή της global μεταβλητής, καθιστώντας εφικτή τη λήψη του flag.
  • https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html
  • Η merge function είναι ευάλωτη γιατί αν και τα δύο indexes που περνιούνται είναι τα ίδια, θα κάνει realloc πάνω σε αυτό και μετά θα το free-άρει αλλά θα επιστρέψει έναν pointer σε εκείνη την freed περιοχή που μπορεί να χρησιμοποιηθεί.
  • Επομένως, δημιουργούνται 2 chunks: chunk0 που θα συγχωνευτεί με τον εαυτό του και chunk1 για να αποτραπεί η ενοποίηση με το top chunk. Έπειτα, η merge function καλείται με το chunk0 δύο φορές, κάτι που θα προκαλέσει use after free.
  • Στη συνέχεια, η view function καλείται με index 2 (που είναι το index του use after free chunk), η οποία θα leak a libc address.
  • Καθώς το binary έχει προστασίες ώστε να malloc-άρει μόνο μεγέθη μεγαλύτερα του global_max_fast, οπότε δεν χρησιμοποιείται fastbin, θα χρησιμοποιηθεί μια unsorted bin attack για να αντικαταστήσει την global μεταβλητή global_max_fast.
  • Έπειτα είναι δυνατό να καλέσεις την edit function με index 2 (τον use after free pointer) και να υπεργράψεις τον bk pointer ώστε να δείχνει σε p64(global_max_fast-0x10). Στη συνέχεια, η δημιουργία ενός νέου chunk θα χρησιμοποιήσει τη προηγουμένως συμβιβασμένη διεύθυνση free (0x20) και θα trigger the unsorted bin attack, υπεργράφοντας το global_max_fast με μια πολύ μεγάλη τιμή, επιτρέποντας τώρα τη δημιουργία chunks στα fast bins.
  • Τώρα εκτελείται μια fast bin attack:
  • Πρώτον ανακαλύπτεται ότι είναι δυνατόν να δουλέψεις με fast chunks of size 200 στην τοποθεσία του __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

  • Αν καταφέρουμε να βάλουμε ένα fast chunk μεγέθους 0x200 σε αυτήν την τοποθεσία, θα είναι δυνατό να υπεγράψουμε έναν δείκτη συνάρτησης που θα εκτελεστεί.
  • Για αυτό, δημιουργείται ένα νέο chunk μεγέθους 0xfc και η merge function καλείται με αυτόν τον pointer δύο φορές, έτσι αποκτούμε έναν pointer σε ένα freed chunk μεγέθους 0xfc*2 = 0x1f8 στο fast bin.
  • Στη συνέχεια, η edit function καλείται σε αυτό το chunk για να τροποποιήσει τη διεύθυνση fd αυτού του fast bin ώστε να δείχνει στην προηγούμενη συνάρτηση __free_hook.
  • Μετά, δημιουργείται ένα chunk με μέγεθος 0x1f8 για να ανακτήσει από το fast bin το προηγούμενο άχρηστο chunk, οπότε δημιουργείται ένα ακόμα chunk μεγέθους 0x1f8 για να πάρει ένα fast bin chunk στο __free_hook το οποίο υπεγράφη με τη διεύθυνση της συνάρτησης system.
  • Και τέλος ένα chunk που περιέχει το string /bin/sh\x00 απελευθερώνεται καλώντας την delete function, ενεργοποιώντας τη συνάρτηση __free_hook που δείχνει στη system με /bin/sh\x00 ως παράμετρο.
  • CTF https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html
  • Ένα ακόμα παράδειγμα εκμετάλλευσης ενός 1B overflow για να ενοποιηθούν chunks στο unsorted bin και να αποκτηθεί ένα libc infoleak και στη συνέχεια να εκτελεστεί ένα fast bin attack για να υπεργραφεί το malloc hook με μια one gadget address
  • Robot Factory. BlackHat MEA CTF 2022
  • Μπορούμε να δεσμεύσουμε μόνο chunks μεγέθους μεγαλύτερου του 0x100.
  • Υπεγράψτε το global_max_fast χρησιμοποιώντας μια Unsorted Bin attack (λειτουργεί 1/16 φορές λόγω ASLR, επειδή χρειάζεται να τροποποιήσουμε 12 bits, αλλά πρέπει να τροποποιήσουμε 16 bits).
  • Fast Bin attack για να τροποποιήσει έναν global πίνακα chunks. Αυτό δίνει ένα arbitrary read/write primitive, το οποίο επιτρέπει την τροποποίηση του GOT και να ορίσει κάποια συνάρτηση να δείχνει στη system.

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

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks