IO_FILE-FSOP

August 2, 2019 PWN 访问: 43 次

由于刚开始接触IO_FILE攻击,利用的是glibc-2.23版本的,没有vtable check机制

数据类型介绍

经过对fopen内部源码的调试和分析,知道了在_IO_FILE_plus结构体中有一个vtable的字段,
_IO_FILE_plus的定义:

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

如下所示:

$21 = {
  file = {
    _flags = -72540024,
    _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 = -1,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7ffff7dd3790 <_IO_stdfile_0_lock>,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7dd19c0 <_IO_wide_data_0>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

vtable指向的也是一个结构体(_IO_jump_t):

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

如下所示:

pwndbg> p *(struct _IO_jump_t *)0x7ffff7dd06e0
$22 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7ffff7a869c0 <_IO_new_file_finish>,
  __overflow = 0x7ffff7a87730 <_IO_new_file_overflow>,
  __underflow = 0x7ffff7a874a0 <_IO_new_file_underflow>,
  __uflow = 0x7ffff7a88600 <__GI__IO_default_uflow>,
  __pbackfail = 0x7ffff7a89980 <__GI__IO_default_pbackfail>,
  __xsputn = 0x7ffff7a861e0 <_IO_new_file_xsputn>,
  __xsgetn = 0x7ffff7a85ec0 <__GI__IO_file_xsgetn>,
  __seekoff = 0x7ffff7a854c0 <_IO_new_file_seekoff>,
  __seekpos = 0x7ffff7a88a00 <_IO_default_seekpos>,
  __setbuf = 0x7ffff7a85430 <_IO_new_file_setbuf>,
  __sync = 0x7ffff7a85370 <_IO_new_file_sync>,
  __doallocate = 0x7ffff7a7a180 <__GI__IO_file_doallocate>,
  __read = 0x7ffff7a861a0 <__GI__IO_file_read>,
  __write = 0x7ffff7a85b70 <_IO_new_file_write>,
  __seek = 0x7ffff7a85970 <__GI__IO_file_seek>,
  __close = 0x7ffff7a85340 <__GI__IO_file_close>,
  __stat = 0x7ffff7a85b60 <__GI__IO_file_stat>,
  __showmanyc = 0x7ffff7a89af0 <_IO_default_showmanyc>,
  __imbue = 0x7ffff7a89b00 <_IO_default_imbue>
}
pwndbg>

这个结构体中存着和IO相关的函数,是一个函数表,而这个vtable就是一个函数表指针。
这个函数表中有19个函数指针,分别完成IO的相关操作
比如说,fwrite最终会调用__write = 0x7ffff7a85b70 <_IO_new_file_write>,

劫持原理

如果可以控制_IO_FILE_plus结构体中的vtable指针,让这个指针指向我们伪造的_IO_jump_t结构体,再通过调用相应的IO函数,就可以触发对我们伪造的_IO_jump_t结构体中的一个函数的调用,就可以劫持程序流。

两种方法

  • 修改内存中已有的_IO_FILE_plus结构体中的vtable指针
  • 修改并伪造整个_IO_FILE_plus结构体

例子

写个小demo:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void pwn()
{
    printf("%s","hack by pwn!");
}
struct fake_vtable{
    char *a0;
    char *a1;
    char *a2;
    char *a3;
    char *a4;
    char *a5;
    char *a6;
    char *a7;
    char *a8;
    char *a9;
    char *a10;
    char *a11;
    char *a12;
    char *a13;
    char *a14;
    char *a15;
    char *a16;
    char *a17;
    char *a18;
    char *a19;
    char *a20;
    char *a21;
    char *a22;
    char *a23;
};
int main()
{
    struct fake_vtable a;
    // a.a7 = pwn;
    a.a8 = pwn;
    long long *vtable_addr;
    FILE *file_point = fopen("test.txt","r");
    char *content;
    vtable_addr = (long long *)((long long)file_point+0xd8);
    *vtable_addr=&a;
    fwrite(content,1,8,file_point)
    // fread(content,1,8,file_point);
    return 0;
}

上面程序的输出:

一筐萝卜➜ test  ./demo1
hack by pwn!#                                                                                                                                 一筐萝卜➜ test

经过测试后发现,调用fwrite时会调用vtable表中的第9个函数,调用fread时会调用vtable表中的第8个函数

IO_FILE 相关的操作对应的vtable表中的函数

首先要明白一点,fopen函数实在分配空间,建立_IO_FILE_plus结构体,并且将结构体插入到_IO_list_all

function name call function in vtable (下标从0开始,相对于结构体的开头)
fread __GI__IO_file_xsgetn(8)、_IO_file_doallocate(13)、__GI__IO_file_stat(18)、_IO_new_file_underflow(4)、__GI__IO_file_read(14)
fwrite _IO_new_file_xsputn (7)、_IO_new_file_overflow(3)、_IO_file_doallocate(13)、__GI__IO_file_stat(18)、_IO_new_file_write(15)
fclose __GI__IO_file_close(17)、_IO_new_file_finish(2)

攻击原理

在一个程序进程中,所有打开的文件结构都是由一个单链表(_IO_list_all)来管理的,而每个结构体中指向下一个节点是_chain字段,

pwndbg> print _IO_list_all
$6 = (struct _IO_FILE_plus *) 0x602010  #自己分配的FILE
pwndbg> print _IO_list_all->file._chain
$7 = (struct _IO_FILE *) 0x7ffff7dd2540 <_IO_2_1_stderr_>
pwndbg> print (struct _IO_FILE *)(_IO_list_all->file._chain)._chain
$8 = (struct _IO_FILE *) 0x7ffff7dd2620 <_IO_2_1_stdout_>
pwndbg> print (struct _IO_FILE *)((_IO_list_all->file._chain)._chain)._chain
$9 = (struct _IO_FILE *) 0x7ffff7dd18e0 <_IO_2_1_stdin_>


待续……

添加新评论