查看: 107|回复: 0

[Pwn] IO FILE之fopen详解

[复制链接]
发表于 2020-5-4 17:39:09 | 显示全部楼层 |阅读模式

前言  最近打算详细整理下IO FILE相关的笔记,不少地方都是知道个大概,因此这次打算从源码出发,把IO FILE相关的东西都过一遍。
  思路大致是fopen、fwrite以及fread之类的IO函数的源码分析,再到libc2.24对vtable检查之前的利用方式,再到vtable检查的分析以及相应的对抗方式。
  第一篇fopen详解,主要是基于源码的分析,源码的动态调试建议大家使用带调试符号的glibc,再次给大家推荐pwn_debug,可以很方便的安装带调试符号的glibc,使用debug模式即可。
源码分析  首先编写一个简单的调用fopen函数的c程序。
#include  int main(){
  FILE*fp=fopen("test","wb");
  char *ptr=malloc(0x20);
  return 0;
  }
  

  编译出来之后使用pwn_debug的debug模式开启程序,或者指定带调试符号的glibc,我这里使用的glibc版本是2.23。接下来开始分析。
  gdb跟进去fopen函数,可以看到fopen实际上是 _IO_new_fopen函数,该函数在/libio/iofopen.c文件中,可以看到它调用的是__fopen_internal:
_IO_FILE *  _IO_new_fopen (const char *filename, const char *mode)
  {
  return __fopen_internal (filename, mode, 1);
  }
  

  跟进去__fopen_internal中,关键源码如下:
_IO_FILE *  __fopen_internal (const char *filename, const char *mode, int is32)
  {
  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));  ## step 1 分配内存
  ...
  _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps); ## step 2 null初始化结构体数据
  ...
  _IO_JUMPS (&new_f->fp) = &_IO_file_jumps; ## 设置vtable为_IO_file_jumps
  _IO_file_init (&new_f->fp); ## step 3 将file结构体链接进去_IO_list_all
  ...
  # step 4 打开文件
  if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
  return __fopen_maybe_mmap (&new_f->fp.file);
  }
  

  整个__fopen_internal函数包含四个部分:
  • malloc分配内存空间。
  • _IO_no_init 对file结构体进行null初始化。
  • _IO_file_init将结构体链接进_IO_list_all链表。
  • _IO_file_fopen执行系统调用打开文件。
  下面详细分析跟进去每个子函数进行分析。
malloc分配内存空间  可以看到首先调用malloc函数分配了一个struct locked_FILE大小的结构体,这个结构体比函数刚开始的地方定义,在64位系统中为0x230,该结构体包含三个_IO_FILE_plus、_IO_lock_t、_IO_wide_data,其中_IO_FILE_plus为使用的IO FILE的结构体。执行完malloc后内存状态如下:

IO FILE之fopen详解

IO FILE之fopen详解

_IO_no_init 对file结构体进行null初始化  在分配完空间后,接着就调用_IO_no_init函数去null初始化结构体,跟进去该函数,函数在/libio/genops.c中:
void  _IO_old_init (_IO_FILE *fp, int flags)
  {
  fp->_flags = _IO_MAGIC|flags;
  fp->_flags2 = 0;
  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;
  ...
  fp->_vtable_offset = 0;
  ...
  }
  void
  _IO_no_init (_IO_FILE *fp, int flags, int orientation,
  struct _IO_wide_data *wd, const struct _IO_jump_t *jmp)
  {
  _IO_old_init (fp, flags);
  fp->_mode = orientation;
  ...
  ## 初始化fp的_wide_data字段
  fp->_wide_data = wd;
  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;
  ...
  fp->_freeres_list = NULL;
  }
  

  可以看到函数最主要的功能是初始化locked_FILE里面的_IO_FILE_plus结构体,基本上将所有的值都初始化为null以及默认值,同时将_wide_data字段赋值并初始化。初始化结束后,FILE结构体如下:

IO FILE之fopen详解

IO FILE之fopen详解

_IO_file_init将结构体链接进_IO_list_all  在执行完_IO_no_init函数后,回到__fopen_internal函数,函数将_IO_FILE_plus结构体的vtable设置成了_IO_file_jumps,然后调用_IO_file_init将_IO_FILE_plus结构体链接进入_IO_list_all链表,跟进去函数,函数在/libio/fileops.c中:
void  _IO_new_file_init (struct _IO_FILE_plus *fp)
  {
  fp->file._offset = _IO_pos_BAD;
  fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;
  ## 调用_IO_link_in和设置_fileno
  _IO_link_in (fp);
  fp->file._fileno = -1;
  }
  libc_hidden_ver (_IO_new_file_init, _IO_file_init)
  

  看到这个函数的主体就是调用了_IO_link_in函数,跟进去,函数在/libio/genops.c中:
void  _IO_link_in (struct _IO_FILE_plus *fp)
  {
  ## 检查flag的标志位是否是_IO_LINKED
  if ((fp->file._flags & _IO_LINKED) == 0)
  {
  ## 设置_IO_LINKED标志位
  fp->file._flags |= _IO_LINKED;
  ...
  fp->file._chain = (_IO_FILE *) _IO_list_all;
  _IO_list_all = fp;
  ++_IO_list_all_stamp;
  ...
  }
  }
  libc_hidden_def (_IO_link_in)
  

  之前一直都知道FILE结构体是通过_IO_list_all的单链表进行管理的,这里_IO_link_in函数的功能是检查FILE结构体是否包含_IO_LINKED标志,如果不包含则表示这个结构体没有链接进入_IO_list_all,则再后面把它链接进入_IO_list_all链表,同时设置FILE结构体的_chain字段为之前的链表的值,否则直接返回。
  所以_IO_file_init主要功能是将FILE结构体链接进入_IO_list_all链表,在没执行_IO_file_init函数前_IO_list_all指向的是stderr结构体:

IO FILE之fopen详解

IO FILE之fopen详解

执行完后可以看到_IO_list_all指向的是申请出来的结构体:

IO FILE之fopen详解

IO FILE之fopen详解
同时此时FILE结构体的_chain字段指向了之前的stderr结构体:

IO FILE之fopen详解

IO FILE之fopen详解

_IO_file_fopen打开文件句柄  将FILE结构体链接到_IO_list_all链表后,程序返回到__fopen_internal中,接下来就调用_IO_new_file_fopen函数,跟进去该函数,函数在libio/fileops.c文件中:
_IO_FILE *  _IO_new_file_fopen (_IO_FILE *fp, const char *filename, const char *mode,
  int is32not64)
  {
  ...
  ## 检查文件是否以打开,打开则返回
  if (_IO_file_is_open (fp))
  return 0;
  ## 设置文件打开模式
  switch (*mode)
  {
  case 'r':
  omode = O_RDONLY;
  read_write = _IO_NO_WRITES;
  break;
  ...
  }
  ...
  ## 调用_IO_file_open函数
  result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
  is32not64);
  ...
  }
  libc_hidden_ver (_IO_new_file_fopen, _IO_file_fopen)
  

  函数先检查文件描述符是否打开,然后设置文件打开的模式,最后调用_IO_file_open函数,跟进去_IO_file_open函数,该函数在/libio/fileops.c里面:
_IO_FILE *  _IO_file_open (_IO_FILE *fp, const char *filename, int posix_mode, int prot,
  int read_write, int is32not64)
  {
  int fdesc;
  ...
  # 调用系统函数open打开文件
  fdesc = open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
  ...
  # 将文件描述符设置到FILE结构体的相应字段_fileno里
  fp->_fileno = fdesc;
  ...
  #再次调用_IO_link_in
  _IO_link_in ((struct _IO_FILE_plus *) fp);
  return fp;
  }
  libc_hidden_def (_IO_file_open)
  

  函数的主要功能就是执行系统调用open打开文件,并将文件描述符赋值给FILE结构体的_fileno 字段,最后再次调用_IO_link_in函数,确保该结构体被链接进入_IO_list_all链表。
  执行完_IO_new_file_fopen函数后,FILE结构体为:

IO FILE之fopen详解

IO FILE之fopen详解

  该函数执行完后,程序返回FILE结构体指针,分析结束
小结  看完代码后,可以将fopen整体的流程可以归纳为:
  • malloc分配内存空间。
  • _IO_no_init 对file结构体进行null初始化。
  • _IO_file_init将结构体链接进_IO_list_all链表。
  • _IO_file_fopen执行系统调用打开文件。
  整个流程还是比较简单的,fopen返回之后_IO_list_all链表指向返回的FILE结构体,且FILE结构体的_chain字段指向之前的结构体(没有其他额外打开文件的话,将是指向stderr),同时其他的字段大多都是默认的null值,vtable存储的是__GI__IO_file_jumps函数表,截图如下。

IO FILE之fopen详解

IO FILE之fopen详解




温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
论坛交流群:672619046
微信公众号
快速回复 返回顶部 返回列表