二进制安全之堆溢出(系列)——堆基础 & 结构(四)
二进制安全之堆溢出(系列)——“堆基础&结构”第四节上回咱们聊到碎块合并问题本次咱们从ulink讲起

unlink
unlink用来将一个双向链表bin链中的一个元素取出来,即断链过程
引发unlink的几种方式
malloc
从恰好大小合适的largebin中获取chunk会unlink
fastbin和smallbin没有使用unlink
依次遍历unsorted bin时也没有使用unlink
合并和切割的时候都会unlink
free
后向合并,合并物理相邻低地址空闲chunk的时候会unlink
前向后并,合并物理相邻高地址空闲chunk的时候会unlink(除了top chunk)
relloc
consolidate
unlink源码分析
/* Take a chunk off a bin list 用于将某一个空闲 chunk从其所处的bin中脱链*/
#define unlink(AV, P, BK, FD) { //P 即为待脱链的空闲 chunk \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
/*检查一下其大小是否一致(size检查)
故在做overlap的时候,伪造的堆块的size需要和presize一致
*/
malloc_printerr ("corrupted size vs. prev_size"); \
FD = P->fd; //FD为 P的前一个空闲chunk
BK = P->bk; //BK为p的后一个空闲chunk
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
/*承上检查,检查当前的p是否与两边相连
*/
malloc_printerr ("corrupted double-linked list");
else { //设置相邻 chunk 的 fd 或 bk 指针
FD->bk = BK;
BK->fd = FD;
/*
可以在这里修改p的fd和bk
p->fd = target //修改fd
p->bk = target //修改bk
因此
FD = target
BK = target
FD->bk = *(target + 0x18) //prev_size + size +fd = 0x18
BK->fd = *(target + 0x10)
上面的改法无法绕过FD->bk != P || BK->fd != P的检查
//TODO follow
FD = target - 0x18 //自己控制堆块修改值即可
BK = target - 0x10
FD->bk = *(target - 0x18 + 0x18) = BK = target - 0x10 = p
BK->fd = *(target - 0x10 + 0x10) = FD = target - 0x18 = p
这样就可以绕过当前的校验
payload = 0x18 *'a' + target_addr
构造payload,替换target为got或任意地址
*/
/*对于smallbin而言,以上脱链完成
下面处理largebin的脱链,多了fd/bk_nextsize的unlink过程
*/
if (!in_smallbin_range (chunksize_nomask (P))
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
//说明P是相应bins的第一个chunk,否则fd_nextsize和bk_nextsize没有意义
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr ("corrupted double-linked list (not small)");
if (FD->fd_nextsize == NULL) { //p断链之后,FD成为当前bins的第一个chunk
if (P->fd_nextsize == P) //只有唯一的chunk时
FD->fd_nextsize = FD->bk_nextsize = FD;
else { //正常断链加链
FD->fd_nextsize = P->fd_nextsize;
FD->bk_nextsize = P->bk_nextsize;
P->fd_nextsize->bk_nextsize = FD;
P->bk_nextsize->fd_nextsize = FD;
}
} else { //p断链之后,FD成为下一组bins的第一个chunk
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;
}
}
}
}
泄露libc的地址
P位于双向链表头部,bk泄露P位于双向链表尾部,fd泄露双向链表中只包含一个空闲chunk时,P位于双向链表中,fd和bk均可泄露tips:这里的头部指的是bin的fd指向的chunk,即双向链表中最新加入的chunk,反之亦然
实例分析

断链的目标:p ,p之后需要有一个已经free的堆块覆盖p->fd = target - 0x18 p->bk = target - 0x10,注意此时的fd和bk都是地址的值
target 为堆中的任意堆块的地址的值,前提是可以通过heap_base + offset 获得地址现在假设target为note2,note2的地址为heap_base+0x60,则target需要填heap_base+0x60
当free p的时候,就会和之后的空闲堆块发生合并,从而引发unlink此时按顺序断链 *(p->fd->bk)= bk,*(p->bk->fd) = fd,即最终实现*(target)=target-0x18
p->fd->bk的内容为bk的值,故`*(p->fd->bk)= bk`p->bk->fd的内容为bk的值,故`*(p->bk->fd)= fd`最终结果为第二条语句的执行结果这时前面的target为指针,上述结果相当于target指向了target-0x18的地址空间
此时我们向target里面写入0x18 * 'a',就相当于指针target+0x18指向了target起始的地址紧接着写入malloc_hook的地址,然后再edit target,写入system的地址,就相当于target+0x18+0x8指向了target+0x8的地址空间此时就相当于*(malloc_hook) = system ,再次malloc的时候就会调用system了。
调试
源代码一
#include<stdio.h>
#include<malloc.h>
#include<unistd.h>
#include<string.h>
int main(){
int size = 0x70;
void *p = malloc(size);
void *q = malloc(size);
void *junk = malloc(size);
sleep(0);
free(q);
sleep(0);
printf("%p\n",q);
int *r = malloc(0x60);
printf("%p\n",r);
sleep(0);
return 0;
堆块分配后的heap与内存布局
0x602000 FASTBIN { ===>chunk1
prev_size = 0,
size = 129,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602080 FASTBIN { ===>chunk2
prev_size = 0,
size = 129,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602100 FASTBIN { ===>chunk3
prev_size = 0,
size = 129,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602180 PREV_INUSE {
prev_size = 0,
size = 134785, ===>top_chunk
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
}
---------------------------------------------------------------------------------------------------------------------------
0x602000: 0x0000000000000000 0x0000000000000081 <=== chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000081 <=== chunk2
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
0x602100: 0x0000000000000000 0x0000000000000081 <=== chunk3
0x602110: 0x0000000000000000 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000000000
0x602150: 0x0000000000000000 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000020e81 <=== top chunk
0x602190: 0x0000000000000000 0x0000000000000000
0x6021a0: 0x0000000000000000 0x0000000000000000
0x6021b0: 0x0000000000000000 0x0000000000000000
0x6021c0: 0x0000000000000000 0x0000000000000000
0x6021d0: 0x0000000000000000 0x0000000000000000
free(q)之后的heap与内存布局
0x602000 FASTBIN {
prev_size = 0,
size = 129,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602080 FASTBIN {
prev_size = 0,
size = 129,
fd = 0x7ffff7dd1be8 <main_arena+200>,
bk = 0x7ffff7dd1be8 <main_arena+200>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602100 FASTBIN {
prev_size = 128,
size = 129,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602180 PREV_INUSE {
prev_size = 0,
size = 1041,
fd = 0x3039303230367830,
bk = 0xa,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602590 PREV_INUSE {
prev_size = 0,
size = 133745,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
---------------------------------------------------------------------------------------------------------------------------
0x602000: 0x0000000000000000 0x0000000000000081
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000081 ==>chunk未发生变化
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
0x602100: 0x0000000000000000 0x0000000000000081
0x602110: 0x0000000000000000 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000000000
0x602150: 0x0000000000000000 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000020e81
//此时free掉的chunk_size为81的原因 :free掉了,但没有把指针置空,第二个堆块变成空闲堆块,由bins链管理,chunk_size依然为0x80
此时的bins链
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x602080 ?— 0x0 ==》被释放的chunk进入了fastbin链
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
分配新的0x60的chunk后的heap和内存布局
0x602000 FASTBIN {
prev_size = 0,
size = 129,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602080 FASTBIN { ===>chunk2被切割出0x70的空间,剩下的0x10自动和top_chunk合并
prev_size = 0,
size = 129,
fd = 0x7ffff7dd1be8 <main_arena+200>,
bk = 0x7ffff7dd1be8 <main_arena+200>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602100 {
prev_size = 128, //说明上一个chunk被free掉了,大小为0x80
size = 128,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602180 PREV_INUSE { //printf的缓冲区堆块 ,从top_chunk中分配,多个printf分配一个chunk
prev_size = 0,
size = 1041,
fd = 0x3039303230367830,
bk = 0xa,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602590 PREV_INUSE {
prev_size = 0,
size = 133745,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
---------------------------------------------------------------------------------------------------------------------------
0x602000: 0x0000000000000000 0x0000000000000081
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000081 ==>重新组装的chunk2
0x602090: 0x00007ffff7dd1be8 0x00007ffff7dd1be8 ==>fd和bk指向main_arena的地址
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
0x602100: 0x0000000000000080 0x0000000000000081
0x602110: 0x0000000000000000 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000000000
0x602150: 0x0000000000000000 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000411
0x602190: 0x3039303230367830 0x000000000000000a
0x6021a0: 0x0000000000000000 0x0000000000000000
0x6021b0: 0x0000000000000000 0x0000000000000000
0x6021c0: 0x0000000000000000 0x0000000000000000
0x6021d0: 0x0000000000000000 0x0000000000000000
此时的bins链
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x80: 0x602080 —? 0x7ffff7dd1be8 (main_arena+200) ?— 0x602080
largebins
empty
源代码二
#include<stdio.h>
#include<malloc.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char *argv[])
{
int size = 0x100;
void *p = malloc(size);
void *junk = malloc(size); //创建以junk防止释放p,q时q的prev_inuse为0而合并
void *q = malloc(size);
void *r = malloc(size); //防止q和top_chunk合并,在没有printf的前提下
sleep(0);
printf("p:0x%x\n",p);
printf("q:0x%x\n",q);
printf("r:0x%x\n",r);
sleep(0);
strcpy(p,"aaaaaaaaaaaaaaaaa");
strcpy(q,"bbbbbbbbbbbbbbbbb");
strcpy(r,"ccccccccccccccccc");
sleep(0);
free(p);
free(q);
sleep(0);
return 0;
}
在不创建junc和r的情况下,p,q以及p,q,top_chunk会发生合并
#include<stdio.h>
#include<malloc.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char *argv[])
{
int size = 0x100;
void *p = malloc(size);
void *q = malloc(size);
sleep(0);
strcpy(p,"aaaaaaaaaaaaaaaaa");
strcpy(q,"bbbbbbbbbbbbbbbbb");
free(p);
free(q);
sleep(0);
return 0;
}
---------------------------------------------------------------------------------------------------------------------------
0x602000 PREV_INUSE { ===>p、q合并
prev_size = 0,
size = 545,
fd = 0x7ffff7dd1b78 <main_arena+88>, ===>fd指向main_arena
bk = 0x7ffff7dd1b78 <main_arena+88>, ===>bk指向main_arena
// 因为当前只有一个main_arena只管理一个合并之后的堆块了
fd_nextsize = 0x61,
bk_nextsize = 0x0
}
0x602220 {
prev_size = 544,
size = 1040,
fd = 0x3132303678303a71, ===>print
bk = 0xa3032,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602630 PREV_INUSE {
prev_size = 0,
size = 133585,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
此时的bins链
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x602000 —? 0x7ffff7dd1b78 (main_arena+88) ?— 0x602000 ==>只有一个合并之后的堆块
smallbins
empty
largebins
empty
(main_arena+88)的原因
main_arena的结构体前项变量:
__libc_lock_define (, mutex); //定义了一个0x4字节的lock
int flags; //0x4
int have_fastchunks; //0x4
mfastbinptr fastbinsY[NFASTBINS]; //fastbin链的管理头,总共10个, 每个0x10字节
mchunkptr top; //0x4 到此为止总共0x96字节
注:glibc2.27为0x96
vmmap查看到(main_arena+88)出现在libc的地址范围
0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
相同版本的libc,main_arena的偏移固定
main_arena的地址存放在0x602000中
pwndbg> x/20gz 0x602000
0x602000: 0x0000000000000000 0x0000000000000221
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
利用方法
overlap --> 打印当前堆块的内容 --> 获取main_arena的地址 --> 通过固定的偏移计算出libc的地址
当size大于0x400的时候,free之后的堆块首先会进入unsorted bin,触发整理后才会进入large bin
free掉0x400以上的堆块后,再malloc一个堆块,才会触发整理,进入到large bin如在源码二的最后加上malloc(0x2000),并把size改为0x1000
#include<stdio.h>
#include<malloc.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char *argv[])
{
int size = 0x1000;
void *p = malloc(size);
void *junk = malloc(size);
void *q = malloc(size);
void *r = malloc(size);
sleep(0);
printf("p:0x%x\n",p);
printf("q:0x%x\n",q);
printf("r:0x%x\n",r);
strcpy(p,"aaaaaaaaaaaaaaaaa");
strcpy(q,"bbbbbbbbbbbbbbbbb");
strcpy(r,"ccccccccccccccccc");
free(p);
free(q);
sleep(0);
malloc(0x2000);
sleep(0);
return 0;
}
此时fd_nextsize和bk_nextsize有效
0x602000 PREV_INUSE {
prev_size = 0,
size = 4113,
fd = 0x604020,
bk = 0x7ffff7dd2198 <main_arena+1656>,
fd_nextsize = 0x602000,
bk_nextsize = 0x602000
}
未malloc(2000)当前的bins链
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x604020 —? 0x602000 —? 0x7ffff7dd1b78 (main_arena+88) ?— 0x604020 /* ' @`' */
smallbins
empty
largebins
empty
malloc(0x2000)的bins链
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
0x1000: 0x602000 —? 0x604020 —? 0x7ffff7dd2198 (main_arena+1656) ?— 0x602000
对于新malloc的堆块大小,有这几种情况
0-0x999
0x1000-0x1008
0x1009以上

当size小于0x80的时候,free后会进入到fastbin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x6020e0 —? 0x602000 ?— 0x0 //p , q都进入fastbin
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
当malloc(0x60)的新chunk时,会首先从后free的0x6020e0中分配空间,分配之后,0x6020e0退出fastbin
0x70: 0x602000 ?— 0x0
源代码三
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<pthread.h>
void* threadFunc(void* arg)
{
int size = 0x200;
sleep(0);
void *q = malloc(size);
printf("%p\n",q);
sleep(0);
free(q);
sleep(0);
}
int main(int argc,char *argv[])
{
int size = 0x100;
void *p = malloc(size);
free(p);
pthread_t t1;
void *s;
int ret;
ret = pthread_create(&t1,NULL,threadFunc,NULL);
ret = pthread_join(t1,&s);
return 0;
}
子线程的堆空间
pwndbg> c
Continuing.
0x7ffff00008c0 ==>第一个堆块
pwndbg>vmmap
0x602000 0x623000 rw-p 21000 0 [heap]
0x7ffff0000000 0x7ffff0021000 rw-p 21000 0
0x7ffff0021000 0x7ffff4000000 ---p 3fdf000 0
0x7ffff6fef000 0x7ffff6ff0000 ---p 1000 0
查看内存布局
pwndbg> x/20gz 0x7ffff00008c0 -0x10
0x7ffff00008b0: 0x0000000000000000 0x0000000000000215 NO_MAIN_ARENA & PREV_INUSE = 5
0x7ffff00008c0: 0x0000000000000000 0x0000000000000000
0x7ffff00008d0: 0x0000000000000000 0x0000000000000000
0x7ffff00008e0: 0x0000000000000000 0x0000000000000000
偏移查看arena结构
pwndbg> x/100gz 0x7ffff00008c0 -0x10 + 0x200
0x7ffff0000ab0: 0x0000000000000000 0x0000000000000000
0x7ffff0000ac0: 0x0000000000000000 0x0000000000000415 ==>第二个堆块,分配了0x410的空间
0x7ffff0000ad0: 0x3066666666377830 0x000a306338303030 ==>printf的堆块
0x7ffff0000ae0: 0x0000000000000000 0x0000000000000000
pwndbg> x/s 0x7ffff0000ad0
0x7ffff0000ad0: "0x7ffff00008c0\n"
pwndbg> x/20gz 0x7ffff00008c0 -0x10 + 0x200 + 0x420
0x7ffff0000ed0: 0x0000000000000000 0x0000000000020131 ==>子线程的top_chunk
0x7ffff0000ee0: 0x0000000000000000 0x0000000000000000
free掉q之后NO_MAIN_ARENA置0
pwndbg> x/20gz 0x7ffff00008c0 -0x10
0x7ffff00008b0: 0x0000000000000000 0x0000000000000211
0x7ffff00008c0: 0x00007ffff0000078 0x00007ffff0000078 ==> 类似于主线程的main_arena + 88
pwndbg> x/20gz 0x00007ffff0000078
0x7ffff0000078: 0x00007ffff0000ed0 0x0000000000000000 ==> 子线程的top_chunk
0x7ffff0000088: 0x00007ffff00008b0 0x00007ffff00008b0 ==> 子线程的第一个堆块
0x7ffff0000098: 0x00007ffff0000088 0x00007ffff0000088
0x7ffff00000a8: 0x00007ffff0000098 0x00007ffff0000098
0x7ffff00000b8: 0x00007ffff00000a8 0x00007ffff00000a8
0x7ffff00000c8: 0x00007ffff00000b8 0x00007ffff00000b8
0x7ffff00000d8: 0x00007ffff00000c8 0x00007ffff00000c8
0x7ffff00000e8: 0x00007ffff00000d8 0x00007ffff00000d8
0x7ffff00000f8: 0x00007ffff00000e8 0x00007ffff00000e8
0x7ffff0000108: 0x00007ffff00000f8 0x00007ffff00000f8
//此这里可以leak main_arena的结构
实验总结
main_arena为主线程的arena
在第一次调用malloc之后,会用brk调用生成堆,并生成top_chunk之后分配的堆,都会从top_chunk上切割当top_chunk不够用时,会再次通过brk系统调用申请内存副线程的arena通过mmap获得,并也会生成top_chunk
好了,到这儿,二进制安全之堆溢出(系列)——堆基础 & 结构就更完了!!
