美文网首页CTF-PWN
Pwn 知识点总结(1)- 利用 _IO_2_1_stdout_

Pwn 知识点总结(1)- 利用 _IO_2_1_stdout_

作者: Nevv | 来源:发表于2020-04-11 11:53 被阅读0次

    利用 _IO_2_1_stdout_泄露libc

    1. FILE 结构体

    pwndbg> p stdout 
    $1 = (struct _IO_FILE *) 0x7ffff7dd0760 <_IO_2_1_stdout_>
    pwndbg> ptype stdout
    type = struct _IO_FILE {
        int _flags;
        char *_IO_read_ptr;
        char *_IO_read_end;
        char *_IO_read_base;
        char *_IO_write_base;  //  本质上是通过修改这个结构题泄露
        char *_IO_write_ptr;   //  这两个指针地址之间的内容
        char *_IO_write_end;
        char *_IO_buf_base;
        char *_IO_buf_end;
        char *_IO_save_base;
        char *_IO_backup_base;
        char *_IO_save_end;
        struct _IO_marker *_markers;
        struct _IO_FILE *_chain;
        int _fileno;
        int _flags2;
        __off_t _old_offset;
        unsigned short _cur_column;
        signed char _vtable_offset;
        char _shortbuf[1];
        _IO_lock_t *_lock;
        __off64_t _offset;
        struct _IO_codecvt *_codecvt;
        struct _IO_wide_data *_wide_data;
        struct _IO_FILE *_freeres_list;
        void *_freeres_buf;
        size_t __pad5;
        int _mode;
        char _unused2[20];
    } *
    
    结构体中的flags
    #define _IO_MAGIC 0xFBAD0000 /* Magic number */
    #define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
    #define _IO_MAGIC_MASK 0xFFFF0000
    #define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
    #define _IO_UNBUFFERED 2
    #define _IO_NO_READS 4 /* Reading not allowed */
    #define _IO_NO_WRITES 8 /* Writing not allowd */
    #define _IO_EOF_SEEN 0x10
    #define _IO_ERR_SEEN 0x20
    #define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
    #define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
    #define _IO_IN_BACKUP 0x100
    #define _IO_LINE_BUF 0x200
    #define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
    #define _IO_CURRENTLY_PUTTING 0x800
    #define _IO_IS_APPENDING 0x1000
    #define _IO_IS_FILEBUF 0x2000
    #define _IO_BAD_SEEN 0x4000
    #define _IO_USER_LOCK 0x8000
    

    _IO_2_1_stdout_一般是这样的,也就是 0xfbad2887:

    _IO_MAGIC|_IO_IS_FILEBUF|_IO_CURRENTLY_PUTTING|_IO_LINKED|_IO_NO_READS | _IO_UNBUFFERED |_IO_USER_BUF
    
    如何leak

    为了泄露处一些libc上的地址,我们需要修改 _IO_2_1_stdout_的_flags、_IO_write_base、_IO_write_ptr。步骤如下:

    • 一般我们将_flags设置为0xfbad18**。目的是为了设置_IO_CURRENTLY_PUTTING=0x800,_IO_IS_APPENDING=0x1000,IO_MAGIC=0xFBAD0000 (后续看puts实现就会知道为什么要这样设置)
    • 设置_IO_write_base指向想要泄露的地方;_IO_write_ptr指向泄露结束的地址。
    • 之后遇到puts或printf,就会将_IO_write_base指向的内容打印出来。
    常见payload
    • p64(0xfbad1800)+p64(0x0)*3+'\x00'

    至于为什么将以下三个指针设置为0,原因在put函数的执行流程中可以一探究竟。

        char *_IO_read_ptr;
        char *_IO_read_end;
        char *_IO_read_base;
    

    2. put函数执行流程

    IO_sputn
    • 实际最终调用的是 _IO_2_1_stdout_vtable中的__xsputn,也就是_IO_new_file_xsputn函数。

    最终会调用到_IO_OVERFLOW,也就是_IO_new_file_overflow,可以看到这里要求 #define _IO_NO_WRITES 8 /* Writing not allowd */标志位不为1,否则就会返回错误。

    int
    _IO_new_file_overflow (_IO_FILE *f, int ch)
    {
      if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
        {
          f->_flags |= _IO_ERR_SEEN;
          __set_errno (EBADF);
          return EOF;
        }
      /* If currently reading or no buffer allocated. */
      // 将 #define _IO_CURRENTLY_PUTTING 0x800置为1  绕过这个if判断,通常没有输出过的话是0,程序输出过就为1
      if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
        {
          /* Allocate a buffer if needed. */
          if (f->_IO_write_base == NULL)
        {
          _IO_doallocbuf (f);
          _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
        }
          /* Otherwise must be currently reading.
         If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
         logically slide the buffer forwards one block (by setting the
         read pointers to all point at the beginning of the block).  This
         makes room for subsequent output.
         Otherwise, set the read pointers to _IO_read_end (leaving that
         alone, so it can continue to correspond to the external position). */
          if (__glibc_unlikely (_IO_in_backup (f)))
        {
          size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
          _IO_free_backup_area (f);
          f->_IO_read_base -= MIN (nbackup,
                       f->_IO_read_base - f->_IO_buf_base);
          f->_IO_read_ptr = f->_IO_read_base;
        }
          if (f->_IO_read_ptr == f->_IO_buf_end)
        f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
          f->_IO_write_ptr = f->_IO_read_ptr;
          f->_IO_write_base = f->_IO_write_ptr;
          f->_IO_write_end = f->_IO_buf_end;
          f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;
          f->_flags |= _IO_CURRENTLY_PUTTING;
          if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
        f->_IO_write_end = f->_IO_write_ptr;
        }
      if (ch == EOF)        
        return _IO_do_write (f, f->_IO_write_base,
                 f->_IO_write_ptr - f->_IO_write_base);
      if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
        if (_IO_do_flush (f) == EOF)
          return EOF;
      *f->_IO_write_ptr++ = ch;
      if ((f->_flags & _IO_UNBUFFERED)
          || ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
        if (_IO_do_write (f, f->_IO_write_base,
                  f->_IO_write_ptr - f->_IO_write_base) == EOF)
          return EOF;
      return (unsigned char) ch;
    }
    libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
    
    IO_do_write
    • 会跳进new_do_write函数
    static
    _IO_size_t
    new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
    {
      _IO_size_t count;
      if (fp->_flags & _IO_IS_APPENDING)   // 所以这里要设置 _IO_IS_APPENDING 为1,否则不会有输出
        /* On a system without a proper O_APPEND implementation,
           you would need to sys_seek(0, SEEK_END) here, but is
           not needed nor desirable for Unix- or Posix-like systems.
           Instead, just indicate that offset (before and after) is
           unpredictable. */
        fp->_offset = _IO_pos_BAD;
      else if (fp->_IO_read_end != fp->_IO_write_base)  //  或者设置stdout->_IO_read_end == stdout->_IO_write_base
        {
          _IO_off64_t new_pos
        = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
          if (new_pos == _IO_pos_BAD)
        return 0;
          fp->_offset = new_pos;
        }
      count = _IO_SYSWRITE (fp, data, to_do);
      if (fp->_cur_column && count)
        fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
      _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
      fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
      fp->_IO_write_end = (fp->_mode <= 0
                   && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
                   ? fp->_IO_buf_base : fp->_IO_buf_end);
      return count;
    }
    

    3. 利用

    ​ 一般都是利用unsorted bin的在tcachefastbin的fd上main_arena的地址(同一个chunk既在unsortbin上,也在tcache上),配合UAF之类的漏洞覆盖低位爆破stdout的地址,然后分配到stdout结构体处,达到泄露的目的。

    ​ 对于没有tcache的 glibc 版本,使用 fastbin attack就好,因为_IO_2_1_stdout_上面就是_IO_2_1_stderr_stderr->__pad2一般是指向_IO_wide_data_2的指针,而_IO_wide_data_2是在libc中的,所以我们可以用其来伪造size。

    相关文章

      网友评论

        本文标题:Pwn 知识点总结(1)- 利用 _IO_2_1_stdout_

        本文链接:https://www.haomeiwen.com/subject/uxqymhtx.html