libc_base
和 heap_base
IO
流操作,常见 exit
或 __malloc_assert
触发_IO_FILE
的 vtable
和 _wide_data
,一般通过 largebin attack
进行控制核心:glibc
对 _IO_wide_data
中的 _wide_vtable
不存在检查。
来看下对 _IO_file_jumps
中虚表指针调用逻辑:
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#if _IO_JUMPS_OFFSET
# define _IO_JUMPS_FUNC(THIS) \
(IO_validate_vtable \
(*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset)))
#else
# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
#endif
可以看到这里是调用了 IO_validate_vtable
对虚表进行了检查的:
/* Perform vtable pointer validation. If validation fails, terminate
the process. */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
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))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
再来看下对 _IO_wide_data
中虚表 _IO_wfile_jumps
的调用逻辑:
#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_FUNC(THIS) _IO_WIDE_JUMPS(THIS)
#define _IO_WIDE_JUMPS(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
可以看到这里并没有对虚表进行检查,而是直接调用的虚表指针。
IO_FILE
的 vtable
为 _IO_wfile_jumps
_wide_data
为可控地址空间_wide_data->_wide_vtable
为可控地址空间IO
流,最终调用 _IO_Wxxxx
函数即可劫持程序执行流所以这里我们需要找到执行 _IO_Wxxxx
函数的链子,原文中给出了三条链子:
_IO_wfile_overflow
_IO_wfile_underflow_mmap
_IO_wdefault_xsgetn
对 fp
的设置如下:
_flags
设置为 ~(2 | 0x8 | 0x800)
,如果不需要控制 rdi
,设置为 0 即可;如果需要获得 shell
,可设置为 sh
;,注意前面有两个空格vtable
设置为 _IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap
地址(加减偏移),使其能成功调用 _IO_wfile_overflow
即可_wide_data
设置为可控堆地址 A
,即满足 *(fp + 0xa0) = A
_wide_data->_IO_write_base
设置为 0,即满足 *(A + 0x18) = 0
_wide_data->_IO_buf_base
设置为 0,即满足 *(A + 0x30) = 0
_wide_data->_wide_vtable
设置为可控堆地址 B
,即满足 *(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate
设置为地址 C
用于劫持 RIP
,即满足 *(B + 0x68) = C
接下来跟进源码去看看为啥这么设置。
_IO_wfile_overflow
wint_t
_IO_wfile_overflow (FILE *f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f);
......
这里当满足:
_flags & _IO_NO_WRITES = 0
_flags & _IO_CURRENTLY_PUTTING = 0
_wide_data->_IO_write_base = 0
_IO_wdoallocbuf(f)
函数#define _IO_NO_WRITES 0x0008
#define _IO_CURRENTLY_PUTTING 0x0800
_IO_wdoallocbuf
void _IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF) // _IO_WXXXX调用
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf, fp->_wide_data->_shortbuf + 1, 0);
}
这里当满足:
_wide_data->_IO_buf_base = 0
_flags & _IO_UNBUFFERED = 0
#define _IO_UNBUFFERED 0x0002
就会调用到 _IO_WDOALLOCATE
函数,所以通过伪造 _wide_data->_wide_vtable->doallocate
即可劫持程序执行流。
对 fp
的设置如下:
_flags
设置为 ~4
,如果不需要控制 rdi,设置为 0 即可;如果需要获得 shell
,可设置为 sh
;,注意前面有个空格vtable
设置为_IO_wfile_jumps_mmap
地址(加减偏移),使其能成功调用 _IO_wfile_underflow_mmap
即可_IO_read_ptr < _IO_read_end
,即满足 *(fp + 8) < *(fp + 0x10)
_wide_data
设置为可控堆地址 A
,即满足 *(fp + 0xa0) = A
_wide_data->_IO_read_ptr >= _wide_data->_IO_read_end
,即满足 *A >= *(A + 8)
_wide_data->_IO_buf_base
设置为 0,即满足 *(A + 0x30) = 0
_wide_data->_IO_save_base
设置为 0 或者合法的可被 free
的地址,即满足 *(A + 0x40) = 0
_wide_data->_wide_vtable
设置为可控堆地址 B
,即满足 *(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate
设置为地址 C
用于劫持 RIP
,即满足 *(B + 0x68) = C
其他的就不多说了就是上面的分析方法,没啥特别的,最后一个链子大家看原文章吧。