Reversing para CTF y HTB: de strings y ltrace a Ghidra y dnSpy
Ingeniería inversa paso a paso para pentesting: identificar el binario, strings, ltrace, Ghidra, gdb y decompilar .NET, Java y Python para sacar contraseñas y claves.
En Hack The Box no hace falta ser un experto en ensamblador para usar el reversing. La mayoría de las veces el objetivo es modesto: un ejecutable que encuentras en la máquina guarda una contraseña, una clave de cifrado o llama a un programa sin ruta absoluta. Sacarlo cuesta minutos si empiezas por las herramientas sencillas y solo abres Ghidra cuando hace falta.
Esta guía es ese orden: de lo más barato a lo más caro.
Requisitos
En Kali o Parrot:
sudo apt install file binutils ltrace strace gdb ghidra jadx
pipx install pyinstxtractor-ng
- Ghidra necesita Java 21: si no arranca,
sudo apt install openjdk-21-jdk. - Para .NET desde Linux:
dotnet tool install -g ilspycmd(necesita el SDK de .NET). En Windows, dnSpy o ILSpy con interfaz. - Ficha de Ghidra con atajos.
- Para practicar: Irked, Blocky y Support, las tres fáciles.
Cuidado
No ejecutes un binario desconocido en tu máquina principal. Hazlo en la propia víctima o en una VM desechable.
Paso 1: qué tienes delante
file binario
binario: ELF 64-bit LSB pie executable, x86-64, dynamically linked, not stripped
La salida decide el camino:
file dice |
Qué es | Herramienta |
|---|---|---|
ELF ... not stripped |
Binario de Linux con nombres de funciones | strings, ltrace, Ghidra |
ELF ... stripped |
Sin nombres: más trabajo en Ghidra | Ghidra, gdb |
PE32 ... Mono/.Net assembly |
.NET | ILSpy, dnSpy, ilspycmd |
Java archive data (JAR) |
Java | jadx-gui o JD-GUI |
python ... byte-compiled o un ELF con PyInstaller en los strings |
Python empaquetado | pyinstxtractor y un decompilador |
Go BuildID en los strings |
Go | Ghidra (lento y verboso) |
Paso 2: strings
Antes de abrir nada, lee las cadenas de texto:
strings -n 6 binario | less
strings -n 6 -e l binario | less
La segunda línea busca cadenas UTF-16, que es como .NET y muchos programas de Windows guardan el texto. Busca contraseñas, rutas, consultas SQL, URLs y nombres de comandos:
strings -n 6 binario | grep -iE "pass|key|secret|user|/bin/|/tmp/|http|select"
En Shoppy la contraseña maestra del gestor de contraseñas de jaeger sale de los strings del binario.
Paso 3: ltrace y strace
Ejecuta el binario viendo qué funciones de la librería llama (ltrace) o qué llamadas al sistema hace (strace):
ltrace ./binario
puts("This application is being devleo"...) = 69
...
setuid(0) = -1
system("/tmp/listusers"sh: 1: /tmp/listusers: not found
Es el binario SUID viewuser de Irked: ejecuta /tmp/listusers, que no existe y que puedes crear tú. Pon dentro /bin/bash, dale permisos (chmod +x /tmp/listusers) y ejecuta viewuser para tener una shell como root.
Con un crackme que pide contraseña, ltrace suele enseñar directamente la comparación:
strcmp("lo-que-escribiste", "la-contraseña-real") = -1
Paso 4: Ghidra
Cuando lo anterior no basta, abre el binario en Ghidra:
ghidraRun→ File > New Project → Non-Shared Project.- File > Import File y elige el binario. Acepta las opciones por defecto.
- Doble clic en el binario y responde Yes a Analyze.
- En el árbol Symbol Tree despliega Functions y abre
main. A la derecha tienes el código decompilado en C.
Atajos que más vas a usar:
| Tecla | Acción |
|---|---|
L |
Renombrar una variable o función |
Ctrl+L |
Cambiar el tipo de una variable (por ejemplo a char *) |
G |
Ir a una dirección |
| Window > Defined Strings | Todas las cadenas, con un clic te lleva a quien las usa |
| Clic derecho > References > Show References to | Quién llama a una función o usa un dato |
Si el binario está stripped y no ves main, busca la función entry: el primer argumento de __libc_start_main es main. Renombra variables a medida que entiendes qué hacen: a los diez minutos el código se lee solo.
Truco
Si ves una cadena rara que se recorre con un bucle y un ^ (XOR), es un "cifrado" casero. Copia los bytes y la clave y repite la operación en Python: bytes(a ^ b for a, b in zip(datos, clave * 100)).
Paso 5: gdb para ver valores en tiempo de ejecución
A veces es más rápido parar el programa y mirar la memoria que entender el algoritmo. Para una comparación de cadenas:
gdb -q ./binario
(gdb) break strcmp
(gdb) run
(gdb) x/s $rdi
(gdb) x/s $rsi
0x7fffffffe3a0: "lo-que-escribiste"
0x555555556010: "la-contraseña-real"
En x86-64 Linux, los dos primeros argumentos van en rdi y rsi. Si el binario hace fork(), añade set follow-fork-mode child antes de run. Para explotar fallos de memoria con gdb, sigue con buffer overflow y ret2libc.
Paso 6: .NET, Java y Python se decompilan casi enteros
Estos lenguajes guardan tanta información que recuperas el código fuente prácticamente tal cual.
.NET (muy habitual en máquinas Windows):
ilspycmd -p -o salida/ UserInfo.exe
grep -rn "password\|key" salida/
En Support, UserInfo.exe guarda la contraseña de LDAP "cifrada" con un XOR, y la clave está en el propio código. En Cascade, CascAudit.exe y su DLL contienen la clave AES con la que se descifra la contraseña de ArkSvc guardada en una base de datos SQLite. En Nest basta con recompilar el programa y dejar que descifre la contraseña por ti.
Java (.jar, .war, .apk):
jadx -d salida/ BlogCore.jar
grep -rn "password" salida/
En Blocky el plugin de Minecraft BlogCore.jar lleva la contraseña de la base de datos, que es también la del usuario notch. En Crafty el patrón se repite con un plugin en Windows.
Python empaquetado con PyInstaller:
pyinstxtractor-ng treport
Te deja una carpeta treport_extracted/ con ficheros .pyc. Decompila el principal con pycdc o, si es Python 3.8 o anterior, con uncompyle6. Es el caso de Unicode; en Wall aparece directamente un .pyc suelto.
Errores típicos
- Ghidra no arranca o se cierra: versión de Java equivocada. Instala el JDK 21 y lanza
ghidraRundesde una terminal para ver el error. ltraceno muestra nada: el binario es estático o de Go, que no usan la libc dinámica. Pasa astraceo a Ghidra.ilspycmd: command not found: añade~/.dotnet/toolsa tuPATH.pycdcdeja funciones a medias: ningún decompilador soporta bien todas las versiones de Python. Mira la versión confilesobre el.pycy prueba otro decompilador o lee el bytecode conpycdas.- Los binarios de Go en Ghidra son ilegibles: busca las funciones
main.*en el Symbol Tree e ignora el resto, que es el runtime de Go.
Y después
Cuando el binario tiene un fallo de memoria en vez de un secreto, sigue con buffer overflow y ret2libc en Linux. Y si lo que sacas es un hash en lugar de una contraseña, con hashcat y John.