Adaptar y corregir exploits
Encontrar el exploit correcto (módulo anterior) es solo la mitad del trabajo. La inmensa mayoría de los exploits públicos, tal como se descargan, no funcionan a la primera. Y eso no significa que estén mal elegidos: casi siempre necesitan un ajuste pequeño para encajar con tu objetivo exacto.
- Reconocer los motivos más habituales por los que un exploit público falla tal cual.
- Leer un traceback de Python y localizar la línea exacta del problema.
- Entender qué es un shellcode y una dirección de retorno, y por qué son tan frágiles entre entornos.
- Adaptar un exploit a tu entorno sin reescribirlo entero, cambiando un fallo cada vez.
- Distinguir un fallo del exploit de un fallo real del objetivo.
- Probar primero en un entorno controlado antes de lanzar contra el objetivo real.
1. Por qué un exploit público casi nunca funciona sin tocarlo
Un exploit se escribe contra un entorno concreto —una versión, un sistema operativo, una configuración, una dirección IP— y se publica tal cual. El tuyo casi nunca es idéntico. Adaptarlo no es un fallo del exploit ni tuyo: es el trabajo. Los motivos habituales, de más trivial a más delicado:
| Motivo | Síntoma habitual |
|---|---|
| Dirección o puerto de ejemplo del autor, sin cambiar | La conexión no llega a ningún sitio: cuelgue o "connection refused". |
| Escrito para Python 2 | SyntaxError al ejecutarlo con Python 3 (print sin paréntesis, xrange...). |
Mezcla de bytes y str | TypeError: can't concat str to bytes (o similar). |
| Llamada a una función con los argumentos mal puestos | TypeError señalando cuántos argumentos esperaba la función. |
| El protocolo del objetivo cambió ligeramente respecto a cuando se escribió el exploit | El objetivo responde, pero con un error o un rechazo, no con un fallo de Python. |
| Terminador de línea o de comando distinto al esperado | El objetivo no responde nunca: se queda esperando el resto de una petición que nunca llega completa. |
| Un valor numérico (longitud, offset, dirección) calculado para otra versión | El objetivo responde con un error explícito, o el exploit "casi" funciona pero el objetivo se cae en vez de darte control. |
| Motivo | Síntoma habitual |
|---|---|
| Dirección o puerto de ejemplo del autor, sin cambiar | La conexión no llega a ningún sitio: cuelgue o "connection refused". |
| Escrito para Python 2 | SyntaxError al ejecutarlo con Python 3 (print sin paréntesis, xrange...). |
Mezcla de bytes y str | TypeError: can't concat str to bytes (o similar). |
| Llamada a una función con los argumentos mal puestos | TypeError señalando cuántos argumentos esperaba la función. |
| El protocolo del objetivo cambió ligeramente respecto a cuando se escribió el exploit | El objetivo responde, pero con un error o un rechazo, no con un fallo de Python. |
| Terminador de línea o de comando distinto al esperado | El objetivo no responde nunca: se queda esperando el resto de una petición que nunca llega completa. |
| Un valor numérico (longitud, offset, repetición) calculado para otra versión | El objetivo responde con un error explícito sobre el valor recibido, si tiene la cortesía de decirlo. |
2. Leer el error antes que nada
Un traceback de Python no es ruido: es la localización exacta del problema.
$ python3 exploit.py
Traceback (most recent call last):
File "exploit.py", line 14, in <module>
payload = b"DIAG5 " + version_cliente + b"\r\n"
TypeError: can't concat str to bytesLa última línea dice el tipo de error; la penúltima, la línea de código exacta. Aquí, version_cliente es una cadena de texto (str) y se está intentando concatenar con datos binarios (bytes); la solución es convertirla con .encode() o escribirla directamente como bytes (b"...").
3. Cuando el objetivo responde, pero no como se esperaba
No todos los fallos son excepciones de Python. Si el exploit se ejecuta sin errores pero el objetivo responde con un mensaje de rechazo, la información está en esa respuesta:
- Un mensaje que menciona un nombre distinto al que envía el exploit sugiere que el comando cambió de nombre entre versiones.
- Un mensaje sobre una longitud o cantidad concreta sugiere que un valor numérico del exploit (relleno, offset) no coincide con esta versión exacta.
- Ningún mensaje en absoluto, y el programa se queda colgado, sugiere que el objetivo sigue esperando datos: revisa el terminador de línea o de comando.
4. Los parámetros frágiles: shellcode, direcciones y offsets
Los exploits de corrupción de memoria (desbordamientos de búfer y similares) llevan piezas especialmente sensibles al entorno que conviene reconocer aunque este curso no construya uno desde cero:
- Shellcode: el código máquina que se ejecuta si el exploit tiene éxito (abrir una shell, conectar de vuelta). Depende de la arquitectura (x86, x64, ARM) y del sistema. Un shellcode de ejemplo para Windows no sirve en Linux; se regenera para tu objetivo (por ejemplo con
msfvenom). - Dirección de retorno / offset: muchos exploits necesitan una dirección de memoria exacta y un offset (a cuántos bytes está el punto que se sobrescribe). Ese número se calculó para una versión concreta del binario; en otra versión, la dirección cambia y el exploit, en vez de darte control, tumba el servicio. Es la causa típica de un exploit que "casi" funciona.
- Bad characters: ciertos bytes (a menudo
\x00,\x0a) rompen el envío según el protocolo. El shellcode debe generarse evitándolos.
La lección para este curso: cuando un exploit de memoria falla sin dar un error de Python claro, sospecha del shellcode o de una dirección calculada para otra versión, no de tu forma de ejecutarlo.
5. Adaptar, no reescribir
El objetivo casi nunca es escribir el exploit desde cero: es cambiar lo mínimo necesario para que funcione contra tu objetivo concreto. El método:
- Ejecuta el exploit tal cual viene, para ver el fallo real (no el que imaginas).
- Localiza la línea exacta señalada por el error, o el punto donde deja de avanzar.
- Cambia solo eso: una dirección, un separador, un tipo de dato, un nombre de comando.
- Vuelve a ejecutar. Si aparece un fallo distinto, ya has resuelto el anterior: repite el proceso con el nuevo.
6. Prueba en un entorno controlado primero
Un exploit adaptado sigue siendo código que puede tumbar el servicio objetivo (sobre todo los de memoria: si el offset no es exacto, el proceso se cae). Lanzarlo directamente contra el sistema de producción de un cliente sin haberlo probado es una forma rápida de provocar una caída no autorizada. La práctica correcta, cuando es posible, es montar una réplica del objetivo (misma versión del software, mismo SO) y afinar ahí el exploit —offset, shellcode, parámetros— hasta que sea fiable, antes de usarlo contra el objetivo real. Es, literalmente, lo que hace el laboratorio de este módulo: un entorno seguro donde equivocarse sale gratis.
msfvenompara generar shellcode a medida (arquitectura, formato, bad characters) — se ve en el módulo 17.- Las guías de RevShell y Tratamiento de TTY de Ghost Academy (ver "Herramientas relacionadas") para el paso siguiente: convertir la ejecución en una shell usable.
- Documentación de Python sobre
bytesvsstry sobre cómo leer un traceback, base de la mayoría de las correcciones de este módulo.
Guías y laboratorios ya publicados en Ghost Academy sobre las herramientas de este módulo:
Descomprime, lee el LEEME.md y lanza startlab.sh. En la carpeta exploits/ hay 8 scripts rotos, cada uno con un fallo distinto de los descritos en esta unidad, contra el servicio de diagnóstico NexusDiag (localhost:9000). Arréglalos uno a uno; cada uno, al funcionar, imprime su flag GO{...}. Verifícalas en http://localhost:8787/verificador.
¿Atascado o ya terminado? La solución paso a paso muestra el error de cada script y su corrección exacta. Úsala cuando lo hayas intentado.
Autoevaluación
- Un exploit falla con
ConnectionRefusedError. ¿Qué es lo primero que revisas? - ¿Qué diferencia hay entre un
SyntaxErrory unTypeErrora la hora de saber qué corregir? - El objetivo responde "comando no reconocido" en vez de fallar el script. ¿Qué te dice eso sobre dónde está el problema?
- ¿Por qué un exploit de memoria puede tumbar el servicio en vez de darte control, y qué parámetro suele ser la causa?
- ¿Qué es un shellcode y por qué uno de ejemplo casi nunca sirve tal cual?
- ¿Por qué conviene corregir un fallo cada vez, en vez de reescribir el script entero de golpe?
- ¿Por qué se prueba un exploit adaptado en una réplica antes de lanzarlo contra el objetivo real?