利用_IO_FILE结构

D0wnBe@t Lv4

参考《CTF竞赛权威指南(PWN篇)》,ctf.wiki

1.FILE结构体

文章:https://www.yuque.com/yuqueyonghupiiwso/gixo00/bekmilyu3sn6sh3e?singleDoc# 《FILE结构》

可以参考上面写的文章,下面简单说一下吧。

FILE结构体

  • FILE结构被被一系列流操作函数(fopen(),fread,fclose()等)所使用、大多数的FILE结构体保存在堆上(stdin,stdout,stderr除外,位于libc数据段),其指针动态创建并由fopen()函数返回。在libc的2.23版本中,这个结构体是_IO_FILE_plus,包含了一个_IO_FILE结构体和一个指向_IO_jump_t结构体的指针。

源码网址:https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/libioP.h#L307

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
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};

/* We always allocate an extra word following an _IO_FILE.
This contains a pointer to the function jump table used.
This is for compatibility with C++ streambuf; the word can
be used to smash to a pointer to a virtual function table. */

struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
  • vtable指向的函数跳转表其实是一种兼容C++虚函数的实现。当程序对某个流进行操作时,会调用该流对应的跳转表中的某个函数。
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
65
66
67
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
};

struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
# else
void *__pad1;
void *__pad2;
void *__pad3;
void *__pad4;
# endif
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};
  • 进程中的FILE结构通过_chain域构成一个链表,链表头部为_IO_list_all全局变量,默认情况下依次链接了stddrr、stdout和stdin三个文件流,并将新创建的流插入到头部

  • 另外,_IO_wide_data也是后面所需要的

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
/* Extra data for wide character streams.  */
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */

__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;

wchar_t _shortbuf[1];

const struct _IO_jump_t *_wide_vtable;
};
#endi

2.FSOP

  • FSOP(File Stream Oriented Programming)是一种劫持_IO_list_all(libc.so中的全局变量)来伪造链表的利用技术,通过调用_IO_flush_all_lockp()函数触发。该函数会在下面三种情况下被调用:
  1. 当libc检测到内存错误从而执行abort流程时;🤯

  2. 执行exit函数时;🤯🤯

  3. 当main函数返回时。🤯🤯🤯

  • 当libc检查到内存错误时,会产生下面的函数调用路径
1
malloc_printerr->_libc_message->_GI_abort->_IO_flush_all_lockp->_IO_OVERFLOW

因此衍生出了FSOP,通过伪造、_IO_jump_t中的_overflowsystem函数地址,最终在_IO_OVERFLOW(fp,EOF)函数中执行system(‘/bin/sh’)并获得shell.

源码地址

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
int _IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;
int last_stamp;
······

last_stamp = _IO_list_all_stamp;
fp = (_IO_FILE *) _IO_list_all; // 覆盖为伪造的链表
while (fp != NULL) {
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
# 条件 if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF) // fp指向伪造的vtable,触发虚函数
result = EOF;

if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;

if (last_stamp != _IO_list_all_stamp)
{
/* Something was added to the list. Start all over again. */
fp = (_IO_FILE *) _IO_list_all;
last_stamp = _IO_list_all_stamp;
}
else
fp = fp->_chain; // 指向下一个_IO_FILE对象
}
.......

return result;
}
  • 条件
1
2
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
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
int
_IO_new_fclose (_IO_FILE *fp)
{
int status;

CHECK_FILE(fp, EOF);
......

/* First unlink the stream. */
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);

_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp); // fp指向虚表
if (fp->_mode > 0)
{
......
}
else
{
......
}

return status;
}
  • 标题: 利用_IO_FILE结构
  • 作者: D0wnBe@t
  • 创建于 : 2024-12-05 14:53:51
  • 更新于 : 2024-12-05 19:13:52
  • 链接: http://downbeat.top/2024/12/05/利用-IO-FILE结构/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
利用_IO_FILE结构