From bb760ece9ceff7ec6f8d844ae5a4077eba67575f Mon Sep 17 00:00:00 2001 From: Translator Date: Thu, 28 Aug 2025 16:56:25 +0000 Subject: [PATCH] Translated ['src/binary-exploitation/libc-heap/unsorted-bin-attack.md'] --- .../libc-heap/unsorted-bin-attack.md | 129 +++++++++++++----- 1 file changed, 95 insertions(+), 34 deletions(-) diff --git a/src/binary-exploitation/libc-heap/unsorted-bin-attack.md b/src/binary-exploitation/libc-heap/unsorted-bin-attack.md index 5f11e5acc..22a7378dd 100644 --- a/src/binary-exploitation/libc-heap/unsorted-bin-attack.md +++ b/src/binary-exploitation/libc-heap/unsorted-bin-attack.md @@ -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 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` 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 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 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.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 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`**: -
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 :      0x0000000000000000      0x0000000000000000
 0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000      0x0000000000000000
 
-- 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}}