0%

IO_FILE源码分析:fopen

IO_FILE源码分析:fopen

连续做了几个 IO_FILE pwn 的题目,发现自己对于 IO_FILE 漏洞的原理不是很了解,于是打算学习学习 IO_FILE 的源码

我打算先学习源码,之后再学习漏洞利用的底层原理

感谢 raycp 师傅的源码讲解!!!


fopen 是IO中最重要的函数之一,每当我们想打开文件时,第一个调用的便是这个函数

先看看它的源码:(libc-2.23)

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
#include "libioP.h"
#ifdef __STDC__
#include <stdlib.h>
#endif
#ifdef _LIBC
# include <shlib-compat.h>
#else
# define _IO_new_fopen fopen
#endif

_IO_FILE *
_IO_new_fopen (filename, mode)
const char *filename;
const char *mode;
{
struct locked_FILE
{
struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct _IO_wide_data wd;
} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
/* malloc分配内存空间 */
if (new_f == NULL)
return NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
#else /* _IO_no_init对file结构体进行初始化 */
_IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
#endif
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps; /* 设置_IO_file_jumps函数表 */
_IO_file_init (&new_f->fp); /* _IO_file_init将结构体链接进_IO_list_all链表 */
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, 1) != NULL)
return (_IO_FILE *) &new_f->fp; /* 执行系统调用打开文件 */
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
}

#ifdef _LIBC
strong_alias (_IO_new_fopen, __new_fopen)
versioned_symbol (libc, _IO_new_fopen, _IO_fopen, GLIBC_2_1);
versioned_symbol (libc, __new_fopen, fopen, GLIBC_2_1);
#endif

fopen 实际上是 _IO_new_fopen 函数,前面都是一些初始化的操作:

  • malloc分配内存空间
  • _IO_no_init 对FILE结构体进行 null 初始化
  • _IO_file_init 将结构体链接进 _IO_list_all 链表
  • _IO_file_fopen 执行系统调用打开文件

接下来便分别对它们进行分析:

malloc 分配内存空间

1
*new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));

这个“locked_FILE”是个结构体(就是FILE结构体),包含了3个条目:

1
2
3
4
5
6
7
8
  struct locked_FILE
{
struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct _IO_wide_data wd;
}

malloc 将会分配“locked_FILE”结构体大小的内存,最后在”fopen“执行完成后释放掉

_IO_no_init 对FILE结构体进行 null 初始化

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
void
_IO_no_init (fp, flags, orientation, wd, jmp)
_IO_FILE *fp;
int flags;
int orientation;
struct _IO_wide_data *wd;
struct _IO_jump_t *jmp;
{
fp->_flags = _IO_MAGIC|flags;
fp->_IO_buf_base = NULL;
fp->_IO_buf_end = NULL;
fp->_IO_read_base = NULL;
fp->_IO_read_ptr = NULL;
fp->_IO_read_end = NULL;
fp->_IO_write_base = NULL;
fp->_IO_write_ptr = NULL;
fp->_IO_write_end = NULL;
fp->_chain = NULL; /* Not necessary. */

fp->_IO_save_base = NULL;
fp->_IO_backup_base = NULL;
fp->_IO_save_end = NULL;
fp->_markers = NULL;
fp->_cur_column = 0;
#if _IO_JUMPS_OFFSET
fp->_vtable_offset = 0;
#endif
#ifdef _IO_MTSAFE_IO
_IO_lock_init (*fp->_lock);
#endif
fp->_mode = orientation;
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
if (orientation >= 0)
{
fp->_wide_data = wd; /* 初始化fp的_wide_data字段 */
fp->_wide_data->_IO_buf_base = NULL;
fp->_wide_data->_IO_buf_end = NULL;
fp->_wide_data->_IO_read_base = NULL;
fp->_wide_data->_IO_read_ptr = NULL;
fp->_wide_data->_IO_read_end = NULL;
fp->_wide_data->_IO_write_base = NULL;
fp->_wide_data->_IO_write_ptr = NULL;
fp->_wide_data->_IO_write_end = NULL;
fp->_wide_data->_IO_save_base = NULL;
fp->_wide_data->_IO_backup_base = NULL;
fp->_wide_data->_IO_save_end = NULL;

fp->_wide_data->_wide_vtable = jmp;
}
#endif
}

对 _IO_FILE_plus 里面的条目进行置空操作,对 _IO_wide_data 进行赋值并置空

_IO_file_init 将结构体链接进 _IO_list_all 链表

1
2
3
4
5
6
7
8
9
10
11
12
# define _IO_new_file_init _IO_file_init

void
_IO_new_file_init (fp)
struct _IO_FILE_plus *fp;
{
fp->file._offset = _IO_pos_BAD;
fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;

_IO_link_in (fp);
fp->file._fileno = -1;
}

这个函数完成了一些设置(暂时不用管)调用了 _IO_link_in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void
_IO_link_in (fp)
struct _IO_FILE_plus *fp;
{
if ((fp->file._flags & _IO_LINKED) == 0)
/* 检查FILE结构体是否包含_IO_LINKED标志 */
{
fp->file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_lock_lock (list_all_lock); /* 加锁 */
#endif
fp->file._chain = (_IO_FILE *) _IO_list_all;
_IO_list_all = fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_unlock (list_all_lock); /* 解锁 */
#endif
}
}

_chain 字段(指向FILE链表的下一个单元)写入 _IO_list_all ,然后在 _IO_list_all 写入 fp ,此时 fp 成为了FILE链表的头部(全局变量 _IO_list_all 永远指向了FILE链表的头部 )

​ // 标准的插头法,和 fastbin,smallbin,unsortedbin 差不多

_IO_file_fopen 打开文件句柄

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# define _IO_new_file_fopen _IO_file_fopen

_IO_FILE *
_IO_new_file_fopen (fp, filename, mode, is32not64)
_IO_FILE *fp;
const char *filename;
const char *mode;
int is32not64;
{
int oflags = 0, omode;
int read_write;
int oprot = 0666;
int i;
_IO_FILE *result;
#if _LIBC
const char *cs;
#endif

if (_IO_file_is_open (fp)) /* 检查文件描述符是否打开 */
return 0;
switch (*mode) /* 设置文件打开的模式(权限&类型) */
{
case 'r':
omode = O_RDONLY; /* 只读 */
read_write = _IO_NO_WRITES;
break;
case 'w':
omode = O_WRONLY; /* 只写 */
oflags = O_CREAT|O_TRUNC;
read_write = _IO_NO_READS;
break;
case 'a':
omode = O_WRONLY; /* 只写 */
oflags = O_CREAT|O_APPEND;
read_write = _IO_NO_READS|_IO_IS_APPENDING;
break;
default:
__set_errno (EINVAL); /* 设置错误代码 */
return NULL;
}
for (i = 1; i < 4; ++i)
{
switch (*++mode) /* 设置文件打开的模式(附加) */
{
case '\0': /* 无附加 */
break;
case '+': /* 附加读&写权限 */
omode = O_RDWR;
read_write &= _IO_IS_APPENDING;
continue;
case 'x': /* 附加可执行权限 */
oflags |= O_EXCL;
continue;
case 'b': /* 以二进制的形式进行读写 */
default:
continue;
}
break;
}

result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
is32not64);
/* 调用_IO_file_open系统调用进行文件读取,后面的操作就不用管了 */

#if _LIBC
/* Test whether the mode string specifies the conversion. */
cs = strstr (mode, ",ccs=");
if (cs != NULL)
{
/* Yep. Load the appropriate conversions and set the orientation
to wide. */
struct gconv_fcts fcts;
struct _IO_codecvt *cc;

if (! _IO_CHECK_WIDE (fp) || __wcsmbs_named_conv (&fcts, cs + 5) != 0)
{
/* Something went wrong, we cannot load the conversion modules.
This means we cannot proceed since the user explicitly asked
for these. */
_IO_new_fclose (result);
return NULL;
}

cc = fp->_codecvt = &fp->_wide_data->_codecvt;

/* The functions are always the same. */
*cc = __libio_codecvt;

cc->__cd_in.__cd.__nsteps = 1; /* Only one step allowed. */
cc->__cd_in.__cd.__steps = fcts.towc;

cc->__cd_in.__cd.__data[0].__invocation_counter = 0;
cc->__cd_in.__cd.__data[0].__internal_use = 1;
cc->__cd_in.__cd.__data[0].__flags = __GCONV_IS_LAST;
cc->__cd_in.__cd.__data[0].__statep = &result->_wide_data->_IO_state;

cc->__cd_out.__cd.__nsteps = 1; /* Only one step allowed. */
cc->__cd_out.__cd.__steps = fcts.tomb;

cc->__cd_out.__cd.__data[0].__invocation_counter = 0;
cc->__cd_out.__cd.__data[0].__internal_use = 1;
cc->__cd_out.__cd.__data[0].__flags = __GCONV_IS_LAST;
cc->__cd_out.__cd.__data[0].__statep = &result->_wide_data->_IO_state;

/* Set the mode now. */
result->_mode = 1;
}
#endif /* GNU libc */

return result;
}

程序会先检查文件描述符是否打开,然后在 Switch-Case 里面设置文件打开的模式


fopen 只是开胃菜,还没有什么漏洞,接下来的 fread 就是重点了