0%

House Of Force-原理

House Of Force

house of force 是修改 top chunk size 的一种利用方法

利用 top chunk 分割中的漏洞来申请任意 chunk,再通过修改模块进行 GOT劫持,hook劫持


House Of Force 利用姿势

该利用姿势是由于libc的堆管理在 malloc 的时候默认 top chunk 的 size 是正确合法的,所以不会去检查 top chunk 的 size 值,这就导致了一种情况,当一个程序存在可以修改 top chunk size 的漏洞时,我们把 top chunk 的 size 修改成 0xffffffff(x86)

假设这个时候的 top_chunk=0x601200,然后 malloc(0xffe00020),然后对 malloc 申请的 size 进行检查,0xffe00030 < top_chunk_size ,所以可以成功malloc内存,然后计算top_chunk的新地址:0xffe00030+0x601200=0x100401230, 因为是x86环境,最高位溢出了,所以top_chunk=0x401230

然后下次我们再malloc的时候,返回的地址就是0x401238

Top chunk的分割机制与利用点

top chunk的作用是作为后备堆空间,在各bin中没有chunk可提供时,分割出一个chunk提供给用户

简化版本的分割操作:(为了方便看懂,进行了修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
victim = av->top; /* 获取addr of top chunk */
size = chunksize(victim); /* 获取top chunk size */
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb; /* 计算剩下的size */
remainder = chunk_at_offset(victim, nb);
av->top = remainder; /* 修改top chunk */
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0)); /* 设置top chunk的头 */
set_head(remainder, remainder_size | PREV_INUSE); /* 设置剩下chunk的头 */

check_malloced_chunk(av, victim, nb);
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}

首先是libc会检查用户申请的大小,top chunk是否能给的起,如果给得起,就由 top chunk 的 head 处,以用户申请大小所匹配的 chunk 大小为偏移量,将 top chunk 的位置“推”到新的位置,而原来的 top chunk head 处就作为新的堆块被分配给用户了

如果我们能控制用户申请的大小为任意值,我们就能将 top chunk 劫持到任意内存地址,然后就可以控制目标内存

1
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) 

保护检查:只有 top chunk 的 size 大于等于申请的 size,才会有后续操作,而 pwn 中劫持内存常常劫持的是malloc_hook、got表等指针,与堆空间中的 top chunk 相距甚远,远到所需要申请的size必定超出top chunk 现有的大小

破解办法:大小检查时用的数据类型是 unsigned long,如果能通过某些漏洞(比如溢出)将 top chunk 的 size 字段篡改成 -1,那么在做这个检查时,size 就变成了无符号整数中最大的值,这样一来,不管用户申请多大的堆空间都可以满足条件

​ // 此外,虽然此处的检查中,用户申请的大小也被当做无符号整型对待,但是在后面推 top chunk 的时候是作为 int 对待的,因此如果劫持目标内存地址比 top chunk 低,我们申请负数大小的内存是可以劫持过去的

利用条件:

  • 用户能够篡改 top chunk 的 size 字段(篡改为负数或很大值)
  • 用户可以申请任意大小的堆内存(包括负数)

版本对 House Of Force 的影响

libc-2.23

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
  /* Try to use top chunk */
/* Require that there be a remainder, ensuring top always exists */
if ( (remainder_size = chunksize(top(ar_ptr)) - nb) < (long)MINSIZE)
{

#if HAVE_MMAP
/* If the request is big and there are not yet too many regions,
and we would otherwise need to extend, try to use mmap instead. */
if ((unsigned long)nb >= (unsigned long)mmap_threshold &&
n_mmaps < n_mmaps_max &&
(victim = mmap_chunk(nb)) != 0)
return victim;
/* 如果申请字节超过“topchunk->size”,调用mmap_chunk */
#endif

/* Try to extend */
malloc_extend_top(ar_ptr, nb);
if ((remainder_size = chunksize(top(ar_ptr)) - nb) < (long)MINSIZE)
{
#if HAVE_MMAP
/* A last attempt: when we are out of address space in a
non-main arena, try mmap anyway, as long as it is allowed at
all. */
if (ar_ptr != &main_arena &&
n_mmaps_max > 0 &&
(victim = mmap_chunk(nb)) != 0)
return victim;
/* 如果,第一次调用mmap_chunk没有成功,则再调用一次 */
#endif
return 0; /* propagate failure */
}
}
victim = top(ar_ptr);
set_head(victim, nb | PREV_INUSE); /* 设置top chunk的头 */
top(ar_ptr) = chunk_at_offset(victim, nb);
set_head(top(ar_ptr), remainder_size | PREV_INUSE); /* 设置剩下chunk的头 */
check_malloced_chunk(ar_ptr, victim, nb); /* 这个检查几乎没有影响 */
return victim;

和给出的例子几乎一样,通过“topchunk->size”判断是否调用“mmap_chunk”

完全可以打 House Of Force

libc-2.27

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
if (av != &main_arena)
{
heap_info *old_heap, *heap;
size_t old_heap_size;

/* First try to extend the current heap. */
old_heap = heap_for_ptr (old_top);
old_heap_size = old_heap->size;
if ((long) (MINSIZE + nb - old_size) > 0
/* top chunk不够用,grow_heap扩展top chunk的空间 */
/* 要打House Of Force,这个if一定不成立(old_size非常大) */
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
{
av->system_mem += old_heap->size - old_heap_size;
set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
| PREV_INUSE);
}
else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
{
/* Use a newly allocated heap. */
heap->ar_ptr = av;
heap->prev = old_heap;
av->system_mem += heap->size;
/* Set up the new top. */
top (av) = chunk_at_offset (heap, sizeof (*heap));
set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);

/* Setup fencepost and free the old top chunk with a multiple of
MALLOC_ALIGNMENT in size. */
/* The fencepost takes at least MINSIZE bytes, because it might
become the top chunk again later. Note that a footer is set
up, too, although the chunk is marked in use. */
old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
if (old_size >= MINSIZE) /* 需要分割 */
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
_int_free (av, old_top, 1);
}
else /* 不需要分割 */
{
set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
set_foot (old_top, (old_size + 2 * SIZE_SZ));
}
}
else if (!tried_mmap)
/* We can at least try to use to mmap memory. */
goto try_mmap;
}
................

这里只展示了“av != &main_arena”,不是 House Of Force 的重点,而“av == &main_arena”太长不方便展示,给出部分代码方便查源码时定位

程序复杂了不少,也多了许多检查:

1
2
/* top chunk is OK */
check_chunk (av, av->top);
1
# define check_chunk(A, P)              do_check_chunk (A, P)
1
2
3
/* Memory allocated from the system in this arena.  */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
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
static void
do_check_chunk (mstate av, mchunkptr p)
{
unsigned long sz = chunksize (p);
/* min and max possible addresses assuming contiguous allocation */
char *max_address = (char *) (av->top) + chunksize (av->top);
char *min_address = max_address - av->system_mem;
/* 这里就是问题的关键 */
/* 因为“topchunk->size”被设置得非常大,所以max_address和min_address也非常大 */
/* 这个设置范围的操作打死了House Of Force */

if (!chunk_is_mmapped (p))
{
/* Has legal address ... */
if (p != av->top)
{
if (contiguous (av))
{
assert (((char *) p) >= min_address);
/* 因为min_address非常大,重新申请的chunk地址不可能大于它 */
assert (((char *) p + sz) <= ((char *) (av->top)));
}
}
else
{
/* top size is always at least MINSIZE */
assert ((unsigned long) (sz) >= MINSIZE);
/* top predecessor always marked inuse */
assert (prev_inuse (p));
}
}
else if (!DUMPED_MAIN_ARENA_CHUNK (p))
{
/* address is outside main heap */
if (contiguous (av) && av->top != initial_top (av))
{
assert (((char *) p) < min_address || ((char *) p) >= max_address);
}
/* chunk is page-aligned */
assert (((prev_size (p) + sz) & (GLRO (dl_pagesize) - 1)) == 0);
/* mem is aligned */
assert (aligned_OK (chunk2mem (p)));
}
}

在检查中给chunk添加了一个“范围”(min_address & max_address),想要打 House Of Force ,就需要把“topchunk->size”设置得很大,这就导致了这个“范围”十分极端,不可能成立

所以不能打 House Of Force

总而言之:House Of Force 只能在 libc-2.23 中生效,因为 libc-2.23 会在检查了“topchunk->size”后就进行分割,可以直接利用,而 libc-2.27 设置了一个“范围”,限制了申请chunk的地址范围,这样就导致 top chunk 无法分割到目标地址了