IO FILE
IO_FILE
结构体:通过chain
域连接stderr
、stdout
、stdin
的链表,链表的表头是__IO_list_all
,stderr
、stdout
、stdin
是程序启动时打开的文件流,_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
其他输入函数如scanf
、gets
等最终实现读取的函数也是_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_base
、f->_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
函数来执行write
将write_ptr
到write_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; };
- ```C
其中vtable
结构体中存着系统调用最终调用的函数,因此劫持vtable
函数之后就可以劫持程序流,需要注意的是在2.23
版本以上就会检查vtable