IO_FILE

文章发布时间:

最后更新时间:

部分堆题没有给予我们打印堆块中间的机会 这种情况下 无法通过unsortedbin来泄露基址 这里学习一种新办法 通过io file来泄露基址

在一个程序中 初始的文件描述符为1,2,3 分别对应着标准输入 标准输出 标准错误 当我们调用scanf函数或者read函数的时候 就会通过调用文件描述符0来从终端输入数据 也就意味着我们可以利用这一点来做到泄露数据

FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始会自动创建的三个文件即stdin、stdout、stderr,它们是在libc上。

1
2
3
4
5
struct _IO_FILE_plus
{
_IO_FILE file;
_IO_jump_t *vtable;
}

_IO_FILE的源码

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
struct _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
};

_IO_list_all变量,其指向FILE链表的头部,结合上面的结构体可知
file对应的就是_IO_FILE结构类型
vtable对应的就是_IO_jump_t类型。
在没有创建其它文件结构时,_IO_list_all指向stderr,然后依次是stdout和stdin,这里通过在前面加上结构体类型可以详细的打印其内存数据信息

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
pwndbg> p /x *(struct _IO_FILE_plus *) _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 = 0x7ffff7dce760,
_fileno = 0x2,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = {0x0},
_lock = 0x7ffff7dcf8b0,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7dcd780,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0,
_unused2 = {0x0 <repeats 20 times>}
},
vtable = 0x7ffff7dca2a0
}

_IO_file_jumps,同 *vtable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> p _IO_file_jumps
$3 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7ffff7a6e2d0 <_IO_new_file_finish>,
__overflow = 0x7ffff7a6f2b0 <_IO_new_file_overflow>,
__underflow = 0x7ffff7a6efd0 <_IO_new_file_underflow>,
__uflow = 0x7ffff7a70370 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7a71c00 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ffff7a6d8d0 <_IO_new_file_xsputn>,
__xsgetn = 0x7ffff7a6d530 <__GI__IO_file_xsgetn>,
__seekoff = 0x7ffff7a6cb30 <_IO_new_file_seekoff>,
__seekpos = 0x7ffff7a70940 <_IO_default_seekpos>,
__setbuf = 0x7ffff7a6c7f0 <_IO_new_file_setbuf>,
__sync = 0x7ffff7a6c670 <_IO_new_file_sync>,
__doallocate = 0x7ffff7a600b0 <__GI__IO_file_doallocate>,
__read = 0x7ffff7a6d8b0 <__GI__IO_file_read>,
__write = 0x7ffff7a6d130 <_IO_new_file_write>,
__seek = 0x7ffff7a6c8b0 <__GI__IO_file_seek>,
__close = 0x7ffff7a6c7e0 <__GI__IO_file_close>,
__stat = 0x7ffff7a6d120 <__GI__IO_file_stat>,
__showmanyc = 0x7ffff7a71d80 <_IO_default_showmanyc>,
__imbue = 0x7ffff7a71d90 <_IO_default_imbue>
}

_IO_FILE结构体中,_chain字段指向下一个链表节点,此处指向的是stdout

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
pwndbg>  p  *(struct _IO_FILE_plus *) stdout
$1 = {
file = {
_flags = -72540028,
_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 = 0x7ffff7dcda00 <_IO_2_1_stdin_>,
_fileno = 1, //文件描述符,stderr的fileno值为2,stdout的fileno值为1
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7ffff7dcf8c0 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7ffff7dcd8c0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dca2a0 <_IO_file_jumps>
}

利用_fileno字段
_fileno的值就是文件描述符,位于stdin文件结构开头0x70偏移处
我们可以尝试篡改_fileno以重定位需要读取的文件
例子之后再放

IO_FILE_leak

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
int _IO_new_file_overflow (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)
{
/* Allocate a buffer if needed. */
if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
/* Otherwise must be currently reading.
If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
logically slide the buffer forwards one block (by setting the
read pointers to all point at the beginning of the block). This
makes room for subsequent output.
Otherwise, set the read pointers to _IO_read_end (leaving that
alone, so it can continue to correspond to the external position). */
if (__glibc_unlikely (_IO_in_backup (f)))
{
size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
_IO_free_backup_area (f);
f->_IO_read_base -= MIN (nbackup,
f->_IO_read_base - f->_IO_buf_base);
f->_IO_read_ptr = f->_IO_read_base;
}
if (f->_IO_read_ptr == f->_IO_buf_end)
f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
f->_IO_write_ptr = f->_IO_read_ptr;
f->_IO_write_base = f->_IO_write_ptr;
f->_IO_write_end = f->_IO_buf_end;
f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;
f->_flags |= _IO_CURRENTLY_PUTTING;
if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
f->_IO_write_end = f->_IO_write_ptr;
}
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;
}

将堆块分配到stdout指针处存储的_IO_2_1_stdout_该IO_FILE结构体处
修改其_flags为0xfbad1800,将后面三个read指针置空
将_IO_write_base处的第一个字节改为0x58,后面的_IO_write_ptr和_IO_write_end保持不变。
之后当程序遇到puts函数时就会打印_IO_write_base到_IO_write_ptr之间的内容
按照上面步骤改动的话,我们泄露出的第一个libc地址是_IO_file_jumps。
常用的payload如下所示,至于为啥需要flags=0xfbad1800(flags也可以是0xfbad3887),
这里分析起来十分复杂,可以参见puts源码,只能说为了达到输出效果需要这样设置
另外该flags这样设置只是针对puts函数,其余打印函数略有不同。

1
payload = p64(0xfbad1800)+p64(0)*3+b"\x58"
1
payload = p64(0xfbad3887)+p64(0)*3+p8(0)

存一个
environ泄露栈地址

1
p2 = p64(0xfbad1800) + p64(0) * 3 + p64(environ) + p64(environ + 8) * 2 #stdout -> environ leak stack_addr

前面的攻击手段,以后再来探索吧~