IO_FILE


IO FILE

IO_FILE结构体:通过chain域连接stderrstdoutstdin的链表,链表的表头是__IO_list_allstderrstdoutstdin是程序启动时打开的文件流,_IO_FILE结构体定义在libio.h头文件中,gdb查看结构体可以使用命令:p *_IO_list_all

struct _IO_FILE {
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  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
};
			        stderr		     stdout		      stdin
IO_list_all  ––⇨     flag	   ¦ ⇨   flag	   ¦ ⇨   flag
                     chain   ––      chain    ––      chain

_IO_FILE_plus结构体:定义在libioP.h中,包含vtable虚函数表

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

vtable:是一个函数指针数组,存储了一个类的虚函数的地址,_IO_jump_t是一个结构体,它定义了一组函数指针,用于实现_IO_FILE_plus结构体中的虚函数表。这些函数指针对应了_IO_FILE_plus结构体中的各种操作,通过虚函数表,可以实现对_IO_FILE_plus结构体中的函数进行动态绑定,使得在运行时可以根据具体对象的类型来调用相应的函数。gdb查看结构体内容时可以使用命令:p *_IO_list_all->vtable

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

_IO_wide_data结构体:结构体中实现了虚表

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

st结构体:struct stat64是一个结构体,用于存储文件的状态信息。在_IO_file_doallocate函数中,通过调用_IO_SYSSTAT宏来获取文件的状态信息,并将其存储在st结构体中。然后根据文件的类型和块大小来确定缓冲区的大小,并使用malloc函数分配相应大小的内存。最后,使用_IO_setb函数将分配的内存设置为文件的缓冲区

struct stat64 {
    __dev_t         st_dev;         /* 文件所在的设备的ID */
    __ino64_t       st_ino;         /* 文件的inode号 */
    __mode_t        st_mode;        /* 文件的类型和访问权限 */
    __nlink_t       st_nlink;       /* 文件的硬链接数 */
    __uid_t         st_uid;         /* 文件的所有者的用户ID */
    __gid_t         st_gid;         /* 文件的所有者的组ID */
    __dev_t         st_rdev;        /* 如果文件是特殊文件(如设备文件),则为其设备号 */
    __off64_t       st_size;        /* 文件的大小(以字节为单位) */
    __blksize_t     st_blksize;     /* 文件系统的块大小 */
    __blkcnt64_t    st_blocks;      /* 文件所占用的块数 */
    struct timespec st_atim;        /* 文件的最后访问时间 */
    struct timespec st_mtim;        /* 文件的最后修改时间 */
    struct timespec st_ctim;        /* 文件的最后状态更改时间 */
    __ino64_t       st_ino;         /* 文件的inode号(备用) */
};

fopen

#include <stdio.h>
#include <stdlib.h>

int main(){
    FILE*fp = fopen("test","wb");
    char *ptr = malloc(0x20);
    return 0;
}

跟进fopen函数

► 0x555555555183 <main+26>    call   fopen@plt                <fopen@plt>
        filename: 0x555555556007 ◂— 0x31b010074736574 /* 'test' */
        modes: 0x555555556004 ◂— 0x74736574006277 /* 'wb' */

可以看到真正调用的是_IO_new_fopen函数,接下来调用__fopen_internal函数

In file: /home/starrysky/IO/glibc-2.23/libio/iofopen.c
    92 }
    93 
    94 _IO_FILE *
    95 _IO_new_fopen (const char *filename, const char *mode)
    96 {
 ►  97   return __fopen_internal (filename, mode, 1);
    98 }

locked_FILE结构体变量new_f分配0x230的空间,结构体如下

 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));
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x231

Top chunk | PREV_INUSE
Addr: 0x55555555b230
Size: 0x20dd1

接下来调用_IO_no_init对结构体中的成员进行null初始化

In file: /home/starrysky/IO/glibc-2.23/libio/iofopen.c
   72     return NULL;
   73 #ifdef _IO_MTSAFE_IO
   74   new_f->fp.file._lock = &new_f->lock;
   75 #endif
   76 #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
 ► 77   _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
   78 #else
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;
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  if (orientation >= 0)
    {
      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;
    }
  else
    /* Cause predictable crash when a wide function is called on a byte
       stream.  */
    fp->_wide_data = (struct _IO_wide_data *) -1L;
#endif
  fp->_freeres_list = NULL;
}

初始化完成之后结构体中的内容如下

pwndbg> p new_f->fp
$1 = {
  file = {
    _flags = -72548352,
    _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 = 0x0,
    _fileno = 0,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x55555555b0f0,
    _offset = 0,
    _codecvt = 0x0,
    _wide_data = 0x55555555b100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x0
}

接下来调用_IO_file_init将该结构体连接到_IO_list_all中,实际调用的是_IO_new_file_init函数

In file: /home/starrysky/IO/glibc-2.23/libio/iofopen.c
   77   _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
   78 #else
   79   _IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
   80 #endif
   81   _IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
 ► 82   _IO_file_init (&new_f->fp);
   83 #if  !_IO_UNIFIED_JUMPTABLES
   84   new_f->fp.vtable = NULL;
void
_IO_new_file_init (struct _IO_FILE_plus *fp)
{
  /* POSIX.1 allows another file handle to be used to change the position
     of our file descriptor.  Hence we actually don't know the actual
     position before we do the first fseek (and until a following fflush). */
  fp->file._offset = _IO_pos_BAD;
  fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;

  _IO_link_in (fp);
  fp->file._fileno = -1;
}
libc_hidden_ver (_IO_new_file_init, _IO_file_init)

_IO_new_file_init函数中调用了_IO_link_in函数用于将文件指针链接到全局文件链表中

void
_IO_link_in (struct _IO_FILE_plus *fp)
{
  if ((fp->file._flags & _IO_LINKED) == 0)
    {
      fp->file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
      _IO_cleanup_region_start_noarg (flush_cleanup);
      _IO_lock_lock (list_all_lock);
      run_fp = (_IO_FILE *) fp;
      _IO_flockfile ((_IO_FILE *) fp);
#endif
      fp->file._chain = (_IO_FILE *) _IO_list_all;
      _IO_list_all = fp;
      ++_IO_list_all_stamp;
#ifdef _IO_MTSAFE_IO
      _IO_funlockfile ((_IO_FILE *) fp);
      run_fp = NULL;
      _IO_lock_unlock (list_all_lock);
      _IO_cleanup_region_end (0);
#endif
    }
}

函数首先检查文件指针的_flags属性是否包含 _IO_LINKED 标志位,如果不包含,则说明还没有链接到链表中,接下来,将结构体的 _chain 属性设置为之前的链表的值并设置_IO_LINKED标志位,查看链表中的内容,将会指向新加入的结构体

pwndbg> p _IO_list_all
$3 = (struct _IO_FILE_plus *) 0x55555555b010

调用_IO_file_fopen打开文件

In file: /home/starrysky/IO/glibc-2.23/libio/iofopen.c
   81   _IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
   82   _IO_file_init (&new_f->fp);
   83 #if  !_IO_UNIFIED_JUMPTABLES
   84   new_f->fp.vtable = NULL;
   85 #endif
 ► 86   if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
   87     return __fopen_maybe_mmap (&new_f->fp.file);
   88 
   89   _IO_un_link (&new_f->fp);
   90   free (new_f);

实际调用的是_IO_new_file_fopen

_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;
    ...
    }
#ifdef _LIBC
  last_recognized = mode;
#endif
  for (i = 1; i < 7; ++i)
    {
      switch (*++mode)
	{
	case '\0':
	  break;
	case '+':
	  omode = O_RDWR;
	  read_write &= _IO_IS_APPENDING;
#ifdef _LIBC
	  last_recognized = mode;
#endif
	  continue;
	...
    }

  result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
			  is32not64);
    ...

会先判断文件是否已被打开,如果已经打开则返回,接下来设置打开模式和一些标志位,最后调用_IO_file_open真正打开文件

_IO_file_open (_IO_FILE *fp, const char *filename, int posix_mode, int prot,
	       int read_write, int is32not64)
{
  int fdesc;
#ifdef _LIBC
  if (__glibc_unlikely (fp->_flags2 & _IO_FLAGS2_NOTCANCEL))
    fdesc = open_not_cancel (filename,
			     posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
  else
    fdesc = open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
#else
  fdesc = open (filename, posix_mode, prot);
#endif
  if (fdesc < 0)
    return NULL;
  fp->_fileno = fdesc;
  _IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
  /* For append mode, send the file offset to the end of the file.  Don't
     update the offset cache though, since the file handle is not active.  */
  if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
      == (_IO_IS_APPENDING | _IO_NO_READS))
    {
      _IO_off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
      if (new_pos == _IO_pos_BAD && errno != ESPIPE)
	{
	  close_not_cancel (fdesc);
	  return NULL;
	}
    }
  _IO_link_in ((struct _IO_FILE_plus *) fp);
  return fp;
}
libc_hidden_def (_IO_file_open)

调用open系统调用函数打开文件之后设置fileno标志位,再调用_IO_link_in使结构体进入_IO_list_all,至此fopen函数调试结束

pwndbg> p new_f->fp
$6 = {
  file = {
    _flags = -72539004,
    _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 = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x55555555b0f0,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x55555555b100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
pwndbg> p _IO_list_all
$7 = (struct _IO_FILE_plus *) 0x55555555b010

fread

#include <stdio.h>

int main(){
    char data[20];
    FILE *fp = fopen("test", "rb");
    fread(data, 1, 20, fp);
    return 0;
}

进入read函数之后实际调用的是_IO_fread

In file: /home/starrysky/IO/glibc-2.23/libio/iofread.c
   26 
   27 #include "libioP.h"
   28 
   29 _IO_size_t
   30 _IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
 ► 31 {
   32   _IO_size_t bytes_requested = size * count;
   33   _IO_size_t bytes_read;
   34   CHECK_FILE (fp, 0);
   35   if (bytes_requested == 0)
   36     return 0;

源码如下

_IO_size_t
_IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
  ...
  bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
  _IO_release_lock (fp);
  return bytes_requested == bytes_read ? count : bytes_read / size;
}
libc_hidden_def (_IO_fread)

调用了_IO_sgetn函数

► 0x7ffff7a7c171 <fread+145>    call   _IO_sgetn                <_IO_sgetn>
        arg0: 0x405010 ◂— 0xfbad2488
        arg1: 0x7fffffffde40 —▸ 0x4011a0 (__libc_csu_init) ◂— endbr64 
        arg2: 0x14
  464 _IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n)
  465 {
  466   /* FIXME handle putback buffer here! */
► 467   return _IO_XSGETN (fp, data, n);
  468 }
  469 libc_hidden_def (_IO_sgetn)

该函数调用了_IO_XSGETN函数,实际调用的是_IO_file_xsgetn函数,即vtable__xsgetn函数,作用是从文件流中读取指定数量的字节,并将其存储到指定的缓冲区中

In file: /home/starrysky/IO/glibc-2.23/libio/fileops.c
   1355 }
   1356 libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)
   1357 
   1358 _IO_size_t
   1359 _IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
 ► 1360 {
   1361   _IO_size_t want, have;
   1362   _IO_ssize_t count;
   1363   char *s = data;
   1364 
   1365   want = n;
pwndbg> p *_IO_list_all->vtable
$1 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7ffff7a878f0 <_IO_new_file_finish>,
  __overflow = 0x7ffff7a88660 <_IO_new_file_overflow>,
  __underflow = 0x7ffff7a883d0 <_IO_new_file_underflow>,
  __uflow = 0x7ffff7a89530 <__GI__IO_default_uflow>,
  __pbackfail = 0x7ffff7a8a8b0 <__GI__IO_default_pbackfail>,
  __xsputn = 0x7ffff7a87110 <_IO_new_file_xsputn>,
  __xsgetn = 0x7ffff7a86df0 <__GI__IO_file_xsgetn>,
  __seekoff = 0x7ffff7a863f0 <_IO_new_file_seekoff>,
  __seekpos = 0x7ffff7a89930 <_IO_default_seekpos>,
  __setbuf = 0x7ffff7a86360 <_IO_new_file_setbuf>,
  __sync = 0x7ffff7a862a0 <_IO_new_file_sync>,
  __doallocate = 0x7ffff7a7b130 <__GI__IO_file_doallocate>,
  __read = 0x7ffff7a870d0 <__GI__IO_file_read>,
  __write = 0x7ffff7a86aa0 <_IO_new_file_write>,
  __seek = 0x7ffff7a868a0 <__GI__IO_file_seek>,
  __close = 0x7ffff7a86270 <__GI__IO_file_close>,
  __stat = 0x7ffff7a86a90 <__GI__IO_file_stat>,
  __showmanyc = 0x7ffff7a8aa20 <_IO_default_showmanyc>,
  __imbue = 0x7ffff7a8aa30 <_IO_default_imbue>
}

跟进该函数, 当文件流的缓冲区为空时调用_IO_doallocbuf函数分配新的缓冲区来初始化


 if (fp->_IO_buf_base == NULL)
   {
     /* Maybe we already have a push back pointer.  */
     if (fp->_IO_save_base != NULL)
{
  free (fp->_IO_save_base);
  fp->_flags &= ~_IO_IN_BACKUP;
}
     _IO_doallocbuf (fp);
   }

跟进_IO_doallocbuf函数,函数中再次检查缓冲区是否为空,即是否已初始化

In file: /home/starrysky/IO/glibc-2.23/libio/genops.c
   390 libc_hidden_def (_IO_setb)
   391 
   392 void
   393 _IO_doallocbuf (_IO_FILE *fp)
   394 {
 ► 395   if (fp->_IO_buf_base)
   396     return;
   397   if (!(fp->_flags & _IO_UNBUFFERED) || fp->_mode > 0)
   398     if (_IO_DOALLOCATE (fp) != EOF)
   399       return;
   400   _IO_setb (fp, fp->_shortbuf, fp->_shortbuf+1, 0);
void
_IO_doallocbuf (_IO_FILE *fp)
{
  if (fp->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED) || fp->_mode > 0)
    if (_IO_DOALLOCATE (fp) != EOF)
      return;
  _IO_setb (fp, fp->_shortbuf, fp->_shortbuf+1, 0);
}
libc_hidden_def (_IO_doallocbuf)

if (_IO_DOALLOCATE (fp) != EOF)中调用_IO_doallocbuf分配缓冲区

  0x7ffff7a894b7 <_IO_doallocbuf+39>    mov    rax, qword ptr [rbx + 0xd8]
  0x7ffff7a894be <_IO_doallocbuf+46>    mov    rdi, rbx
► 0x7ffff7a894c1 <_IO_doallocbuf+49>    call   qword ptr [rax + 0x68]        <_IO_file_doallocate>
       rdi: 0x405010 ◂— 0xfbad2488
       rsi: 0x7fffffffde40 —▸ 0x4011a0 (__libc_csu_init) ◂— endbr64 
       rdx: 0x14
       rcx: 0x405010 ◂— 0xfbad2488
In file: /home/starrysky/IO/glibc-2.23/libio/filedoalloc.c
    90  * optimisation) right after the _fstat() that finds the buffer size.
    91  */
    92 
    93 int
    94 _IO_file_doallocate (_IO_FILE *fp)
 ►  95 {
    96   _IO_size_t size;
    97   char *p;
    98   struct stat64 st;
    99 
   100 #ifndef _LIBC

_IO_file_doallocate首先会调用_IO_SYSSTAT,即vtable中的 __stat函数,获取文件状态并修改st结构体

_IO_file_doallocate (_IO_FILE *fp)
{
 ...
  if (fp->_fileno >= 0 && __builtin_expect (_IO_SYSSTAT (fp, &st), 0) >= 0)
    {
      if (S_ISCHR (st.st_mode))
	{
	  /* Possibly a tty.  */
	  if (
#ifdef DEV_TTY_P
	      DEV_TTY_P (&st) ||
#endif
	      local_isatty (fp->_fileno))
	    fp->_flags |= _IO_LINE_BUF;
	}
#if _IO_HAVE_ST_BLKSIZE
      if (st.st_blksize > 0)
	size = st.st_blksize;
#endif
  ...
}
libc_hidden_def (_IO_file_doallocate)
In file: /home/starrysky/IO/glibc-2.23/libio/fileops.c
   1224 }
   1225 libc_hidden_def (_IO_file_seek)
   1226 
   1227 int
   1228 _IO_file_stat (_IO_FILE *fp, void *st)
 ► 1229 {
   1230   return __fxstat64 (_STAT_VER, fp->_fileno, (struct stat64 *) st);
   1231 }
   1232 libc_hidden_def (_IO_file_stat)
   1233 
   1234 int

完成后查看一下st结构体

pwndbg> p st
$3 = {
  st_dev = 2053,
  st_ino = 1112909,
  st_nlink = 1,
  st_mode = 33204,
  st_uid = 1000,
  st_gid = 1000,
  __pad0 = 0,
  st_rdev = 0,
  st_size = 399,
  st_blksize = 4096,
  st_blocks = 8,
  st_atim = {
    tv_sec = 1692447996,
    tv_nsec = 127240661
  },
  st_mtim = {
    tv_sec = 1692447276,
    tv_nsec = 42244262
  },
  st_ctim = {
    tv_sec = 1692447276,
    tv_nsec = 46248351
  },
  __glibc_reserved = {0, 0, 0}
}

size取了st_blksize的值,接下来用这个大小创建堆来分配缓冲区并调用_IO_setb函数设置_IO_list_all中的_IO_buf_base_IO_buf_end,至此完成初始化

_IO_file_doallocate (_IO_FILE *fp)
{
  ...
  p = malloc (size);
  if (__glibc_unlikely (p == NULL))
    return EOF;
  _IO_setb (fp, p, p + size, 1);
  return 1;
}
libc_hidden_def (_IO_file_doallocate)
pwndbg> p *_IO_list_all
$4 = {
  file = {
    _flags = -72539000,
    _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 = 0x405240 "",
    _IO_buf_end = 0x406240 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x4050f0,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x405100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x230 (with flag bits: 0x231)

Allocated chunk | PREV_INUSE
Addr: 0x405230
Size: 0x1010 (with flag bits: 0x1011)

Top chunk | PREV_INUSE
Addr: 0x406240
Size: 0x1fdc0 (with flag bits: 0x1fdc1)

接下来回到_IO_file_xsgetn函数,拷贝缓冲区中原本存在的数据。其中_IO_read_end指向输入缓冲区结束地址,_IO_read_ptr指向输入缓冲区开始地址,该过程即将开始到结束部分的内容复制到目标缓冲区

_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
 ...
  while (want > 0)
    {
      have = fp->_IO_read_end - fp->_IO_read_ptr;
      if (want <= have)
	{
	  memcpy (s, fp->_IO_read_ptr, want);
	  fp->_IO_read_ptr += want;
	  want = 0;
	}
      else
	{
	  if (have > 0)
	    {
#ifdef _LIBC
	      s = __mempcpy (s, fp->_IO_read_ptr, have);
#else
	      memcpy (s, fp->_IO_read_ptr, have);
	      s += have;
#endif
	      want -= have;
	      fp->_IO_read_ptr += have;
	    }

	  /* Check for backup and repeat */
	  if (_IO_in_backup (fp))
	    {
	      _IO_switch_to_main_get_area (fp);
	      continue;
	    }
	...
    }

  return n - want;
}
libc_hidden_def (_IO_file_xsgetn)

最后在目标缓冲区内容长度小于所需长度时调用__underflow函数真正读取文件

if (fp->_IO_buf_base
	      && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
	    {
	      if (__underflow (fp) == EOF)
		break;

	      continue;
	    }

进行一系列检查之后调用_IO_UNDERFLOW来读取文件,实际调用的是_IO_new_file_underflow函数

int
__underflow (_IO_FILE *fp)
{
...
  return _IO_UNDERFLOW (fp);
}
libc_hidden_def (__underflow)
    

_IO_new_file_underflow函数中首先检查是否有读权限

if (fp->_flags & _IO_NO_READS)
    {
      fp->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }

接着判断目标缓冲区是否初始化,若没初始化则调用_IO_doallocbuf初始化

if (fp->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_IO_save_base != NULL)
	{
	  free (fp->_IO_save_base);
	  fp->_flags &= ~_IO_IN_BACKUP;
	}
      _IO_doallocbuf (fp);
    }

初始化file结构体即fp_IO_buf_base

fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;
 fp->_IO_read_end = fp->_IO_buf_base;
 fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
   = fp->_IO_buf_base;
pwndbg> p *fp
$8 = {
  _flags = -72539000,
  _IO_read_ptr = 0x405240 "",
  _IO_read_end = 0x405240 "",
  _IO_read_base = 0x405240 "",
  _IO_write_base = 0x405240 "",
  _IO_write_ptr = 0x405240 "",
  _IO_write_end = 0x405240 "",
  _IO_buf_base = 0x405240 "",
  _IO_buf_end = 0x406240 "",
  _IO_save_base = 0x0,
  _IO_backup_base = 0x0,
  _IO_save_end = 0x0,
  _markers = 0x0,
  _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
  _fileno = 3,
  _flags2 = 0,
  _old_offset = 0,
  _cur_column = 0,
  _vtable_offset = 0 '\000',
  _shortbuf = "",
  _lock = 0x4050f0,
  _offset = -1,
  _codecvt = 0x0,
  _wide_data = 0x405100,
  _freeres_list = 0x0,
  _freeres_buf = 0x0,
  __pad5 = 0,
  _mode = -1,
  _unused2 = '\000' <repeats 19 times>
}

调用_IO_SYSREAD读取文件内容,实际调用的是_IO_file_read函数

count = _IO_SYSREAD (fp, fp->_IO_buf_base,
       fp->_IO_buf_end - fp->_IO_buf_base);
In file: /home/starrysky/IO/glibc-2.23/libio/fileops.c
   1211 _IO_ssize_t
   1212 _IO_file_read (_IO_FILE *fp, void *buf, _IO_ssize_t size)
   1213 {
   1214   return (__builtin_expect (fp->_flags2 & _IO_FLAGS2_NOTCANCEL, 0)
   1215           ? read_not_cancel (fp->_fileno, buf, size)
 ► 1216           : read (fp->_fileno, buf, size));
   1217 }
   1218 libc_hidden_def (_IO_file_read)

函数中调用read函数读取到输入缓冲区中,读入大小为输入缓冲区的大小fp->_IO_buf_end - fp->_IO_buf_base

设置缓冲区结束地址

fp->_IO_read_end += count;

查看一下_IO_list_all,可以看到_IO_read_end已经设置为缓冲区结束位置

pwndbg> p *_IO_list_all
$9 = {
  file = {
    _flags = -72539000,
    _IO_read_ptr = 0x405240 'a' <repeats 200 times>...,
    _IO_read_end = 0x4053cf "",
    _IO_read_base = 0x405240 'a' <repeats 200 times>...,
    _IO_write_base = 0x405240 'a' <repeats 200 times>...,
    _IO_write_ptr = 0x405240 'a' <repeats 200 times>...,
    _IO_write_end = 0x405240 'a' <repeats 200 times>...,
    _IO_buf_base = 0x405240 'a' <repeats 200 times>...,
    _IO_buf_end = 0x406240 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x4050f0,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x405100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

接下来回到_IO_file_xsgetn函数的循环中,再来一次循环将输入缓冲区内容复制给目标缓冲区,fread函数结束

在此过程中调用的vtable函数

  • _IO_sgetn函数调用了vtable_IO_file_xsgetn
  • _IO_doallocbuf函数调用了vtable_IO_file_doallocate以初始化输入缓冲区
  • vtable中的_IO_file_doallocate调用了vtable中的__GI__IO_file_stat以获取文件信息
  • __underflow函数调用了vtable中的_IO_new_file_underflow实现文件数据读取
  • vtable中的_IO_new_file_underflow调用了vtable中的__GI__IO_file_read最终去执行系统调用read

其他输入函数如scanfgets等最终实现读取的函数也是_IO_new_file_underflow,因此利用IO攻击读取函数主要是攻击_IO_new_file_underflow,需要注意的是对标志位的判断的绕过

if (fp->_flags & _IO_NO_READS)
  {
    fp->_flags |= _IO_ERR_SEEN;
    __set_errno (EBADF);
    return EOF;
  }

fwrite

#include<stdio.h>

int main(){
    char *data=malloc(0x1000);
    FILE*fp=fopen("test","wb");
    
    fwrite(data,1,0x30,fp);
    return 0;
}

看一下vtable里有哪些函数

pwndbg> p *_IO_list_all->vtable
$1 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7ffff7a878f0 <_IO_new_file_finish>,
  __overflow = 0x7ffff7a88660 <_IO_new_file_overflow>,
  __underflow = 0x7ffff7a883d0 <_IO_new_file_underflow>,
  __uflow = 0x7ffff7a89530 <__GI__IO_default_uflow>,
  __pbackfail = 0x7ffff7a8a8b0 <__GI__IO_default_pbackfail>,
  __xsputn = 0x7ffff7a87110 <_IO_new_file_xsputn>,
  __xsgetn = 0x7ffff7a86df0 <__GI__IO_file_xsgetn>,
  __seekoff = 0x7ffff7a863f0 <_IO_new_file_seekoff>,
  __seekpos = 0x7ffff7a89930 <_IO_default_seekpos>,
  __setbuf = 0x7ffff7a86360 <_IO_new_file_setbuf>,
  __sync = 0x7ffff7a862a0 <_IO_new_file_sync>,
  __doallocate = 0x7ffff7a7b130 <__GI__IO_file_doallocate>,
  __read = 0x7ffff7a870d0 <__GI__IO_file_read>,
  __write = 0x7ffff7a86aa0 <_IO_new_file_write>,
  __seek = 0x7ffff7a868a0 <__GI__IO_file_seek>,
  __close = 0x7ffff7a86270 <__GI__IO_file_close>,
  __stat = 0x7ffff7a86a90 <__GI__IO_file_stat>,
  __showmanyc = 0x7ffff7a8aa20 <_IO_default_showmanyc>,
  __imbue = 0x7ffff7a8aa30 <_IO_default_imbue>
}

跟进fwrite函数,实际调用了_IO_fwrite

In file: /home/starrysky/IO/glibc-2.23/libio/iofwrite.c
   26 
   27 #include "libioP.h"
   28 
   29 _IO_size_t
   30 _IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
 ► 31 {
   32   _IO_size_t request = size * count;
   33   _IO_size_t written = 0;
   34   CHECK_FILE (fp, 0);
   35   if (request == 0)
   36     return 0;

源码如下:

_IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
  ...
  if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
    written = _IO_sputn (fp, (const char *) buf, request);
  ...
  if (written == request || written == EOF)
    return count;
  else
    return written / size;
}
libc_hidden_def (_IO_fwrite)

主要就是调用了_IO_sputn函数,跟进之后实际调用的是_IO_new_file_xsputn函数,即vtable中的__xsputn

首先计算输出缓冲区剩余空间, 并将所需输出的数据复制到输出缓冲区

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  ...
  if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
    {
      count = f->_IO_buf_end - f->_IO_write_ptr;
      ...
    }
  else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

  /* Then fill the buffer. */
  if (count > 0)
    {
      if (count > to_do)
	count = to_do;
#ifdef _LIBC
      f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
#else
      memcpy (f->_IO_write_ptr, s, count);
      f->_IO_write_ptr += count;
#endif
      s += count;
      to_do -= count;
    }

如果没能完全复制过去,那么说明输出缓冲区未建立或已满,调用_IO_OVERFLOW函数来建立或刷新输出缓冲区,刷新_IO_list_all

In file: /home/starrysky/IO/glibc-2.23/libio/fileops.c
   1326     }
   1327   if (to_do + must_flush > 0)
   1328     {
   1329       _IO_size_t block_size, do_write;
   1330       /* Next flush the (full) buffer. */
 ► 1331       if (_IO_OVERFLOW (f, EOF) == EOF)
   1332         /* If nothing else has to be written we must not signal the
   1333            caller that everything has been written.  */
   1334         return to_do == 0 ? EOF : n - to_do;
   1335 
   1336       /* Try to maintain alignment: write a whole number of blocks.  */

跟进这个函数,实际调用的函数是_IO_new_file_overflow

In file: /home/starrysky/IO/glibc-2.23/libio/fileops.c
   803 
   804 
   805 int
   806 _IO_new_file_overflow (_IO_FILE *f, int ch)
   807 {
 ► 808   if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
   809     {
   810       f->_flags |= _IO_ERR_SEEN;
   811       __set_errno (EBADF);
   812       return EOF;
   813     }

该函数首先判断了是否有可写权限

if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }

若输出缓冲区未创建则创建输出缓冲区,调用_IO_doallocbuf函数分配输入输出缓冲区并将指针_IO_buf_base_IO_buf_end赋值,调用_IO_setg宏将输入相关的缓冲区指针赋值为_IO_buf_base指针

if (f->_IO_write_base == NULL)
	{
	  _IO_doallocbuf (f);
	  _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
	}
#define _IO_setg(fp, eb, g, eg)  ((fp)->_IO_read_base = (eb),\
  (fp)->_IO_read_ptr = (g), (fp)->_IO_read_end = (eg))

接着将f->_IO_write_basef->_IO_write_ptr设置成f->_IO_read_ptr指针;将f->_IO_write_end赋值为f->_IO_buf_end

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

指针执行完看一下_IO_list_all_IO_buf_base_IO_buf_end被赋值,且输入缓冲区相关指针被赋值为_IO_buf_base

pwndbg> p *_IO_list_all
$3 = {
  file = {
    _flags = -72536956,
    _IO_read_ptr = 0x406250 "",
    _IO_read_end = 0x406250 "",
    _IO_read_base = 0x406250 "",
    _IO_write_base = 0x406250 "",
    _IO_write_ptr = 0x406250 "",
    _IO_write_end = 0x407250 "",
    _IO_buf_base = 0x406250 "",
    _IO_buf_end = 0x407250 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x406100,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x406110,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

接下来调用_IO_new_do_write函数来执行writewrite_ptrwrite_end之间的输入读入缓冲区

if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base,
			 f->_IO_write_ptr - f->_IO_write_base);
int
_IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  return (to_do == 0
	  || (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)

函数中又调用了_IO_new_do_write函数

static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  _IO_size_t count;
  ...
  count = _IO_SYSWRITE (fp, data, to_do);
  if (fp->_cur_column && count)
    fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0
		       && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
		       ? fp->_IO_buf_base : fp->_IO_buf_end);
  return count;
}

此时来到了最终实现write的地方,即_IO_SYSWRITE函数(vtable中的``__write函数),实际调用的是_IO_new_file_write函数,按照指定长度调用write`函数写入内容

In file: /home/starrysky/IO/glibc-2.23/libio/fileops.c
   1253 
   1254 _IO_ssize_t
   1255 _IO_new_file_write (_IO_FILE *f, const void *data, _IO_ssize_t n)
   1256 {
   1257   _IO_ssize_t to_do = n;
 ► 1258   while (to_do > 0)
   1259     {
   1260       _IO_ssize_t count = (__builtin_expect (f->_flags2
   1261                                              & _IO_FLAGS2_NOTCANCEL, 0)
   1262                            ? write_not_cancel (f->_fileno, data, to_do)
   1263                            : write (f->_fileno, data, to_do));
_IO_ssize_t
_IO_new_file_write (_IO_FILE *f, const void *data, _IO_ssize_t n)
{
  _IO_ssize_t to_do = n;
  while (to_do > 0)
    {
      _IO_ssize_t count = (__builtin_expect (f->_flags2
					     & _IO_FLAGS2_NOTCANCEL, 0)
			   ? write_not_cancel (f->_fileno, data, to_do)
			   : write (f->_fileno, data, to_do));
      if (count < 0)
	{
	  f->_flags |= _IO_ERR_SEEN;
	  break;
	}
      to_do -= count;
      data = (void *) ((char *) data + count);
    }
  n -= to_do;
  if (f->_offset >= 0)
    f->_offset += n;
  return n;
}
  0x7ffff7a86ac8 <_IO_file_write+40>     test   byte ptr [r14 + 0x74], 2
  0x7ffff7a86acd <_IO_file_write+45>     je     _IO_file_write+128                <_IO_file_write+128>
   ↓
  0x7ffff7a86b20 <_IO_file_write+128>    mov    edi, dword ptr [r14 + 0x70]
  0x7ffff7a86b24 <_IO_file_write+132>    mov    rdx, rbx
  0x7ffff7a86b27 <_IO_file_write+135>    mov    rsi, rbp
► 0x7ffff7a86b2a <_IO_file_write+138>    call   write                <write>
       fd: 0x3 (/home/starrysky/IO/test)
       buf: 0x406250 ◂— 0x0
       n: 0x30

  0x7ffff7a86b2f <_IO_file_write+143>    test   rax, rax
  0x7ffff7a86b32 <_IO_file_write+146>    js     _IO_file_write+82                <_IO_file_write+82>

  0x7ffff7a86b34 <_IO_file_write+148>    sub    rbx, rax
  0x7ffff7a86b37 <_IO_file_write+151>    add    rbp, rax
  0x7ffff7a86b3a <_IO_file_write+154>    test   rbx, rbx

调用结束后回到_new_do_write函数,设置各种指针刷新输出缓冲区

if (fp->_cur_column && count)
   fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
 _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
 fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
 fp->_IO_write_end = (fp->_mode <= 0
	       && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
	       ? fp->_IO_buf_base : fp->_IO_buf_end);

查看一下_IO_list_all结构体,此时输出缓冲区中没有内容

pwndbg> p *_IO_list_all
$5 = {
  file = {
    _flags = -72536956,
    _IO_read_ptr = 0x406250 "",
    _IO_read_end = 0x406250 "",
    _IO_read_base = 0x406250 "",
    _IO_write_base = 0x406250 "",
    _IO_write_ptr = 0x406250 "",
    _IO_write_end = 0x407250 "",
    _IO_buf_base = 0x406250 "",
    _IO_buf_end = 0x407250 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x406100,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x406110,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

接下来回到_IO_file_xsputn函数,判断剩余输出数据是否大于输入缓冲区buf大小,若大于则不使用输出缓冲区直接调用new_do_write输出并刷新指针

     block_size = f->_IO_buf_end - f->_IO_buf_base;
     do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

     if (do_write)
{
  count = new_do_write (f, s, do_write);
  to_do -= count;
  if (count < do_write)
    return n - to_do;
}

输出完大块的内容剩余的小块的内容继续调用_IO_default_xsputn函数输出到输出缓冲区,在长度不同的时候使用了不同的输出方式,即大于20使用memcpy,小于20使用for循环

to_do -= _IO_default_xsputn (f, s+do_write, to_do);
_IO_size_t
_IO_default_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  const char *s = (char *) data;
  _IO_size_t more = n;
  if (more <= 0)
    return 0;
  for (;;)
    {
      /* Space available. */
      if (f->_IO_write_ptr < f->_IO_write_end)
	{
	  _IO_size_t count = f->_IO_write_end - f->_IO_write_ptr;
	  if (count > more)
	    count = more;
	  if (count > 20)
	    {
#ifdef _LIBC
	      f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
#else
	      memcpy (f->_IO_write_ptr, s, count);
	      f->_IO_write_ptr += count;
#endif
	      s += count;
	    }
	  else if (count)
	    {
	      char *p = f->_IO_write_ptr;
	      _IO_ssize_t i;
	      for (i = count; --i >= 0; )
		*p++ = *s++;
	      f->_IO_write_ptr = p;
	    }
	  more -= count;
	}
      if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
	break;
      more--;
    }
  return n - more;
}
libc_hidden_def (_IO_default_xsputn)

最后结构体如下,输出缓冲区的大小刚好为0x30

pwndbg> p *_IO_list_all
$6 = {
  file = {
    _flags = -72536956,
    _IO_read_ptr = 0x406250 "",
    _IO_read_end = 0x406250 "",
    _IO_read_base = 0x406250 "",
    _IO_write_base = 0x406250 "",
    _IO_write_ptr = 0x406280 "",
    _IO_write_end = 0x407250 "",
    _IO_buf_base = 0x406250 "",
    _IO_buf_end = 0x407250 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x406100,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x406110,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>

其他输出函数最终实现部分调用的也是_IO_new_file_overflow函数,所以利用输出函数就是利用_IO_new_file_overflow函数,需要注意函数对标志位的判断

if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
  {
    f->_flags |= _IO_ERR_SEEN;
    __set_errno (EBADF);
    return EOF;
  }

调用vtable中的函数:

  • _IO_fwrite函数调用了vtable的_IO_new_file_xsputn
  • _IO_new_file_xsputn函数调用了vtable中的_IO_new_file_overflow实现缓冲区的建立以及刷新缓冲区。
  • vtable中的_IO_new_file_overflow函数调用了vtable的_IO_file_doallocate以初始化输入缓冲区。
  • vtable中的_IO_file_doallocate调用了vtable中的__GI__IO_file_stat以获取文件信息。
  • new_do_write中的_IO_SYSWRITE调用了vtable_IO_new_file_write最终去执行系统调用write。

总结

  • fopen

    • _IO_new_fopen

      • __fopen_internal

        • _IO_no_init:初始化fp结构体

        • _IO_file_init -> _IO_new_file_init:将fp结构体连接到_IO_list_all结构体中

          • _IO_link_in:将文件指针链接到全局文件链表中
        • _IO_file_fopen -> _IO_new_file_fopen:判断文件是否已打开以及一些标志位

          • _IO_file_open:调用open之后设置fileno标志位
            • open
            • _IO_link_in:使结构体进入_IO_list_all
  • fread

    • _IO_fread

      • _IO_sgetn

        • _IO_XSGETN -> _IO_file_xsgetn :从文件流中读取指定数量的字节存储到目标缓冲区

          • _IO_doallocbuf :分配新的缓冲区并设置_IO_buf_base_IO_buf_end
            • _IO_DOALLOCATE -> _IO_file_doallocate:分配缓冲区
              • _IO_SYSSTAT :获取文件状态,即st结构体
            • _IO_setb:设置_IO_list_all中的_IO_buf_base_IO_buf_end
          • _IO_read_ptr_IO_read_end之间输入缓冲区原有的数据输入目标缓冲区
          • __underflow
            • **_IO_UNDERFLOW -> _IO_new_file_underflow**:首先检查是否有读权限
              • _IO_doallocbuf:目标缓冲区未初始化时调用来初始化各指针为_IO_buf_base
              • _IO_SYSREAD -> _IO_file_read
                • read:向输入缓冲区读取大小为fp->_IO_buf_end - fp->_IO_buf_base的内容
              • 设置输入缓冲区结束地址
          • 再次循环将输入缓冲区复制到目标缓冲区
  • fwrite

    • _IO_fwrite

      • _IO_sputn -> _IO_new_file_xsputn

        • 计算输出缓冲区剩余空间,将需要输出数据复制到输出缓冲区

        • **_IO_OVERFLOW -> _IO_new_file_overflow**:缓冲区未建立或已满时建立或刷新输出缓冲区,会判断标志位,即是否有可写权限

        • _IO_doallocbuf:若输出缓冲区未创建则调用该函数创建并给_IO_buf_base_IO_buf_end指针赋值

        • _IO_setg:将输入相关的缓冲区指针赋值为_IO_buf_base

          • ```C
            #define _IO_setg(fp, eb, g, eg) ((fp)->_IO_read_base = (eb),
            (fp)->_IO_read_ptr = (g), (fp)->_IO_read_end = (eg))
            
                    - 将`f->_IO_write_base`、`f->_IO_write_ptr`设置成`f->_IO_read_ptr`指针;将`f->_IO_write_end`赋值为`f->_IO_buf_end`
            
                  - `_IO_do_write	->	_IO_new_do_write`
            
                    - `new_do_write`
                      - `_IO_SYSWRITE	->	_IO_new_file_write`
                        - `write`
                      - 设置各种指针刷新输出缓冲区
            
                  - 根据剩余输出数据大小选择`memcpy`或`for`循环输出并刷新指针
            
            ## 劫持vtable原理
            
            `_IO_FILE_plus`结构体
            
            ```C
            struct _IO_FILE_plus
            {
              _IO_FILE file;
              const struct _IO_jump_t *vtable;
            };

其中vtable结构体中存着系统调用最终调用的函数,因此劫持vtable函数之后就可以劫持程序流,需要注意的是在2.23版本以上就会检查vtable

reference

https://www.z1r0.top/2021/10/09/IO-FILE


  目录