IO_FILE的具体结构和功能在FSOP中写过,这里主要关注下其中的打印函数_IO_2_1_stdout的相关利用,通常用来在堆利用时,由于没有show之类的打印堆块内容的选项,导致无法泄露libc地址的情况。
前言
这里的_flags在_IO_2_1_stdout结构体中,一旦我们想要通过_IO_2_1_stdout来打印指定内存地址的内容,就需要对_flags的值进行设置,绕过一些检查,才能最终进入_IO_SYSWRITE函数打印。
1._IO_new_file_overflow检查:
条件一
不能进入,判断语句需要为假,否则直接返回EOF了。
1 2 3 4 5 6 7 8
| #注释头
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */ { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; }
|
需要满足条件:f->_flags & _IO_NO_WRITES == false
条件二
不能进入,判断语句需要为假
1 2 3
| #注释头
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
|
因为这里一旦进入,就会有一个初始化指针的操作,导致我们的_IO_write_base被覆盖,从而无法输出想要的地址的内容。
需要满足条件:((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL) == false
之后就跳到如下语句:
1 2 3 4
| #注释头
if (ch == EOF) return _IO_do_write (f, f->_IO_write_base,f->_IO_write_ptr - f->_IO_write_base);
|
进入_IO_do_write函数。
2._IO_do_write检查:
▲由于如下定义:libc_hidden_ver (_IO_new_do_write, _IO_do_write),该函数成了_IO_new_do_write函数,定义如下:
1 2 3 4 5 6 7
| #注释头
_IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do) { return (to_do == 0 || (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF; }
|
这个函数中没什么操作,直接进入到new_do_write函数.
3.new_do_write检查:
1 2 3 4
| #注释头
if (fp->_flags & _IO_IS_APPENDING) else if (fp->_IO_read_end != fp->_IO_write_base)
|
这里其实不太明白,很多地方说要满足其中一个才能进入到实际调用打印的系统函数:
1 2 3
| #注释头
count = _IO_SYSWRITE (fp, data, to_do);
|
但是我认为if和else if都绕过应该也可以运行到count的执行语句,不知道是不是因为需要设置fp->_offset才能打印,那如果是的话,控制_IO_2_1_stdout的结构体不也能设置fp->_offset的值吗。
(1)针对if (fp->_flags & _IO_IS_APPENDING):
这个可以进入,影响不大
1 2 3 4
| #注释头
if (fp->_flags & _IO_IS_APPENDING) fp->_offset = _IO_pos_BAD;
|
(2)针对else if (fp->_IO_read_end != fp->_IO_write_base)
这个不太能够进入,因为该语句如下:
1 2 3 4 5 6 7
| else if (fp->_IO_read_end != fp->_IO_write_base) { _IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1); if (new_pos == _IO_pos_BAD) return 0; fp->_offset = new_pos; }
|
因为_IO_SYSSEEK可能会执行错误,崩溃,无法到达count的执行语句。而且fp->_IO_read_end != fp->_IO_write_base判断语句满足的概率相当大,这就导致如果第一个if不进入,那么第二个else if就有很大概率进入,然后就可能会崩溃。所以在只能设置flags值的情况下还是进入第一个If语句是最好的选择。
所以需要满足条件:fp->_flags & _IO_IS_APPENDING == true
▲后面才想明白是因为如果只设置flags的话,而_IO_read_end和_IO_write_base的值无法控制的情况下,最好使程序流进去if (fp->_flags & _IO_IS_APPENDING)语句,而不要使程序流进入else if语句。
4.综上三个条件:
1 2 3 4 5
| #注释头
f->_flags & _IO_NO_WRITES == FALSE ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL) == FALSE fp->_flags & _IO_IS_APPENDING == TRUE
|
再加上flags值的相关宏定义:
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
| #注释头
//高16位 #define _IO_MAGIC 0xFBAD0000 /* Magic number */ #define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */ #define _IO_MAGIC_MASK 0xFFFF0000
//低16位 ------------------------------------------------------------------- #define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */ #define _IO_UNBUFFERED 2 #define _IO_NO_READS 4 /* Reading not allowed */ #define _IO_NO_WRITES 8 /* Writing not allowd */ #define _IO_EOF_SEEN 0x10 #define _IO_ERR_SEEN 0x20 #define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */ #define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/ #define _IO_IN_BACKUP 0x100 #define _IO_LINE_BUF 0x200 #define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */ #define _IO_CURRENTLY_PUTTING 0x800 #define _IO_IS_APPENDING 0x1000 #define _IO_IS_FILEBUF 0x2000 #define _IO_BAD_SEEN 0x4000 #define _IO_USER_LOCK 0x8000
|
flags的高16位为_IO_MAGIC,基本固定,由libc确定,不同版本可能有差异。后面低16位分别对应不同的表示。
可得最终的flags应该为0xFBAD1800,其实也不一定非得是这个值,只要满足以上所列的条件即可:
f->flag & 0xa00 and f->flag & 0x1000 == 1以及f->write_base != f->write_ptr
最后设置_IO_write_base指向想要泄露的位置,_IO_write_ptr指向泄露结束的位置即可。
▲这里需要注意的是_IO_CURRENTLY_PUTTING标志位在程序已经有打印过东西的情况下就已经是1了,没有打印过则为0。