IO攻击总结
_IO_FILE结构体相关
truct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
# else
void *__pad1;
void *__pad2;
void *__pad3;
void *__pad4;
# endif
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};
对于_IO_FILE_plus结构体来说,就是上述结构体的基础上封装了一个跳转表
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
其中值得关注的部分如下:
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
int _fileno; //文件描述符
int _mode; //文件操作模式
struct _IO_FILE *_chain; //指向下一个FILE结构体
struct _IO_jump_t *vtable //函数跳转表,这个很重要
vtable是指向_IO_jump_t结构体类型的指针,_IO_jumpt_t结构体的定义如下:
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
这里面的函数都是在IO操作中可能用到的,也是我们进行劫持的主要地方。
其它的赋值相关具体细节可以看fopen系列源码。
IO_FILE 攻击要点
IO调用的vtable 函数
fopen函数是在分配空间,建立FILE结构体,未调用vtable中的函数。
fread函数中调用的vtable函数有:
• _IO_sgetn函数调用了vtable的_IO_file_xsgetn。
• _IO_doallocbuf函数调用了vtable的_IO_file_doallocate以初始化输入缓冲区。
•vtable中的_IO_file_doallocate调用了vtable中的GIIO_file_stat以获取文件信息。_
_•__underflow函数调用了vtable中的_IO_new_file_underflow实现文件数据读取。
• vtable中的_IO_new_file_underflow调用了vtable__GI__IO_file_read最终去执行系统调用read。
fwrite 函数调用的vtable函数有:
• _IO_fwrite函数调用了vtable的_IO_new_file_xsputn。
•_IO_new_file_xsputn函数调用了vtable中的_IO_new_file_overflow实现缓冲区的建立以及刷新缓冲区。
•vtable中的_IO_new_file_overflow函数调用了vtable的_IO_file_doallocate以初始化输入缓冲区。
•vtable中的_IO_file_doallocate调用了vtable中的GIIO_file_stat以获取文件信息。_
_•new_do_write中的_IO_SYSWRITE调用了vtable_IO_new_file_write最终去执行系统调用write。
fclose函数调用的vtable函数有:
• 在清空缓冲区的_IO_do_write函数中会调用vtable中的函数。_
_•关闭文件描述符_IO_SYSCLOSE函数为vtable中的close函数。
•_IO_FINISH函数为vtable中的finish函数。
其他的IO函数功能相类似的调用的应该都差不多,可以参考下。
FSOP
IO_FILE结构体通过_IO_link_in函数链接进入_IO_list_all,我们可以伪造IO_FILE结构体来实现劫持。
glibc中有一个函数_IO_flush_all_lockp,该函数的功能是刷新所有FILE结构体的输出缓冲区
实上,会_IO_flush_all_lockp调用函数的时机包括:
• libc执行abort函数时。
• 程序执行exit函数时。
• 程序从main函数返回时。
IO_FILE 攻击构造
glibc 2.23 伪造vtable
类似于实现堆块任意分配的时候,我们可以在vtable 附近找合适的堆块,改写vtable的地址(注意是在libc的数据段上),然后在合适的偏移处写上one gadget,那么调用相对于vtable函数的时候就会调用one gadget,具体伪造方式也写在下面哪个2.24里面了。
glibc 2.24开始的vtable check及其绕过
这个博客里面有细讲,这里具体说一下怎么构造。可以根据这个看,这是一个正常的IO结构体
pwndbg> p *_IO_list_all
$1 = {
file = {
_flags = -72540025, #这里是伪造成0
_IO_read_ptr = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", #有检查的话这里就要伪造成合适的size 0x61或者0xb1之类的。
_IO_read_end = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "",
_IO_read_base = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "",
_IO_write_base = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", 这里是0
_IO_write_ptr = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", 这里是1,其实就是_IO_write_ptr > _IO_write_base的一个绕过
_IO_write_end = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "",
_IO_buf_base = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", #binsh指针,因为它会被当成参数
_IO_buf_end = 0x7f5769d605a4 <_IO_2_1_stderr_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f5769d60600 <_IO_2_1_stdout_>,
_fileno = 2,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7f5769d61750 <_IO_stdfile_2_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f5769d5f640 <_IO_wide_data_2>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0, # 这里要小于等于0
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f5769d5c440 <__GI__IO_file_jumps> #伪造成fake_table,有检查的话就是_IO_str_jumps的地址-8
}
后面跟上system的地址或者,one gadget,这都是根据偏移算出的。
IO 任意写
1.设置_IO_read_end
等于_IO_read_ptr
。
2.设置_flag &~ _IO_NO_READS
即_flag &~ 0x4
。
3.设置_fileno
为0。
4.设置_IO_buf_base
为write_start
,_IO_buf_end
为write_end
;且使得_IO_buf_end-_IO_buf_base
大于fread要读的数据。
IO任意读
stdout会将数据拷贝至输出缓冲区,并将输出缓冲区中的数据输出出来,所以如果可控stdout结构体,通过构造可实现利用其进行任意地址读以及任意地址写。
这里面就是改flag的值,并且把write_base的最后一个字节置NULL一般就可以泄露了。