Saltearse al contenido

Introducción al reversing

La ingeniería inversa (reversing) es el arte de entender cómo funciona un programa sin tener su código fuente, partiendo solo del binario compilado. Se usa para analizar malware, encontrar vulnerabilidades, saltarse protecciones (licencias, DRM), entender protocolos propietarios, y resolver retos de CTF. Consiste en traducir el código máquina de vuelta a algo comprensible —ensamblador y, con decompiladores, pseudo-C— y razonar sobre su lógica.

- análisis de malware: entender qué hace un ejecutable sospechoso (ver malware)
- búsqueda de vulnerabilidades: encontrar bugs en software sin fuente (pwn)
- bypass de protecciones: licencias, DRM, anti-cheat, comprobaciones
- interoperabilidad: entender formatos/protocolos propietarios
- CTF: resolver retos de "crackme" (encontrar la contraseña/flag)

Las dos grandes aproximaciones, complementarias:

Análisis estático (ver rev-static) examinar el binario SIN ejecutarlo
-> desensamblar/decompilar, leer la lógica, strings, imports
-> seguro (no ejecutas malware), pero más laborioso
Análisis dinámico (ver rev-dynamic) ejecutar el binario y observarlo
-> debugger, trazas, monitorizar syscalls/APIs, ver la memoria en vivo
-> rápido para entender comportamiento, pero ejecutas el código (riesgo en malware)

En la práctica se combinan: estático para mapear la lógica, dinámico para confirmar y para lo que es difícil de seguir estáticamente (código ofuscado, descifrado en runtime).

file binario # tipo, arquitectura, ¿stripped? ¿static?
strings binario # cadenas legibles: mensajes, rutas, URLs, pistas
strings -e l binario # strings unicode (Windows)
checksec --file=binario # mitigaciones (si es ELF)
nm binario / readelf -s # símbolos (si no está stripped)
ltrace / strace ./binario # llamadas a librería/sistema (dinámico rápido)

strings es el primer paso siempre: a menudo revela contraseñas, mensajes de error, nombres de funciones, o la propia flag.

ELF Linux (ejecutables, .so)
PE Windows (.exe, .dll)
Mach-O macOS/iOS
# además: .NET (IL, decompilable casi al fuente con dnSpy/ILSpy),
# Java (bytecode -> jd-gui), Python (pyc -> decompyle3), etc.

Los lenguajes con bytecode (.NET, Java, Python) son mucho más fáciles de revertir (casi se recupera el fuente); C/C++ compilado a nativo es lo más difícil.

# decompiladores/desensambladores (estático)
Ghidra gratuito, potente, decompilador a pseudo-C (NSA) -> el estándar libre
IDA Pro el estándar comercial; IDA Free limitado
Binary Ninja, radare2/cutter, Hopper
# debuggers (dinámico)
gdb + pwndbg/GEF (Linux), x64dbg (Windows), lldb
# utilidades
strings, file, nm, objdump, readelf, ltrace/strace
# bytecode: dnSpy/ILSpy (.NET), jadx/jd-gui (Java/Android)
1. file + strings + checksec -> primera idea
2. abrir en Ghidra -> localizar main / la función de comprobación
3. leer la lógica (pseudo-C): ¿cómo valida la contraseña/flag?
4. si está claro -> deducir la entrada correcta
5. si hay ofuscación/cifrado -> dinámico (gdb): poner breakpoints, ver memoria en runtime
6. (si procede) parchear el binario para saltarse la comprobación

El reversing se dificulta —no se impide— con protecciones que se cubren en rev-antidebug:

- ofuscación del código y de strings
- packers / cifrado del ejecutable (se descifra en runtime)
- anti-debugging / anti-VM / detección de análisis
- comprobaciones de integridad (anti-tampering)
# para proteger propiedad intelectual o malware; para el analista, son obstáculos a sortear
  • file/strings/checksec como primer contacto
  • Diferenciar estático vs dinámico y cuándo usar cada uno
  • Reconocer el formato (ELF/PE/Mach-O/bytecode)
  • Abrir un binario en Ghidra y localizar main/lógica
  • Leer pseudo-C y seguir la lógica de validación
  • ltrace/strace para ver llamadas rápidamente
  • Combinar estático + dinámico en código ofuscado
  • Resolver un crackme básico (deducir/parchear)