Saltearse al contenido

Ensamblador x86-64

Para explotar binarios y hacer reversing necesitas leer (y a veces escribir) ensamblador: es el lenguaje al que se compila el código y el nivel en el que trabajas cuando manipulas la ejecución. No hace falta dominarlo como un desarrollador de sistemas, pero sí entender registros, instrucciones básicas, cómo funciona el stack y las convenciones de llamada. Esta ficha es la base práctica para pwn-reversing.

# propósito general (64 bits; las versiones de 32/16/8 bits son sub-registros)
RAX RBX RCX RDX RSI RDI RBP RSP R8-R15
# equivalencias de tamaño (RAX)
RAX (64) -> EAX (32) -> AX (16) -> AL (8)
# especiales
RIP instruction pointer (qué se ejecuta) <- el objetivo del exploit
RSP stack pointer (cima del stack)
RBP base pointer (marco de la función)
RFLAGS flags (resultado de comparaciones: ZF, CF, SF...)
mov dst, src copiar lea dst, [addr] cargar dirección
add/sub dst, src aritmética xor/and/or lógicas
push/pop meter/sacar del stack
call func llamar (push RIP + salto) ret retornar (pop RIP)
cmp a, b comparar (setea flags)
jmp addr salto incondicional
je/jne/jg/jl... saltos condicionales (según flags)
nop no hacer nada (0x90) -> útil en shellcode (NOP sled)
syscall llamada al sistema (kernel)
# el stack crece hacia DIRECCIONES BAJAS
# al llamar una función:
call func -> push de la dirección de retorno, salto a func
# prólogo típico:
push rbp ; guardar el RBP anterior
mov rbp, rsp ; nuevo marco
sub rsp, N ; reservar espacio para locales
# epílogo:
leave ; mov rsp, rbp ; pop rbp
ret ; pop rip (vuelve a la dirección de retorno guardada)

El ret saca de la cima del stack la dirección a la que volver y la pone en RIP. Si controlas lo que hay en esa posición del stack, controlas RIP (base del stack overflow, ver Stack buffer overflow).

Convención de llamada (System V, Linux x86-64)

Sección titulada «Convención de llamada (System V, Linux x86-64)»

Clave para exploits: saber en qué registros van los argumentos.

# los primeros 6 argumentos van en registros, en este orden:
RDI, RSI, RDX, RCX, R8, R9
# el valor de retorno va en RAX
# ejemplo: system("/bin/sh") -> RDI = puntero a "/bin/sh", luego call system
# argumentos adicionales (7+) van en el stack

Esto es por qué en ROP (Return-Oriented Programming (ROP)) buscas gadgets pop rdi; ret — para poner el argumento en RDI antes de llamar a una función.

# Intel (la que usan la mayoría de herramientas de pwn, más legible)
mov rax, rbx ; destino, origen
# AT&T (gdb por defecto, GCC)
mov %rbx, %rax ; origen, destino (orden inverso, % y $)
# en gdb: set disassembly-flavor intel
objdump -d -M intel ./binario # desensamblar
gdb -> disassemble main # en el debugger (pwndbg/GEF)
# en reversing: ghidra/IDA dan pseudo-C + ensamblador lado a lado (ver rev-static)

Entender ensamblador no es una defensa en sí, pero es la base para: analizar malware (ver malware), auditar binarios propios, entender exactamente qué hace un exploit, y escribir detecciones precisas. Del lado del desarrollo, los compiladores con mitigaciones (canarios, CFI) insertan comprobaciones a nivel de ensamblador que conviene conocer.

  • Reconozco los registros y sus tamaños (RAX/EAX/AX/AL)
  • Entiendo RIP/RSP/RBP y su papel
  • Leo las instrucciones básicas (mov, call, ret, jmp, cmp…)
  • Explico el prólogo/epílogo y cómo ret controla RIP
  • Conozco la convención de llamada (RDI, RSI, RDX…)
  • Distingo sintaxis Intel de AT&T
  • Desensamblo con objdump/gdb/ghidra
  • Entiendo cómo syscall hace llamadas al kernel