IO_FILE源码分析:stdin任意写 如果能过伪造这些缓冲区指针,在一定的条件下应该可以完成任意地址的读写
接下来描述这两部分的原理以及给出相应的题目实践,原理介绍部分是基于已经拥有可以伪造IO FILE结构体的缓冲区指针漏洞的基础上进行的,在后续过程假设我们目标写的地址是write_start
,写结束地址为write_end
,读的目标地址为read_start
,读的结束地址为read_end
字段名称
中文描述
_IO_buf_base
输入输出缓冲区基地址
_IO_buf_end
输入输出缓冲区结束地址
_IO_write_base
输出缓冲区基地址
_IO_write_ptr
输出缓冲区已使用的地址
_IO_write_end
输出缓冲区结束地址
简述 fread 的执行过程:
第一部分是 fp->_IO_buf_base
为空的情况,表明此时的FILE结构体中的指针未被初始化,输入缓冲区未建立,则调用 _IO_doallocbuf
去初始化指针,建立输入缓冲区
第二部分是输入缓冲区里有输入并且够用,此时将缓冲区里的数据直接拷贝至目标buff
第三部分是输入缓冲区里的数据为空或者是不能满足全部的需求,则调用 __underflow
调用系统调用读入数据到缓冲区,然后再把数据从缓冲区中复制给用户
假设我们能过控制输入缓冲区指针,使得输入缓冲区指向想要写的地址,那么在第三步调用系统调用读取数据到输入缓冲区的时候,也就会调用系统调用读取数据到我们想要写的地址,从而实现任意地址写的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 # define _IO_new_file_underflow _IO_file_underflow int _IO_new_file_underflow (fp) _IO_FILE *fp; { _IO_ssize_t count; #if 0 if (fp->_flags & _IO_EOF_SEEN) return (EOF); #endif if (fp->_flags & _IO_NO_READS) { fp->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; } if (fp->_IO_read_ptr < fp->_IO_read_end) return *(unsigned char *) fp->_IO_read_ptr; if (fp->_IO_buf_base == NULL ) { if (fp->_IO_save_base != NULL ) { free (fp->_IO_save_base); fp->_flags &= ~_IO_IN_BACKUP; } _IO_doallocbuf (fp); } if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED)) _IO_flush_all_linebuffered (); _IO_switch_to_get_mode (fp); fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base; fp->_IO_read_end = fp->_IO_buf_base; fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_buf_base; count = _IO_SYSREAD (fp, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base); if (count <= 0 ) { if (count == 0 ) fp->_flags |= _IO_EOF_SEEN; else fp->_flags |= _IO_ERR_SEEN, count = 0 ; } fp->_IO_read_end += count; if (count == 0 ) return EOF; if (fp->_offset != _IO_pos_BAD) _IO_pos_adjust (fp->_offset, count); return *(unsigned char *) fp->_IO_read_ptr; }
将上述条件综合表述为:
设置 _IO_read_end
等于 _IO_read_ptr
设置 _flag &~ _IO_NO_READS
即 _flag &~ 0x4
设置 _fileno
为 0
设置 _IO_buf_base
为 write_start
, _IO_buf_end
为 write_end
且使得 _IO_buf_end-_IO_buf_base
大于fread要读的数据
stdin 任意写漏洞的关键就是这几步:
1 2 3 4 5 6 7 8 9 10 11 12 if (fp->_IO_read_ptr < fp->_IO_read_end) return *(unsigned char *) fp->_IO_read_ptr; ........ fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base; fp->_IO_read_end = fp->_IO_buf_base; fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_buf_base; count = _IO_SYSREAD (fp, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base);
当 _IO_read_ptr
>= _IO_read_end
时,程序就会执行系统调用
然后各种指针都会被更新为 _IO_buf_base
最后执行系统调用向 _IO_buf_base
缓冲区输入数据
stdin 任意写漏洞,其实就是通过改写 _IO_buf_base
来控制缓冲区的位置,进而控制键盘输入数据的位置(键盘输入的数据先放入缓冲区,再复制到目标位置)
通常程序不能直接在 _IO_buf_base
中写入数据(如果可以直接写入,那不就可以打hook了吗),而是可以对其进行覆盖,小幅度调节它的位置,然后利用新读入的数据去覆盖 _IO_buf_end
等其他字段,进而控制程序
讲起来有点抽象,其实我看 raycp 师傅的博客时就不是很明白,但去把博客上附带的那道例题复现了之后就清晰了不少