Translated ['src/binary-exploitation/libc-heap/heap-overflow.md', 'src/b

This commit is contained in:
Translator 2025-07-30 06:14:10 +00:00
parent 934e6661c8
commit d6a8e3fc96
2 changed files with 68 additions and 8 deletions

View File

@ -4,7 +4,7 @@
## Informazioni di base
Un heap overflow è simile a un [**stack overflow**](../stack-overflow/index.html) ma nell'heap. Fondamentalmente significa che è stato riservato dello spazio nell'heap per memorizzare dei dati e **i dati memorizzati erano più grandi dello spazio riservato.**
Un heap overflow è simile a un [**stack overflow**](../stack-overflow/index.html) ma nell'heap. Fondamentalmente significa che è stato riservato dello spazio nell'heap per memorizzare alcuni dati e **i dati memorizzati erano più grandi dello spazio riservato.**
Negli stack overflow sappiamo che alcuni registri come il puntatore di istruzione o il frame dello stack verranno ripristinati dallo stack e potrebbe essere possibile abusarne. Nel caso degli heap overflow, **non ci sono informazioni sensibili memorizzate per impostazione predefinita** nel chunk dell'heap che può essere sovrascritto. Tuttavia, potrebbero esserci informazioni sensibili o puntatori, quindi la **criticità** di questa vulnerabilità **dipende** da **quali dati potrebbero essere sovrascritti** e da come un attaccante potrebbe abusarne.
@ -17,13 +17,13 @@ Negli stack overflow l'organizzazione e i dati che saranno presenti nello stack
Tuttavia, nel caso di un heap overflow, la memoria utilizzata non è lineare ma **i chunk allocati sono solitamente in posizioni separate della memoria** (non uno accanto all'altro) a causa di **bins e zone** che separano le allocazioni per dimensione e perché **la memoria precedentemente liberata viene utilizzata** prima di allocare nuovi chunk. È **complicato sapere quale oggetto andrà a collidere con quello vulnerabile** a un heap overflow. Quindi, quando viene trovato un heap overflow, è necessario trovare un **modo affidabile per fare in modo che l'oggetto desiderato sia il prossimo in memoria** rispetto a quello che può essere sovrascritto.
Una delle tecniche utilizzate per questo è **Heap Grooming** che viene utilizzata ad esempio [**in questo post**](https://azeria-labs.com/grooming-the-ios-kernel-heap/). Nel post viene spiegato come quando nel kernel iOS una zona esaurisce la memoria per memorizzare chunk di memoria, essa si espande di una pagina del kernel, e questa pagina viene suddivisa in chunk delle dimensioni previste che verrebbero utilizzate in ordine (fino alla versione 9.2 di iOS, poi questi chunk vengono utilizzati in modo randomizzato per rendere più difficile l'esploitazione di questi attacchi).
Una delle tecniche utilizzate per questo è **Heap Grooming** che viene utilizzata ad esempio [**in questo post**](https://azeria-labs.com/grooming-the-ios-kernel-heap/). Nel post viene spiegato come, quando nel kernel iOS una zona esaurisce la memoria per memorizzare chunk di memoria, essa si espande di una pagina del kernel, e questa pagina viene suddivisa in chunk delle dimensioni previste che verrebbero utilizzate in ordine (fino alla versione 9.2 di iOS, poi questi chunk vengono utilizzati in modo randomizzato per rendere più difficile l'esploitazione di questi attacchi).
Pertanto, nel post precedente in cui si verifica un heap overflow, per forzare l'oggetto sovrascritto a collidere con un oggetto vittima, vengono forzati diversi **`kallocs` da diversi thread per cercare di garantire che tutti i chunk liberi siano riempiti e che venga creata una nuova pagina**.
Per forzare questo riempimento con oggetti di una dimensione specifica, l'**allocazione out-of-line associata a un mach port iOS** è un candidato ideale. Modificando la dimensione del messaggio, è possibile specificare esattamente la dimensione dell'allocazione `kalloc` e quando il corrispondente mach port viene distrutto, l'allocazione corrispondente verrà immediatamente rilasciata a `kfree`.
Quindi, alcuni di questi segnaposto possono essere **liberati**. La **lista libera `kalloc.4096` rilascia elementi in ordine last-in-first-out**, il che significa fondamentalmente che se alcuni segnaposto vengono liberati e l'exploit tenta di allocare diversi oggetti vittima mentre cerca di allocare l'oggetto vulnerabile all'overflow, è probabile che questo oggetto sa seguito da un oggetto vittima.
Quindi, alcuni di questi segnaposto possono essere **liberati**. La **lista libera `kalloc.4096` rilascia elementi in ordine last-in-first-out**, il che significa fondamentalmente che se alcuni segnaposto vengono liberati e l'exploit tenta di allocare diversi oggetti vittima mentre cerca di allocare l'oggetto vulnerabile all'overflow, è probabile che questo oggetto sia seguito da un oggetto vittima.
### Esempio libc
@ -43,6 +43,38 @@ python3 -c 'print("/"*0x400+"/bin/ls\x00")' > hax.txt
- [**Auth-or-out. Hack The Box**](https://7rocky.github.io/en/ctf/htb-challenges/pwn/auth-or-out/)
- Utilizziamo una vulnerabilità di Integer Overflow per ottenere un Heap Overflow.
- Corrompiamo i puntatori a una funzione all'interno di un `struct` del chunk sovrascritto per impostare una funzione come `system` e ottenere l'esecuzione del codice.
- Corrompiamo i puntatori a una funzione all'interno di un `struct` del chunk sovrascritto per impostare una funzione come `system` e ottenere l'esecuzione di codice.
### Esempio del Mondo Reale: CVE-2025-40597 Uso improprio di `__sprintf_chk`
Nel firmware 10.2.1.15 di SonicWall SMA100, il modulo reverse-proxy `mod_httprp.so` alloca un chunk heap di **0x80-byte** e poi concatena diverse stringhe al suo interno con `__sprintf_chk`:
```c
char *buf = calloc(0x80, 1);
/* … */
__sprintf_chk(buf, /* destination (0x80-byte chunk) */
-1, /* <-- size argument !!! */
0, /* flags */
"%s%s%s%s", /* format */
"/", "https://", path, host);
```
`__sprintf_chk` fa parte di **_FORTIFY_SOURCE**. Quando riceve un parametro `size` **positivo**, verifica che la stringa risultante rientri nel buffer di destinazione. Passando **`-1` (0xFFFFFFFFFFFFFFFF)**, gli sviluppatori hanno effettivamente **disabilitato il controllo dei limiti**, riportando la chiamata rinforzata a una classica `sprintf` non sicura.
Fornire un'intestazione **`Host:`** eccessivamente lunga consente quindi a un attaccante di **superare il chunk di 0x80 byte e sovrascrivere i metadati del chunk heap successivo** (tcache / fast-bin / small-bin a seconda dell'allocatore). Un crash può essere riprodotto con:
```python
import requests, warnings
warnings.filterwarnings('ignore')
requests.get(
'https://TARGET/__api__/',
headers={'Host': 'A'*750},
verify=False
)
```
L'exploitation pratica richiederebbe **heap grooming** per posizionare un oggetto controllabile subito dopo il chunk vulnerabile, ma la causa principale evidenzia due importanti insegnamenti:
1. **_FORTIFY_SOURCE non è una soluzione miracolosa** l'uso improprio può annullare la protezione.
2. Passa sempre la **dimensione corretta del buffer** alla famiglia `_chk` (o, ancora meglio, usa `snprintf`).
## Riferimenti
* [watchTowr Labs Stack Overflows, Heap Overflows and Existential Dread (SonicWall SMA100)](https://labs.watchtowr.com/stack-overflows-heap-overflows-and-existential-dread-sonicwall-sma100-cve-2025-40596-cve-2025-40597-and-cve-2025-40598/)
{{#include ../../banners/hacktricks-training.md}}

View File

@ -4,11 +4,11 @@
## Che cos'è uno Stack Overflow
Un **stack overflow** è una vulnerabilità che si verifica quando un programma scrive più dati nello stack di quanti ne siano allocati per contenerli. Questi dati in eccesso **sovrascriveranno lo spazio di memoria adiacente**, portando alla corruzione di dati validi, alla disruption del flusso di controllo e potenzialmente all'esecuzione di codice malevolo. Questo problema si verifica spesso a causa dell'uso di funzioni non sicure che non eseguono il controllo dei limiti sugli input.
Un **stack overflow** è una vulnerabilità che si verifica quando un programma scrive più dati nello stack di quanti ne siano stati allocati per contenerli. Questi dati in eccesso **sovrascriveranno lo spazio di memoria adiacente**, portando alla corruzione di dati validi, alla disruption del flusso di controllo e potenzialmente all'esecuzione di codice malevolo. Questo problema si verifica spesso a causa dell'uso di funzioni non sicure che non eseguono controlli sui limiti dell'input.
Il problema principale di questa sovrascrittura è che il **puntatore di istruzione salvato (EIP/RIP)** e il **puntatore di base salvato (EBP/RBP)** per tornare alla funzione precedente sono **memorizzati nello stack**. Pertanto, un attaccante sarà in grado di sovrascrivere questi e **controllare il flusso di esecuzione del programma**.
La vulnerabilità di solito si verifica perché una funzione **copia nello stack più byte della quantità allocata per essa**, riuscendo così a sovrascrivere altre parti dello stack.
La vulnerabilità di solito si verifica perché una funzione **copia nello stack più byte della quantità allocata per essa**, riuscendo quindi a sovrascrivere altre parti dello stack.
Alcune funzioni comuni vulnerabili a questo sono: **`strcpy`, `strcat`, `sprintf`, `gets`**... Inoltre, funzioni come **`fgets`**, **`read` & `memcpy`** che prendono un **argomento di lunghezza**, potrebbero essere utilizzate in modo vulnerabile se la lunghezza specificata è maggiore di quella allocata.
@ -25,7 +25,7 @@ printf("You entered: %s\n", buffer);
Il modo più comune per trovare gli stack overflow è fornire un input molto grande di `A`s (ad esempio `python3 -c 'print("A"*1000)'`) e aspettarsi un `Segmentation Fault` che indica che l'**indirizzo `0x41414141` è stato tentato di essere accesso**.
Inoltre, una volta trovata la vulnerabilità di Stack Overflow, sarà necessario trovare l'offset fino a quando non sarà possibile **sovrascrivere l'indirizzo di ritorno**; per questo si usa solitamente una **sequenza di De Bruijn.** Che per un dato alfabeto di dimensione _k_ e sottosequenze di lunghezza _n_ è una **sequenza ciclica in cui ogni possibile sottosequenza di lunghezza _n_ appare esattamente una volta** come sottosequenza contigua.
Inoltre, una volta trovata la vulnerabilità di Stack Overflow, sarà necessario trovare l'offset fino a quando non sarà possibile **sovrascrivere l'indirizzo di ritorno**, per questo si usa solitamente una **sequenza di De Bruijn.** Che per un dato alfabeto di dimensione _k_ e sottosequenze di lunghezza _n_ è una **sequenza ciclica in cui ogni possibile sottosequenza di lunghezza _n_ appare esattamente una volta** come sottosequenza contigua.
In questo modo, invece di dover capire manualmente quale offset è necessario per controllare l'EIP, è possibile utilizzare come padding una di queste sequenze e poi trovare l'offset dei byte che hanno finito per sovrascriverlo.
@ -81,7 +81,7 @@ Questa tecnica è il framework fondamentale per bypassare la principale protezio
## Heap Overflows
Un overflow non sarà sempre nello stack, potrebbe anche essere nell'**heap**, per esempio:
Un overflow non sarà sempre nello stack, potrebbe anche essere nell'**heap**, ad esempio:
{{#ref}}
../libc-heap/heap-overflow.md
@ -95,4 +95,32 @@ Ci sono diverse protezioni che cercano di prevenire l'exploitation delle vulnera
../common-binary-protections-and-bypasses/
{{#endref}}
### Esempio del Mondo Reale: CVE-2025-40596 (SonicWall SMA100)
Una buona dimostrazione del perché **`sscanf` non dovrebbe mai essere considerato affidabile per l'analisi di input non fidati** è apparsa nel 2025 nell'appliance SSL-VPN SMA100 di SonicWall. La routine vulnerabile all'interno di `/usr/src/EasyAccess/bin/httpd` tenta di estrarre la versione e l'endpoint da qualsiasi URI che inizia con `/__api__/`:
```c
char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
```
1. La prima conversione (`%2s`) memorizza in modo sicuro **due** byte in `version` (ad es. `"v1"`).
2. La seconda conversione (`%s`) **non ha specificatore di lunghezza**, quindi `sscanf` continuerà a copiare **fino al primo byte NUL**.
3. Poiché `endpoint` si trova nello **stack** ed è lungo **0x800 byte**, fornire un percorso più lungo di 0x800 byte corrompe tutto ciò che si trova dopo il buffer incluso il **stack canary** e il **salvato indirizzo di ritorno**.
Una prova di concetto su una sola riga è sufficiente per attivare il crash **prima dell'autenticazione**:
```python
import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)
```
Anche se i canarini dello stack abortiscono il processo, un attaccante ottiene comunque un **Denial-of-Service** primitivo (e, con ulteriori perdite di informazioni, possibilmente l'esecuzione di codice). La lezione è semplice:
* Fornire sempre una **larghezza massima del campo** (ad es. `%511s`).
* Preferire alternative più sicure come `snprintf`/`strncpy_s`.
## Riferimenti
* [watchTowr Labs Stack Overflows, Heap Overflows and Existential Dread (SonicWall SMA100)](https://labs.watchtowr.com/stack-overflows-heap-overflows-and-existential-dread-sonicwall-sma100-cve-2025-40596-cve-2025-40597-and-cve-2025-40598/)
{{#include ../../banners/hacktricks-training.md}}