Registros x86_64
rax — Accumulator / Syscall Number / Return Value
¿Qué es?
El registro más usado. Tiene tres roles dependiendo del contexto:
- Número de syscall (antes de
syscall) - Valor de retorno (después de
syscallocall) - Acumulador de operaciones aritméticas
Ejemplo 1 — Syscall number
mov rax, 1 ; le dices al kernel: "quiero hacer write"
syscall ; el kernel lee rax para saber qué hacerEjemplo 2 — Valor de retorno
; después de un syscall, rax tiene el resultado
mov rax, 1 ; write syscall
mov rdi, 1
mov rsi, message
mov rdx, 13
syscall
; ahora rax contiene cuántos bytes se escribieron (13)
; si rax es negativo → hubo un errorEjemplo 3 — En aritmética
mov rax, 100
add rax, 50 ; rax = 150
imul rax, rax ; rax = 22500En contexto ofensivo
; shellcode de execve — rax determina qué syscall ejecutar
xor rax, rax
mov al, 59 ; execve (sin null bytes)
syscall ; kernel ejecuta /bin/sh
; También se usa para verificar si una syscall falló:
; si rax < 0 después de syscall → error
; por ejemplo, open() retorna -1 si el archivo no existeSubregistros útiles
rax ; 64-bit — operaciones grandes
eax ; 32-bit — limpiar los bits altos automáticamente
ax ; 16-bit
al ; 8-bit ← más usado en shellcoding (evita null bytes)rbx — Base Register / Callee Saved
¿Qué es?
Registro "seguro". Las funciones y syscalls garantizan que no lo van a tocar. Si guardas algo en rbx antes de un call, seguirá ahí después.
Ejemplo 1 — Preservar un valor entre funciones
mov rbx, 0xdeadbeef ; guardar valor importante
call algunaFuncion ; puede modificar rax, rcx, rdx...
; pero NO toca rbx
; rbx sigue siendo 0xdeadbeef aquí
cmp rbx, 0xdeadbeef
je sigueBienEjemplo 2 — Loop donde necesitas preservar un puntero
mov rbx, rsi ; guardar puntero al buffer
mov rcx, 10
loopFib:
add rax, rbx ; rbx sigue siendo el puntero
loop loopFib
; rbx intacto después del loop
mov [rbx], rax ; escribir resultado en el buffer originalEn contexto ofensivo
; En shellcodes de reverse shell, rbx suele usarse para
; guardar el file descriptor del socket mientras se hacen
; syscalls de dup2 para redirigir stdin/stdout/stderr:
mov rbx, rax ; guardar fd del socket (retorno de socket())
; ahora hacer dup2(sockfd, 0), dup2(sockfd, 1), dup2(sockfd, 2)
mov rdi, rbx ; sockfd sigue disponible
mov rsi, 0
mov rax, 33 ; dup2
syscall
mov rdi, rbx ; sockfd aún intacto
mov rsi, 1
mov rax, 33
syscallrdi — Destination Index / 1er Argumento
¿Qué es?
Primer argumento de cualquier función o syscall. Su nombre histórico "Destination Index" viene de instrucciones de strings como movs.
Ejemplo 1 — Como argumento de syscall
; write(fd, buffer, count)
mov rdi, 1 ; 1er arg = fd (1 = stdout)Ejemplo 2 — Como destino en instrucciones de string
; copiar memoria de rsi → rdi (como memcpy)
lea rdi, [destino]
lea rsi, [origen]
mov rcx, 100 ; copiar 100 bytes
rep movsb ; repite movsb 100 vecesEn contexto ofensivo
; En shellcodes, rdi es el pathname de execve:
push rdx ; null terminator
mov rdi, '/bin//sh'
push rdi
mov rdi, rsp ; rdi → "/bin//sh"
; En hooking de funciones, rdi contiene el primer argumento
; que la función víctima recibió — útil para interceptar calls
; Ejemplo: hook de open() para loggear qué archivos abre el proceso
; cuando open() es llamado, rdi = pathname del archivorsi — Source Index / 2do Argumento
¿Qué es?
Segundo argumento. Históricamente el "Source Index" para operaciones de strings.
Ejemplo 1 — Como argumento de syscall
; write(fd, buffer, count)
mov rsi, message ; 2do arg = puntero al bufferEjemplo 2 — Puntero a string en el stack (sin variables)
; Técnica shellcoding: string en stack, rsi apunta a él
xor rbx, rbx
mov bx, 'y!'
push rbx
mov rbx, 'B Academ'
push rbx
mov rbx, 'Hello HT'
push rbx
mov rsi, rsp ; rsi apunta al string "Hello HTB Academy!"En contexto ofensivo
; En un shellcode de lectura de archivo sensible (/etc/passwd):
; open("/etc/passwd", O_RDONLY)
mov rdi, filename ; 1er arg = pathname
mov rsi, 0 ; 2do arg = O_RDONLY (flags)
mov rax, 2 ; syscall open
syscall
; rax = fd del archivo abiertordx — Data Register / 3er Argumento
¿Qué es?
Tercer argumento. También recibe la parte alta en divisiones y multiplicaciones.
Ejemplo 1 — Como argumento
; write(fd, buffer, count)
mov rdx, 18 ; 3er arg = cantidad de bytes a escribirEjemplo 2 — División (rdx contiene el resto)
mov rax, 17
xor rdx, rdx ; limpiar rdx antes de dividir (IMPORTANTE)
mov rbx, 5
div rbx ; rax = 17/5 = 3, rdx = 17%5 = 2En contexto ofensivo
; En shellcodes de execve, rdx = envp (variables de entorno)
xor rdx, rdx ; NULL — sin variables de entorno
mov rax, 59 ; execve
syscall
; Si rdx no está en NULL, execve puede fallar o comportarse
; de forma inesperada — siempre limpiarlo antesrcx — Counter Register / 4to Argumento
¿Qué es?
Contador natural para loops. En Windows x64, es el 1er argumento (convención diferente a Linux).
Ejemplo 1 — Contador de loop
mov rcx, 10 ; repetir 10 veces
bucle:
; instrucciones
loop bucle ; dec rcx + jnz bucle automáticamenteEjemplo 2 — Con instrucciones de repetición
; llenar 100 bytes con 0x90 (NOP sled)
mov rdi, buffer
mov al, 0x90
mov rcx, 100
rep stosb ; almacena al en [rdi] y avanza, rcx vecesEn contexto ofensivo
; NOP sled — técnica clásica de buffer overflow
; Se rellena la memoria con NOPs antes del shellcode
; para aumentar las chances de caer en el shellcode
mov rdi, buffer_addr
xor rax, rax
mov al, 0x90 ; NOP opcode
mov rcx, 200 ; 200 NOPs
rep stosb ; escribir el NOP sled
; En Windows, rcx es el primer argumento:
; VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
; rcx = lpAddress (NULL = kernel elige la dirección)r8 y r9 — 5to y 6to Argumentos
¿Qué son?
Registros generales extra añadidos en x86_64. Principalmente usados como 5to y 6to argumentos en funciones complejas.
Ejemplo — Función con muchos argumentos
; mmap(addr, length, prot, flags, fd, offset)
; en Linux x86_64:
mov rax, 9 ; syscall mmap
xor rdi, rdi ; addr = NULL (kernel elige)
mov rsi, 4096 ; length = 4096 bytes
mov rdx, 7 ; prot = PROT_READ|PROT_WRITE|PROT_EXEC (rwx)
mov r10, 0x22 ; flags = MAP_PRIVATE|MAP_ANONYMOUS
mov r8, -1 ; fd = -1 (sin archivo)
xor r9, r9 ; offset = 0
syscall
; rax = dirección de la memoria asignadaEn contexto ofensivo
; mmap con permisos rwx es la técnica principal para
; asignar memoria ejecutable y cargar shellcode ahí:
; prot = 7 = PROT_READ(1) | PROT_WRITE(2) | PROT_EXEC(4)
; Después del mmap, copias el shellcode a esa dirección
; y saltas a ella con call rax o jmp raxrsp — Stack Pointer (Top)
¿Qué es?
Apunta SIEMPRE al tope actual del stack. Se mueve solo con push/pop/call/ret. Nunca estático — cambia constantemente.
Ejemplo 1 — push y pop
; antes: rsp = 0x7fff1000
push rax ; rsp = 0x7fff0ff8 (baja 8 bytes)
push rbx ; rsp = 0x7fff0ff0 (baja 8 más)
pop rbx ; rsp = 0x7fff0ff8 (sube 8)
pop rax ; rsp = 0x7fff1000 (restaurado)Ejemplo 2 — String en el stack
; técnica para shellcoding sin variables
push 0 ; null terminator
mov rax, '/bin//sh'
push rax
mov rdi, rsp ; rdi apunta al string en el stackEn contexto ofensivo
; Buffer overflow — cuando desbordas un buffer en el stack,
; puedes sobreescribir la dirección de retorno que está
; guardada en el stack.
;
; Stack layout típico de una función:
; [variables locales] [saved rbp] [return address] ← rsp apunta aquí al ret
;
; Si sobreescribes la return address con tu shellcode:
; cuando la función hace 'ret', salta a tu shellcode
; Stack pivoting — técnica avanzada:
; controlas rsp para que apunte a donde tú quieras
xchg rsp, rax ; rsp ahora apunta a tu cadena ROP
ret ; ejecuta el primer gadgetrbp — Base Pointer (Frame)
¿Qué es?
Ancla fija que marca el inicio del stack frame de la función actual. No se mueve durante la ejecución de una función — sirve como referencia.
Ejemplo 1 — Prólogo y epílogo de función
miFuncion:
push rbp ; guardar el frame anterior
mov rbp, rsp ; rbp = base del frame actual
; variables locales accesibles con offsets negativos:
; [rbp-8] = primera variable local
; [rbp-16] = segunda variable local
sub rsp, 16 ; reservar espacio para 2 variables
mov [rbp-8], rax ; guardar valor en variable local 1
mov [rbp-16], rbx ; guardar valor en variable local 2
; ... código de la función ...
mov rax, [rbp-8] ; recuperar variable local 1
pop rbp ; restaurar frame anterior
retEn contexto ofensivo
; rbp es clave en análisis de binarios:
; - En gdb puedes ver el frame con 'info frame'
; - Cada función tiene su propio [rbp-N] para variables locales
; - Al hacer reversing, rbp te dice dónde están las variables
; Frame pointer overwrite — técnica de explotación:
; si sobreescribes rbp + return address, puedes controlar
; el frame de la función que llamó (leave = mov rsp, rbp; pop rbp)
; 'leave' instruction = mov rsp, rbp + pop rbp
; si controlas rbp, controlas rsp después del leave → stack pivotrip — Instruction Pointer
¿Qué es?
Siempre apunta a la próxima instrucción a ejecutar. No puedes modificarlo directamente — solo call, ret, jmp y similares lo cambian.
Ejemplo 1 — Relativo a rip (RIP-relative addressing)
; acceder a datos con dirección relativa a rip
; nasm lo hace automáticamente con labels:
lea rax, [rel message] ; rax = rip + offset_a_message
; esto evita direcciones absolutas — clave en shellcodes y PIE binariesEjemplo 2 — call y ret
; cuando haces call:
; 1. rip (dirección de la siguiente instrucción) se pushea al stack
; 2. rip salta a la función
call miFuncion
; aquí rip apunta a esta instrucción antes del call
; miFuncion puede acceder a esa dirección con [rsp]
miFuncion:
; [rsp] contiene la return address (el rip guardado)
ret ; pop rsp → rip (volver)En contexto ofensivo
; RIP es el objetivo final en explotación de binarios:
; controlar RIP = controlar el flujo de ejecución del programa
; Técnica clásica:
; 1. Buffer overflow sobreescribe la return address en el stack
; 2. Al hacer 'ret', rip = tu valor = dirección de tu shellcode
; ROP (Return Oriented Programming):
; en vez de shellcode, encadenas 'gadgets' (fragmentos que terminan en ret)
; cada ret carga el siguiente gadget desde el stack
; así ejecutas código arbitrario sin escribir shellcode nuevo
; RIP-relative: útil en shellcodes porque no usa direcciones absolutas
; que cambiarían según dónde se cargue el shellcode en memoriaResumen aplicado al desarrollo ofensivo
rax → qué syscall ejecutar / qué retornó
clave: execve=59, write=1, mmap=9, open=2
rbx → guardar valores entre calls (callee-saved)
clave: guardar fd del socket en reverse shells
rdi → 1er arg: pathname en execve, fd en write/dup2
clave: /bin/sh en shellcodes
rsi → 2do arg: argv en execve, buffer en write
clave: puntero al string en el stack
rdx → 3er arg: envp (NULL) en execve, permisos en mmap
clave: siempre xor rdx, rdx antes de execve
rcx → contador de loops / 4to arg en Linux (1ro en Windows)
clave: NOP sleds con rep stosb
r8/r9 → 5to/6to arg
clave: flags y fd en mmap para memoria ejecutable rwx
rsp → tope del stack
clave: string technique + buffer overflow target
rbp → base del frame
clave: reversing de variables locales + frame pivot
rip → próxima instrucción
clave: objetivo final de cualquier exploit (control flow hijack)