SigReturn-Oriented Programming (SROP)
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 | .text |
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:
- Write stack frame to stack (
rax,rdi,/bin/sh) - Jump to read,
readreturns number of bytes read torax - Send exactly
15bytes to setrax = 15 - Jump to
syscalland executert_sigreturn - Jump to
syscallagain to executeexecve
Composing stack frame is straightfoward using pwntools.
My initial attempt:
1 | conn = process("./vuln") |
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 | ... |
- 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.