IO_FILE leak

上次比赛出了个house of roman思路的题,结果发现这个利用方式已经落后于时代了,当大家纷纷把这题秒了的时候,我顿时对自己产生了怀疑。所以记录一下。。。。

其实主要思路就是修改stdoutflag位为0xfbad1800,并且将_IO_write_base的最后一个字节改小,从而实现多输出一些内容,这些内容里面就包含了libc地址。

stdout泄露

为什么flag要改成0xfbad1800,看源码:

puts函数在源码中是由_IO_puts实现的,而_IO_puts函数内部会调用_IO_sputn,结果会执行_IO_new_file_xsputn,最终会执行_IO_overflow

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
int 
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{//避免进入
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL){
......//避免进入
......
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base); //进入目标
if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return EOF;
*f->_IO_write_ptr++ = ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (_IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
return EOF;
return (unsigned char) ch;
}

可以看到_IO_do_write是最后调用的函数,而_IO_write_base是我们要修改的目标。

这里f->_flag & _IO_NO_WRITES的值应该为0,为了不进入第一个if分支

同时使f->_flag &_IO_CURRENTLY_PUTTING的值为1,为了不进入第二个if分支

_IO_do_write函数的参数为:stdout结构体、_IO_write_basesize(由f->_IO_write_ptr - f->_IO_write_base决定),而_IO_do_write实际会调用new_do_write,参数一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
...
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
fp->_offset = _IO_pos_BAD;
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;
}
// 调用函数输出输出缓冲区
count = _IO_SYSWRITE (fp, data, to_do); //最终输出
...

return count;
}

这里,_IO_SYSWRITE就是我们的目标,这相当于write(fp,data,to_do)

_IO_SYSSEEK只是简单的调用lseek,但是我们不能完全控制fp->_IO_write_base - fp->_IO_read_end的值,如果fp->_IO_read_end的值设置为0,那么_IO_SYSSEEK的第二个参数值就会过大,如果设置fp->_IO_write_base = fp->_IO_read_end的话,那么在其它地方就会有问题,因为fp->_IO_write_base 不能大于 fp->_IO_write_end。所以这里要设置fp->_flags | _IO_IS_APPENDING,避免进入else if 分支。

最终需要构造的fp-flags是这样的,才能绕过上面提到的分支。

1
2
3
4
_flags = 0xfbad0000 
_flags &= ~_IO_NO_WRITES ## _flags = 0xfbad0000
_flags |= _IO_CURRENTLY_PUTTING ## _flags = 0xfbad0800
_flags |= _IO_IS_APPENDING ## _flags = 0xfbad1800

所以通常将stdout的flags修改成0xfbad1800,将_IO_write_base改小,就可以造成libc的泄漏。

-------------本文结束感谢您的阅读-------------
+ +