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 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:
- 当 libc 执行 abort 流程时
- 当执行 exit 函数时
- 当执行流从 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