io basic knowledge

[TOC] _IO_list_all、 _IO_2_1_stderr、 stderr

FSOP

  • FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。
  • FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow
typedef int (*_IO_overflow_t) (FILE *, int);
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
# define _IO_JUMPS_FUNC(THIS) \
  (IO_validate_vtable                                                   \
   (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS)	\
			     + (THIS)->_vtable_offset)))

#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define _IO_WIDE_JUMPS(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
  • _IO_flush_all_lockp
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
int _IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  FILE *fp;
#ifdef _IO_MTSAFE_IO
  _IO_cleanup_region_start_noarg (flush_cleanup);
  _IO_lock_lock (list_all_lock);
#endif
  for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
    {
      run_fp = fp;
      if (do_lock)
	    _IO_flockfile (fp);
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
	   || (_IO_vtable_offset (fp) == 0
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
	   )
	  && _IO_OVERFLOW (fp, EOF) == EOF) 
    //调用 vtable中的 overflow指针
	result = EOF;

      if (do_lock)
	   _IO_funlockfile (fp);
      run_fp = NULL;
    }

#ifdef _IO_MTSAFE_IO
  _IO_lock_unlock (list_all_lock);
  _IO_cleanup_region_end (0);
#endif
  return result;
}

而_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:

  1. 当 libc 执行 abort 流程时
  2. 当执行 exit 函数时
  3. 当执行流从 main 函数返回时

需要满足的条件

1. _IO_list_all写入一个可控堆地址
2.  FAKE FILE+0x88(_IO_lock_t *_lock)的值=writable addr
3.  FAKE FILE+0xc0(fp->_mode)的值=0
4.  FAKE FILE+0x28的值>FAKE FILE+0x20的值(fp->_IO_write_ptr > fp->_IO_write_base)

_IO_flockfile

因此fp->_lock要填入一个可写地址

# define _IO_flockfile(_fp) \
  if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)->_lock)

#define _IO_lock_lock(_name)	__libc_lock_lock_recursive (_name)

#define __libc_lock_lock_recursive(NAME)   \
  ({   \
     __libc_lock_recursive_t *const __lock = &(NAME);   \
     void *__self = __libc_lock_owner_self ();   \
     if (__self != __lock->owner)   \
       {   \
         lll_lock (__lock->lock, 0);   \
         __lock->owner = __self;   \
       }   \
     ++__lock->cnt;   \
     (void)0;   \
   })

__malloc_assert

目前的理解用malloc_assert都是要修改stderr 只有house of kiwi用fflush (stderr) 其他的house系列都用__fxprintf

  • 触发malloc_assert large bin attack 去篡改 top chunk 的 size 将其改为非法(要往小了改,因为只有 top chunk 无法满足要申请的 size 时,才会触发 sysmalloc) 注意 large bin attack 想将 top chunk 的 size 改小的话,需要地址错位
  assert ((old_top == initial_top (av) && old_size == 0) ||
          ((unsigned long) (old_size) >= MINSIZE &&
           prev_inuse (old_top) &&
           ((unsigned long) old_end & (pagesize - 1)) == 0));

  /* Precondition: not enough current space to satisfy nb request */
  assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
       const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
           __progname, __progname[0] ? ": " : "",
           file, line,
           function ? function : "", function ? ": " : "",
           assertion);
fflush (stderr);
abort ();
}

这里有这样一条执行链 __malloc_assert-> __fxprintf->__vfxprintf->locked_vfxprintf->__vfprintf_internal->_IO_file_xsputn,但要注意这个是stderr的vtable

int
__fxprintf (FILE *fp, const char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  int res = __vfxprintf (fp, fmt, ap, 0);
  va_end (ap);
  return res;
}
__vfxprintf (FILE *fp, const char *fmt, va_list ap,
	     unsigned int mode_flags)
{
  if (fp == NULL)
    fp = stderr;
  _IO_flockfile (fp);//在这个地方会有一个检查,我们需要回复lock字段的值
  int res = locked_vfxprintf (fp, fmt, ap, mode_flags);
  _IO_funlockfile (fp);
  return res;
}

locked_vfxprintf (FILE *fp, const char *fmt, va_list ap,
		  unsigned int mode_flags)
{
  if (_IO_fwide (fp, 0) <= 0)
    return __vfprintf_internal (fp, fmt, ap, mode_flags);
}

vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{
	...
        经过一系列跳转到执行了IO_validate_vtable
}

IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  uintptr_t ptr = (uintptr_t) vtable;
  uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    _IO_vtable_check ();
  return vtable; 调用vtable表中偏移0x38的位置
      
}
  • fflush (stderr)
# define fflush(s) _IO_fflush (s)
int
_IO_fflush (FILE *fp)
{
  if (fp == NULL)
    return _IO_flush_all ();
  else
    {
      int result;
      CHECK_FILE (fp, EOF);
      _IO_acquire_lock (fp);
      result = _IO_SYNC (fp) ? EOF : 0;
      _IO_release_lock (fp);
      return result;
    }
}

_IO_jump_t

struct _IO_jump_t
{
   0 JUMP_FIELD(size_t, __dummy);
   0x8 JUMP_FIELD(size_t, __dummy2);
   0x10 JUMP_FIELD(_IO_finish_t, __finish);
   0x18 JUMP_FIELD(_IO_overflow_t, __overflow);
   0x20 JUMP_FIELD(_IO_underflow_t, __underflow);
   0x28 JUMP_FIELD(_IO_underflow_t, __uflow);
   0x30 JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
   0x38 JUMP_FIELD(_IO_xsputn_t, __xsputn);
   0x40 JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
   0x48 JUMP_FIELD(_IO_seekoff_t, __seekoff);
   0x50 JUMP_FIELD(_IO_seekpos_t, __seekpos);
   0x58 JUMP_FIELD(_IO_setbuf_t, __setbuf);
   0x60 JUMP_FIELD(_IO_sync_t, __sync);
   0x68 JUMP_FIELD(_IO_doallocate_t, __doallocate);
   0x70 JUMP_FIELD(_IO_read_t, __read);
   0x78 JUMP_FIELD(_IO_write_t, __write);
   0x80 JUMP_FIELD(_IO_seek_t, __seek);
   0x88 JUMP_FIELD(_IO_close_t, __close);
   0x90 JUMP_FIELD(_IO_stat_t, __stat);
   0x98 JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
   0x100 JUMP_FIELD(_IO_imbue_t, __imbue);
};

__io_vtables

extern const struct _IO_jump_t __io_vtables[] attribute_hidden;
#define _IO_str_jumps                    (__io_vtables[IO_STR_JUMPS])
#define _IO_wstr_jumps                   (__io_vtables[IO_WSTR_JUMPS])
#define _IO_file_jumps                   (__io_vtables[IO_FILE_JUMPS])
#define _IO_file_jumps_mmap              (__io_vtables[IO_FILE_JUMPS_MMAP])
#define _IO_file_jumps_maybe_mmap        (__io_vtables[IO_FILE_JUMPS_MAYBE_MMAP])
#define _IO_wfile_jumps                  (__io_vtables[IO_WFILE_JUMPS])
#define _IO_wfile_jumps_mmap             (__io_vtables[IO_WFILE_JUMPS_MMAP])
#define _IO_wfile_jumps_maybe_mmap       (__io_vtables[IO_WFILE_JUMPS_MAYBE_MMAP])
#define _IO_cookie_jumps                 (__io_vtables[IO_COOKIE_JUMPS])
#define _IO_proc_jumps                   (__io_vtables[IO_PROC_JUMPS])
#define _IO_mem_jumps                    (__io_vtables[IO_MEM_JUMPS])
#define _IO_wmem_jumps                   (__io_vtables[IO_WMEM_JUMPS])
#define _IO_printf_buffer_as_file_jumps  (__io_vtables[IO_PRINTF_BUFFER_AS_FILE_JUMPS])
#define _IO_wprintf_buffer_as_file_jumps (__io_vtables[IO_WPRINTF_BUFFER_AS_FILE_JUMPS])
#define _IO_old_file_jumps               (__io_vtables[IO_OLD_FILE_JUMPS])
#define _IO_old_proc_jumps               (__io_vtables[IO_OLD_PROC_JUMPS])
#define _IO_old_cookie_jumps             (__io_vtables[IO_OLD_COOKIED_JUMPS])

_IO_wide_data

struct _IO_wide_data
{
  0 wchar_t *_IO_read_ptr;    /* Current read pointer */
  0x8 wchar_t *_IO_read_end;    /* End of get area. */
  0x10 wchar_t *_IO_read_base;    /* Start of putback+get area. */
  0x18 wchar_t *_IO_write_base;    /* Start of put area. */
  0x20 wchar_t *_IO_write_ptr;    /* Current put pointer. */
  0x28 wchar_t *_IO_write_end;    /* End of put area. */
  0x30 wchar_t *_IO_buf_base;    /* Start of reserve area. */
  0x38 wchar_t *_IO_buf_end;        /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  0x40 wchar_t *_IO_save_base;    /* Pointer to start of non-current get area. */
  0x48 wchar_t *_IO_backup_base;    /* Pointer to first valid character of
                   backup area */
  0x50 wchar_t *_IO_save_end;    /* Pointer to end of non-current get area. */
  
  __mbstate_t _IO_state;
  __mbstate_t _IO_last_state;
  struct _IO_codecvt _codecvt;
  wchar_t _shortbuf[1];
  0xe0 const struct _IO_jump_t *_wide_vtable;
};

_IO_FILE

struct _IO_FILE
{
 0 int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
 0x8 char *_IO_read_ptr;	/* Current read pointer */
 0x10 char *_IO_read_end;	/* End of get area. */
 0x18 char *_IO_read_base;	/* Start of putback+get area. */
 0x20 char *_IO_write_base;	/* Start of put area. */
 0x28 char *_IO_write_ptr;	/* Current put pointer. */
 0x30 char *_IO_write_end;	/* End of put area. */
 0x38 char *_IO_buf_base;	/* Start of reserve area. */
 0x40 char *_IO_buf_end;	/* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
 0x48 char *_IO_save_base; /* Pointer to start of non-current get area. */
 0x50 char *_IO_backup_base;  /* Pointer to first valid character of backup area */
 0x58 char *_IO_save_end; /* Pointer to end of non-current get area. */

 0x60 struct _IO_marker *_markers;

 0x68 struct _IO_FILE *_chain;

 0x70 int _fileno;
 0x78 int _flags2;
 0x80 __off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  0x88 _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_FILE_complete
{
  struct _IO_FILE_file;
#endif
 0x90 __off64_t _offset;
  /* Wide character stream stuff.  */
 0x98 struct _IO_codecvt *_codecvt;
 0xa0 struct _IO_wide_data *_wide_data;
 0xa8 struct _IO_FILE *_freeres_list;
 0xb0 void *_freeres_buf;
 0xb8 size_t __pad5;
 0xc0 int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
 0xd8 vtable;
};
  • 大部分的命名和常规认识是一致的,这里需要格外注意的是flags

_IO_list_all

_IO_FILE_plus

struct _IO_FILE_plus
{
  FILE file;
  const struct _IO_jump_t *vtable;
};

#ifdef _IO_USE_OLD_IO_FILE

struct _IO_FILE_complete_plus
{
  struct _IO_FILE_complete file;
  const struct _IO_jump_t *vtable;
};
#endif

其实就是把IO_FILE给包装起来,然后加入了vtable,64位偏移为0xd8,这个偏移其实就是结构体里面偏移,因为前面是结构体struct IO_FILE