Unsorted Bin Attack

Reading time: 12 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Informazioni di base

Per maggiori informazioni su cos'è un unsorted bin controlla questa pagina:

Bins & Memory Allocations

Le unsorted lists sono in grado di scrivere l'indirizzo di unsorted_chunks (av) nel campo bk del chunk. Quindi, se un attaccante può modificare l'indirizzo del puntatore bk in un chunk presente nell'unsorted bin, potrebbe riuscire a scrivere quell'indirizzo in un indirizzo arbitrario, il che può essere utile per ottenere un leak di indirizzi Glibc o bypassare qualche protezione.

In sostanza, questo attacco permette di impostare un numero grande in un indirizzo arbitrario. Questo numero grande è un indirizzo, che potrebbe essere un indirizzo heap o un indirizzo Glibc. Un bersaglio tradizionale era global_max_fast per permettere la creazione di fast bin di dimensioni maggiori (e passare da un unsorted bin attack a un fast bin attack).

  • Nota moderna (glibc ≥ 2.39): global_max_fast è diventato una globale a 8 bit. Scriverci ciecamente un puntatore tramite una write da unsorted-bin sovrascriverà dati libc adiacenti e non aumenterà più in modo affidabile il limite di fastbin. Preferire altri target o primitive quando si lavora contro glibc 2.39+. Vedi "Modern constraints" sotto e considera di combinare con altre tecniche come un large bin attack o un fast bin attack una volta che si dispone di una primitive stabile.

tip

T> ake 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

Nota che questo attacco corrompe l'unsorted bin (e quindi anche small e large). Perciò ora possiamo usare solo allocazioni dai fast bin (un programma più complesso potrebbe fare altre allocazioni e crashare), e per attivarlo dobbiamo allocare lo stesso size o il programma andrà in crash.

Nota che sovrascrivere global_max_fast potrebbe aiutare in questo caso, fidandosi che il fast bin si occuperà di tutte le altre allocazioni fino al completamento dell'exploit.

Il codice di guyinatuxedo lo spiega molto bene; tuttavia se modifichi le malloc per allocare memoria abbastanza grande da non finire in Tcache puoi vedere che l'errore menzionato prima compare impedendo questa tecnica: malloc(): unsorted double linked list corrupted

Come avviene effettivamente la scrittura

  • The unsorted-bin write is triggered on free when the freed chunk is inserted at the head of the unsorted list.
  • Durante l'inserimento, l'allocator esegue bck = unsorted_chunks(av); fwd = bck->fd; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
  • Se puoi impostare victim->bk a (mchunkptr)(TARGET - 0x10) prima di chiamare free(victim), l'istruzione finale eseguirà la scrittura: *(TARGET) = victim.
  • Più tardi, quando l'allocator processerà l'unsorted bin, i controlli di integrità verificheranno (tra le altre cose) che bck->fd == victim e victim->fd == unsorted_chunks(av) prima di unlinkare. Poiché l'inserimento ha già scritto victim in bck->fd (il nostro TARGET), questi controlli possono essere soddisfatti se la scrittura ha avuto successo.

Modern constraints (glibc ≥ 2.33)

Per usare le unsorted‑bin writes in modo affidabile sulle glibc attuali:

  • Interferenza di Tcache: per le dimensioni che rientrano nel tcache, i free vengono deviati lì e non toccheranno l'unsorted bin. Oppure
  • fare richieste con size > MAX_TCACHE_SIZE (≥ 0x410 su 64‑bit di default), oppure
  • riempire il corrispondente tcache bin (7 entry) in modo che i free successivi raggiungano i global bins, oppure
  • se l'ambiente è controllabile, disabilitare tcache (es., GLIBC_TUNABLES glibc.malloc.tcache_count=0).
  • Controlli di integrità sull'unsorted list: nel percorso di allocazione successivo che esamina l'unsorted bin, glibc controlla (semplificato):
  • bck->fd == victim e victim->fd == unsorted_chunks(av); altrimenti abortisce con malloc(): unsorted double linked list corrupted.
  • Questo significa che l'indirizzo che punti deve tollerare due scritture: prima *(TARGET) = victim al momento del free; più tardi, mentre il chunk viene rimosso, *(TARGET) = unsorted_chunks(av) (l'allocator riscrive bck->fd tornando alla testa del bin). Scegli target dove forzare semplicemente un valore grande non-zero sia utile.
  • Tipici target stabili negli exploit moderni
  • Stato dell'applicazione o globale che tratta valori "grandi" come flag/limiti.
  • Primitive indirette (es., preparare un successivo [fast bin attack](Fast Bin Attack) o pivotare una write-what-where successiva).
  • Evitare __malloc_hook/__free_hook nelle glibc nuove: sono stati rimossi in 2.34. Evitare global_max_fast su ≥ 2.39 (vedi nota precedente).
  • Su global_max_fast nelle glibc recenti
  • Su glibc 2.39+, global_max_fast è una globale a 8‑bit. Il trucco classico di scriverci un puntatore heap (per ingrandire i fastbins) non funziona più bene e probabilmente corromperà lo stato dell'allocator adiacente. Preferire altre strategie.

Ricetta minima per lo sfruttamento (glibc moderna)

Obiettivo: ottenere una singola scrittura arbitraria di un puntatore heap in un indirizzo arbitrario usando la primitive di inserimento dell'unsorted‑bin, senza causare crash.

  • Layout/grooming
  • Allocare A, B, C con dimensioni abbastanza grandi da bypassare il tcache (es., 0x5000). C previene la consolidazione con il top chunk.
  • Corruzione
  • Overflow da A nell'header di B per impostare B->bk = (mchunkptr)(TARGET - 0x10).
  • Trigger
  • free(B). Al momento dell'inserimento l'allocator esegue bck->fd = B, quindi *(TARGET) = B.
  • Continuazione
  • Se prevedi di continuare ad allocare e il programma usa l'unsorted bin, aspettati che l'allocator più tardi imposti *(TARGET) = unsorted_chunks(av). Entrambi i valori sono tipicamente grandi e possono essere sufficienti per modificare la semantica di size/limite in target che controllano solo per "grande".

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

• Se non puoi bypassare tcache tramite la dimensione, riempi il tcache bin per la dimensione scelta (7 frees) prima di freeare il chunk corrotto così il free va all'unsorted. • Se il programma abortisce immediatamente alla successiva allocazione a causa dei controlli dell'unsorted-bin, ricontrolla che victim->fd sia ancora uguale al bin head e che il tuo TARGET contenga l'esatto puntatore victim dopo la prima scrittura.

Unsorted Bin Infoleak Attack

Questo è in realtà un concetto molto semplice. I chunk nell'unsorted bin avranno dei puntatori. Il primo chunk nell'unsorted bin avrà effettivamente i link fd e bk puntati a una parte della main arena (Glibc).
Quindi, se puoi mettere un chunk dentro un unsorted bin e leggerlo (use after free) o riallocarlo senza sovrascrivere almeno 1 dei puntatori per poi leggerlo, puoi ottenere un Glibc info leak.

Un attack used in this writeup simile consisteva nell'abusare di una struttura di 4 chunk (A, B, C e D - D serve solo a prevenire la consolidazione con il top chunk) così un null byte overflow in B è stato usato per far sì che C indicasse che B era unused. Inoltre, in B il campo prev_size è stato modificato in modo che la size invece di essere la size di B fosse A+B.
Poi C è stato deallocato e consolidato con A+B (ma B era ancora in use). Un nuovo chunk di size A è stato allocato e poi gli indirizzi libc leaked sono stati scritti in B da dove sono stati leakati.

Riferimenti & Altri esempi

  • https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap
  • L'obiettivo è sovrascrivere una variabile globale con un valore maggiore di 4869 in modo da poter ottenere la flag e PIE non è abilitato.
  • È possibile generare chunk di dimensioni arbitrarie ed esiste un heap overflow della dimensione desiderata.
  • L'attacco inizia creando 3 chunk: chunk0 per abusare dell'overflow, chunk1 da overfloware e chunk2 affinché il top chunk non consolidi i precedenti.
  • Poi, chunk1 viene freed e chunk0 viene overflowato in modo che il puntatore bk di chunk1 punti a: bk = magic - 0x10
  • Poi, chunk3 viene allocato con la stessa size di chunk1, il che innescherà l'unsorted bin attack e modificherà il valore della variabile globale, rendendo possibile ottenere la flag.
  • https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html
  • La funzione merge è vulnerabile perché se entrambi gli indici passati sono lo stesso, verrà eseguito un realloc su di esso e poi freed ma verrà restituito un puntatore a quella regione freed che può essere riutilizzata.
  • Pertanto, vengono creati 2 chunk: chunk0 che verrà mergiato con se stesso e chunk1 per evitare la consolidazione con il top chunk. Poi la funzione merge viene chiamata con chunk0 due volte causando un use after free.
  • Poi, la funzione view viene chiamata con l'indice 2 (che è l'indice del chunk use after free), il che farà leakare un indirizzo libc.
  • Poiché il binario ha protezioni che permettono solo malloc di size maggiori di global_max_fast quindi non vengono usati i fastbin, verrà usato un unsorted bin attack per sovrascrivere la variabile globale global_max_fast.
  • Successivamente, è possibile chiamare la funzione edit con l'indice 2 (il puntatore use after free) e sovrascrivere il puntatore bk per puntare a p64(global_max_fast-0x10). Poi, creando un nuovo chunk si userà l'indirizzo freed precedentemente compromesso (0x20) e si innescherà l'unsorted bin attack sovrascrivendo global_max_fast con un valore molto grande, permettendo ora di creare chunk nei fast bin.
  • Ora viene eseguito un fast bin attack:
  • Prima di tutto si scopre che è possibile lavorare con fast chunk di size 200 nella posizione di __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

  • Se riusciamo a ottenere un fast chunk di size 0x200 in questa posizione, sarà possibile sovrascrivere un puntatore a funzione che verrà eseguito.
  • Per questo, viene creato un nuovo chunk di size 0xfc e la funzione merge viene chiamata con quel puntatore due volte; in questo modo otteniamo un puntatore a un chunk freed di size 0xfc*2 = 0x1f8 nel fast bin.
  • Poi, la funzione edit viene chiamata su questo chunk per modificare l'indirizzo fd di questo fast bin in modo da puntare al precedente __free_hook.
  • Poi, viene creato un chunk di size 0x1f8 per recuperare dal fast bin il chunk inutile precedente e successivamente un altro chunk di size 0x1f8 per ottenere un fast bin chunk in __free_hook che viene sovrascritto con l'indirizzo della funzione system.
  • Infine viene freed un chunk contenente la stringa /bin/sh\x00 chiamando la funzione delete, innescando __free_hook che punta a system con /bin/sh\x00 come parametro.
  • CTF https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html
  • Un altro esempio di abuso di un overflow di 1B per consolidare chunk nell'unsorted bin e ottenere un libc infoleak e poi eseguire un fast bin attack per sovrascrivere malloc hook con un one gadget.
  • Robot Factory. BlackHat MEA CTF 2022
  • Possiamo allocare solo chunk di size maggiore di 0x100.
  • Sovrascrivere global_max_fast usando un Unsorted Bin attack (funziona 1/16 volte a causa di ASLR, perché dobbiamo modificare 12 bit, ma dobbiamo modificare 16 bit).
  • Fast Bin attack per modificare un array globale di chunk. Questo fornisce un primitive di arbitrary read/write, che permette di modificare la GOT e puntare qualche funzione a system.

Riferimenti

  • 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

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks