Translated ['src/binary-exploitation/libc-heap/unsorted-bin-attack.md']

This commit is contained in:
Translator 2025-08-28 16:56:25 +00:00
parent 3af5806a00
commit bb760ece9c

View File

@ -2,54 +2,111 @@
{{#include ../../banners/hacktricks-training.md}}
## Basic Information
## Informazioni di base
Per maggiori informazioni su cosa sia un unsorted bin controlla questa pagina:
Per ulteriori informazioni su cosa sia un unsorted bin, controlla questa pagina:
{{#ref}}
bins-and-memory-allocations.md
{{#endref}}
Le liste non ordinate sono in grado di scrivere l'indirizzo in `unsorted_chunks (av)` nell'indirizzo `bk` del chunk. Pertanto, se un attaccante può **modificare l'indirizzo del puntatore `bk`** in un chunk all'interno dell'unsorted bin, potrebbe essere in grado di **scrivere quell'indirizzo in un indirizzo arbitrario**, il che potrebbe essere utile per rivelare indirizzi Glibc o bypassare alcune difese.
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 consente di **impostare un grande numero in un indirizzo arbitrario**. Questo grande numero è un indirizzo, che potrebbe essere un indirizzo heap o un indirizzo Glibc. Un obiettivo tipico è **`global_max_fast`** per consentire di creare fast bin con dimensioni maggiori (e passare da un attacco unsorted bin a un attacco fast bin).
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](large-bin-attack.md) o un [fast bin attack](fast-bin-attack.md) una volta ottenuto un primitive stabile.
> [!TIP]
> Dare un'occhiata all'esempio fornito in [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle) e utilizzare 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 attivato.
> T> Prendendo un'occhiata all'esempio fornito in [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](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 attacco unsorted bin ora (tra i vari controlli) richiede anche di essere in grado di riparare la lista doppiamente collegata in modo che venga bypassato `victim->bk->fd == victim` o non `victim->fd == av (arena)`, il che significa che l'indirizzo in cui vogliamo scrivere deve avere l'indirizzo del fake chunk nella sua posizione `fd` e che il fake chunk `fd` punta all'arena.
> 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 (quindi anche small e large). Quindi possiamo solo **utilizzare allocazioni dal fast bin ora** (un programma più complesso potrebbe fare altre allocazioni e andare in crash), e per attivare questo dobbiamo **allocare la stessa dimensione o il programma andrà in crash.**
> 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 sarà in grado di gestire tutte le altre allocazioni fino al completamento dell'exploit.
> 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**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) lo spiega molto bene, anche se se modifichi le malloc per allocare memoria abbastanza grande da non finire in un Tcache puoi vedere che l'errore precedentemente menzionato appare impedendo questa tecnica: **`malloc(): unsorted double linked list corrupted`**
Il codice di [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) 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 chiamare `free(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` e `victim->fd == unsorted_chunks(av)` prima di effettuare l'unlink. Poiché l'inserimento ha già scritto `victim` in `bck->fd` (il nostro `TARGET`), questi controlli possono risultare soddisfatti se la scrittura è riuscita.
## Vincoli moderni (glibc ≥ 2.33)
Per usare le scritture su unsortedbin 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 64bit 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` 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.
- 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]({{#ref}}fast-bin-attack.md{{#endref}}) o per pivotare una successiva write-what-where).
- Evita `__malloc_hook`/`__free_hook` nelle nuove glibc: sono stati rimossi in 2.34. Evita `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. 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 unsortedbin, 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 esegue `bck->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:
```c
// 64-bit glibc 2.352.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 Bs 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 avranno dei puntatori. Il primo chunk nell'unsorted bin avrà effettivamente i link **`fd`** e **`bk`** **che puntano a una parte dell'arena principale (Glibc)**.\
Pertanto, se puoi **mettere un chunk all'interno di un unsorted bin e leggerlo** (use after free) o **allocarlo di nuovo senza sovrascrivere almeno 1 dei puntatori** per poi **leggerlo**, puoi avere una **Glibc info leak**.
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**.
Un simile [**attacco utilizzato in questo writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html) è stato quello di abusare di una struttura di 4 chunk (A, B, C e D - D serve solo a prevenire la consolidazione con il top chunk) quindi un overflow di byte nullo in B è stato utilizzato per far indicare a C che B era inutilizzato. Inoltre, in B i dati `prev_size` sono stati modificati in modo che la dimensione invece di essere la dimensione di B fosse A+B.\
Poi C è stato deallocato e consolidato con A+B (ma B era ancora in uso). Un nuovo chunk di dimensione A è stato allocato e poi gli indirizzi di libc sono stati scritti in B da dove sono stati rivelati.
A similar [**attack used in this writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html), è 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**](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 il flag e PIE non è abilitato.
- È possibile generare chunk di dimensioni arbitrarie e c'è un overflow heap con la dimensione desiderata.
- L'attacco inizia creando 3 chunk: chunk0 per abusare dell'overflow, chunk1 da sovrascrivere e chunk2 in modo che il top chunk non consolidi i precedenti.
- Poi, chunk1 viene liberato e chunk0 viene sovrascritto in modo che il puntatore `bk` di chunk1 punti a: `bk = magic - 0x10`
- Poi, chunk3 viene allocato con la stessa dimensione di chunk1, il che attiverà l'attacco unsorted bin e modificherà il valore della variabile globale, rendendo possibile ottenere il flag.
- 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**](https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html)
- La funzione di merge è vulnerabile perché se entrambi gli indici passati sono lo stesso verrà realloc su di esso e poi liberato ma restituendo un puntatore a quella regione liberata che può essere utilizzata.
- Pertanto, **vengono creati 2 chunk**: **chunk0** che verrà unito con se stesso e chunk1 per prevenire la consolidazione con il top chunk. Poi, la **funzione di merge viene chiamata con chunk0** due volte, il che causerà un use after free.
- Poi, la funzione **`view`** viene chiamata con l'indice 2 (che è l'indice del chunk use after free), il che **rivelerà un indirizzo libc**.
- Poiché il binario ha protezioni per allocare solo dimensioni maggiori di **`global_max_fast`**, quindi non viene utilizzato alcun fastbin, verrà utilizzato un attacco unsorted bin per sovrascrivere la variabile globale `global_max_fast`.
- Poi, è possibile chiamare la funzione di 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 utilizzerà l'indirizzo libero precedentemente compromesso (0x20) che **attiverà l'attacco unsorted bin** sovrascrivendo il `global_max_fast` con un valore molto grande, consentendo ora di creare chunk nei fast bins.
- Ora viene eseguito un **attacco fast bin**:
- Prima di tutto, si scopre che è possibile lavorare con fast **chunk di dimensione 200** nella posizione **`__free_hook`**:
- 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 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) che **innescherà l'unsorted bin attack** sovrascrivendo `global_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`**:
- <pre class="language-c"><code class="lang-c">gef➤ p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
@ -58,16 +115,20 @@ gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
0x7ff1e9e6076f <list_all_lock+15>: 0x0000000000000000 0x0000000000000000
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
</code></pre>
- Se riusciamo a ottenere un fast chunk di dimensione 0x200 in questa posizione, sarà possibile sovrascrivere un puntatore di funzione che verrà eseguito.
- Per questo, viene creato un nuovo chunk di dimensione `0xfc` e la funzione di merge viene chiamata con quel puntatore due volte, in questo modo otteniamo un puntatore a un chunk liberato di dimensione `0xfc*2 = 0x1f8` nel fast bin.
- Poi, la funzione di edit viene chiamata in questo chunk per modificare l'indirizzo **`fd`** di questo fast bin per puntare alla precedente funzione **`__free_hook`**.
- Poi, viene creato un chunk di dimensione `0x1f8` per recuperare dal fast bin il precedente chunk inutile, quindi viene creato un altro chunk di dimensione `0x1f8` per ottenere un fast bin chunk nella **`__free_hook`** che viene sovrascritto con l'indirizzo della funzione **`system`**.
- E infine, un chunk contenente la stringa `/bin/sh\x00` viene liberato chiamando la funzione di delete, attivando la funzione **`__free_hook`** che punta a system con `/bin/sh\x00` come parametro.
- 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 size `0xfc*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 size `0x1f8` viene creato per ottenere un fast bin chunk in **`__free_hook`** che viene sovrascritto con l'indirizzo della funzione **`system`**.
- 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**](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 una libc infoleak e poi eseguire un attacco fast bin per sovrascrivere malloc hook con un indirizzo one gadget.
- 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**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/)
- Possiamo solo allocare chunk di dimensioni maggiori di `0x100`.
- Sovrascrivere `global_max_fast` utilizzando un attacco Unsorted Bin (funziona 1/16 volte a causa di ASLR, perché dobbiamo modificare 12 bit, ma dobbiamo modificare 16 bit).
- Attacco Fast Bin per modificare un array globale di chunk. Questo fornisce una primitiva di lettura/scrittura arbitraria, che consente di modificare il GOT e impostare alcune funzioni per puntare a `system`.
- 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
{{#include ../../banners/hacktricks-training.md}}