Parte: 5 — Explotación de sistemas y binarios · Fuente: Andriesse, Practical Binary Analysis ⏱️ Duración estimada: 120 min · Nivel: Avanzado
Profundizar en el análisis estático: entender un binario sin ejecutarlo. Estudiarás el desensamblado (lineal vs recursivo), la reconstrucción del grafo de control de flujo (CFG) y de llamadas (call graph), el análisis de flujo de datos elemental y los límites del enfoque estático frente a código ofuscado o generado dinámicamente. Es la base rigurosa del reversing profesional.
⚠️ Ética: solo binarios propios/autorizados.
Al finalizar, el alumno podrá:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Desensamblado lineal | Simple pero se confunde con datos |
| 2 | Desensamblado recursivo | Sigue el flujo; más preciso |
| 3 | CFG (control flow graph) | Estructura lógica de la función |
| 4 | Call graph | Relaciones entre funciones |
| 5 | Data flow básico | Rastrear valores y constantes |
| 6 | Detección de funciones | Prólogos y heurísticas |
| 7 | Límites: packing, indirección | Cuándo el estático no basta |
| 8 | capstone/pyelftools | Automatizar el análisis |
pip install capstone pyelftools
sudo apt install -y binutils
# Ghidra para CFG/decompilado (clase 131)
Entorno propio.
bash
objdump -d crackme # lineal (GNU)
# En Ghidra/IDA/r2 el análisis es recursivo: contrasta la función donde difieren
Reconstruye el CFG de main en Ghidra (Window → Function Graph) e identifica bucles y ramas de
éxito/fracaso.
Escribe un desensamblador mínimo con capstone para ver cómo se decodifican instrucciones:
python
from capstone import *
from elftools.elf.elffile import ELFFile
f = ELFFile(open("crackme","rb"))
text = f.get_section_by_name(".text")
code, addr = text.data(), text["sh_addr"]
md = Cs(CS_ARCH_X86, CS_MODE_64)
for i in md.disasm(code, addr):
print(f"0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}")
Haz un análisis def-use manual de la variable de entrada: dónde se lee (scanf), dónde se compara,
qué transformación sufre.
Empaqueta un binario con upx y muestra que el desensamblado estático solo ve el stub (límite del
estático):
bash
upx -9 crackme -o crackme.packed
objdump -d crackme.packed | head # apenas el descompresor
call hay en .text.upx -t/entropía si un binario está empacado.jmp rax) y explica por qué complica el estático.Con capstone y pyelftools, escribe un script que liste todas las instrucciones call de .text con
su dirección y, cuando sea directo, la función destino.
Criterio de aceptación: el script imprime las llamadas directas resueltas correctamente
(verificable contra objdump -d) y marca las indirectas como "no resueltas".
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| Desensamblado "basura" a mitad | Datos incrustados; usa análisis recursivo (Ghidra/r2) |
| Faltan funciones en el CFG | Alcanzadas por saltos indirectos; complementa con dinámico |
| capstone no decodifica | Modo/arquitectura equivocados (32 vs 64) |
| Binario "vacío" en objdump | Está empacado (UPX u otro); desempácalo primero |
| Call destino incorrecto | Confundes dirección relativa con absoluta |
❓ ¿Estático o dinámico? El estático da visión global sin ejecutar; el dinámico revela lo que solo ocurre en runtime. Se complementan.
❓ ¿Cómo detecto packing? Alta entropía, pocas secciones, imports mínimos, o firmas conocidas
(upx).
❓ ¿Puedo resolver saltos indirectos estáticamente? A veces con análisis de valores; en general requieren ejecución/emulación.