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
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Informazioni di base
Per maggiori informazioni su cosa sia un unsorted bin controlla questa pagina:
Le unsorted lists sono in grado di scrivere l'indirizzo di unsorted_chunks (av)
nel campo bk
del chunk. Pertanto, se un attacker può modificare l'indirizzo del puntatore bk
in un chunk all'interno dell'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 certe difese.
Quindi, fondamentalmente, questo attacco permette di impostare un grande valore in un indirizzo arbitrario. Questo grande valore è un indirizzo, che potrebbe essere un indirizzo heap o un indirizzo Glibc. Un obiettivo tradizionale era global_max_fast
per permettere di creare fast bin con dimensioni maggiori (e passare da un unsorted bin attack a un fast bin attack).
- Nota moderna (glibc ≥ 2.39):
global_max_fast
è diventata una variabile globale a 8 bit. Scrivere ciecamente un pointer lì tramite una scrittura su unsorted-bin sovrascriverà dati libc adiacenti e non aumenterà più in modo affidabile il limite dei fastbin. Preferire altri target o altri 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 ottenuto un primitive stabile.
tip
T> Prendendo un'occhiata all'esempio fornito in https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle e usando 0x4000 e 0x5000 invece di 0x400 e 0x500 come dimensioni dei chunk (per evitare Tcache) è possibile vedere che oggigiorno l'errore malloc(): unsorted double linked list corrupted
viene scatenato.
Pertanto, questo unsorted bin attack ora (tra altri controlli) richiede anche la capacità di sistemare la double linked list così che questo non venga bypassato victim->bk->fd == victim
o victim->fd == av (arena)
, il che significa che l'indirizzo dove vogliamo scrivere deve avere l'indirizzo del fake chunk nella sua posizione fd
e che il fd
del fake chunk punti all'arena.
caution
Nota che questo attacco corrompe l'unsorted bin (e quindi anche small e large). Quindi ora possiamo usare solo allocazioni dai fast bin (un programma più complesso potrebbe fare altre allocazioni e crashare), e per attivarlo dobbiamo allocare della stessa dimensione altrimenti il programma crasha.
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, anche se se modifichi i malloc per allocare memoria abbastanza grande da non finire in Tcache puoi vedere che appare l'errore menzionato precedentemente impedendo questa tecnica: malloc(): unsorted double linked list corrupted
Come avviene effettivamente la scrittura
- La scrittura su unsorted-bin viene innescata su
free
quando il chunk liberato è inserito in testa alla 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 chiamarefree(victim)
, l'istruzione finale eseguirà la scrittura:*(TARGET) = victim
. - Successivamente, quando l'allocator processa l'unsorted bin, i controlli di integrità verificheranno (tra le altre cose) che
bck->fd == victim
evictim->fd == unsorted_chunks(av)
prima di effettuare l'unlink. Poiché l'inserimento ha già scrittovictim
inbck->fd
(il nostroTARGET
), questi controlli possono risultare soddisfatti se la scrittura è riuscita.
Vincoli moderni (glibc ≥ 2.33)
Per usare le scritture su unsorted‑bin in modo affidabile sulle glibc attuali:
- Interferenza di Tcache: per le dimensioni che rientrano in tcache, le free vengono deviate lì e non toccheranno l'unsorted bin. Oppure
- fare richieste con dimensioni > MAX_TCACHE_SIZE (≥ 0x410 su 64‑bit di default), oppure
- riempire il corrispondente tcache bin (7 voci) così che ulteriori free 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
evictim->fd == unsorted_chunks(av)
; altrimenti abortisce conmalloc(): 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 riscrivebck->fd
tornando alla testa del bin). Scegli target dove forzare semplicemente un valore grande non-zero sia utile. - Target tipici stabili negli exploit moderni
- Stato dell'applicazione o variabili globali che trattano valori "grandi" come flag/limiti.
- Primitive indirette (es., predisporre per un successivo [fast bin attack](Fast Bin Attack) o per pivotare una successiva write-what-where).
- Evita
__malloc_hook
/__free_hook
nelle nuove glibc: sono stati rimossi in 2.34. Evitaglobal_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. La tecnica classica di scriverci un pointer heap per ingrandire i fastbins non funziona più pulitamente ed è probabile che corrompa lo stato dell'allocator adiacente. Preferire altre strategie.
Ricetta di sfruttamento minima (glibc moderno)
Obiettivo: ottenere una singola scrittura arbitraria di un pointer heap in un indirizzo arbitrario usando il primitive di inserimento unsorted‑bin, senza far crashare.
- Layout/preparazione
- Allocare A, B, C con dimensioni abbastanza grandi da bypassare tcache (es., 0x5000). C impedisce la consolidazione con il top chunk.
- Corruzione
- Overflow da A nell'header di B per impostare
B->bk = (mchunkptr)(TARGET - 0x10)
. - Attivazione
free(B)
. Al momento dell'inserimento l'allocator eseguebck->fd = B
, quindi*(TARGET) = B
.- Proseguimento
- Se hai intenzione di continuare ad allocare e il programma usa l'unsorted bin, aspettati che l'allocator in seguito imposti
*(TARGET) = unsorted_chunks(av)
. Entrambi i valori sono tipicamente grandi e potrebbero essere sufficienti a cambiare la semantica di size/limite in target che controllano solo per "grande".
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
• Se non riesci a bypassare tcache tramite la size, riempi la tcache bin per la size scelta (7 frees) prima di freeare il chunk corrotto in modo che il free vada in unsorted.
• Se il programma abortisce immediatamente alla successiva allocazione a causa degli unsorted-bin checks, ricontrolla che victim->fd
sia ancora uguale alla 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 basilare. I chunk nell'unsorted bin conterranno dei puntatori. Il primo chunk nell'unsorted bin avrà effettivamente i link fd
e bk
che puntano a una parte del main arena (Glibc).
Quindi, se puoi mettere un chunk dentro un unsorted bin e leggerlo (use after free) o allocarlo di nuovo senza sovrascrivere almeno 1 dei puntatori per poi leggerlo, puoi ottenere un Glibc info leak.
A similar attack used in this writeup, è stato abusare di una struttura di 4 chunk (A, B, C e D - D è usato solo per prevenire la consolidation con il top chunk) in modo che un null byte overflow in B venisse usato per far sì che C indicasse che B era unused. Inoltre, in B il campo prev_size
è stato modificato così la size invece di essere la size di B era A+B.
Poi C è stato deallocated e consolidated con A+B (ma B era ancora in uso). È stato allocato un nuovo chunk di size A e poi gli indirizzi libc leakati sono stati scritti in B dai quali sono stati leaked.
References & Other examples
- 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 e c'è un heap overflow della size 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
bk
pointer di chunk1 punti a:bk = magic - 0x10
- Successivamente, 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, farà realloc su di esso e poi lo freea restituendo un puntatore a quella regione freed che può essere riutilizzata.
- Di conseguenza, si creano 2 chunk: chunk0 che verrà merged con se stesso e chunk1 per evitare la consolidazione con il top chunk. Poi viene chiamata la funzione merge con chunk0 due volte, causando un use after free.
- Poi viene chiamata la funzione
view
con l'indice 2 (che è l'indice del chunk use after free), che farà leak di un indirizzo libc. - Poiché il binario ha protezioni che permettono soltanto malloc di size maggiori di
global_max_fast
quindi non vengono usati fastbin, verrà usato un unsorted bin attack per sovrascrivere la variabile globaleglobal_max_fast
. - Successivamente, è possibile chiamare la funzione edit con l'indice 2 (il puntatore use after free) e sovrascrivere il puntatore
bk
per puntare ap64(global_max_fast-0x10)
. Poi, creando un nuovo chunk si userà l'indirizzo freed precedentemente compromesso (0x20) che innescherà l'unsorted bin attack sovrascrivendoglobal_max_fast
con un valore molto grande, permettendo ora di creare chunk nelle 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 location, sarà possibile sovrascrivere un function pointer che verrà eseguito.
- Per questo, viene creato un nuovo chunk di size
0xfc
e la funzione merged viene chiamata con quel puntatore due volte; in questo modo otteniamo un puntatore a un chunk freed di size0xfc*2 = 0x1f8
nella fast bin. - Poi viene chiamata la funzione edit su questo chunk per modificare l'indirizzo
fd
di questa fast bin per puntare al precedente function__free_hook
. - Successivamente, viene creato un chunk di size
0x1f8
per recuperare dalla fast bin il chunk inutile precedente, quindi un altro chunk di size0x1f8
viene creato per ottenere un fast bin chunk in__free_hook
che viene sovrascritto con l'indirizzo della funzionesystem
. - Infine un chunk contenente la stringa
/bin/sh\x00
viene freed chiamando la funzione delete, innescando la__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 address
- 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 in pratica dobbiamo modificare 16 bit). - Fast Bin attack per modificare un array globale di chunk. Questo fornisce un primitivo di read/write arbitrario, che permette di modificare la GOT e far puntare alcune funzioni a
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
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
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.