1. what is heap?
heap其实指的是程序当中动态分配的内存空间
其用途在于灵活的控制程序当中的内存大小。常见的使用例子是:游戏中NPC们的可变长度列表
1.1 古早的动态分配:mmap
mmap其实是最初的动态分配的雏形,它允许程序根据需要申请/释放内存,且内存长期存在,不会随函数结束就释放。
但是mmap的缺点也有:1、mmap申请/释放的内存大小必须是4096字节的整数倍,不够灵活;2、mmap申请/释放内存速度非常慢,因为涉及到系统调用,陷入内核
1.2 更聪明的做法: 维护mmap得到的内存,按需取用
如果我们先用mmap得到一块内存,将这块内存管理起来,按需求分配,这样在需求小于一定值的时候,不需要进行系统调用,方便快捷!
这个思路直接导致Dynamic Allocators
诞生
不同系统使用的Dynamic Allocators
版本也不一样:
General Purpose:
Doug Lea (pictured) releases dlmalloc into public domain in 1987.
Linux:
ptmalloc (Posix Thread aware fork of dlmalloc)
FreeBSD:
jemalloc (also used in Firefox, Android)
Windows:
Segment Heap, NT Heap
Kernel allocators:
kmalloc (Linux kernel memory allocator)
kalloc (iOS kernel memory allocator)
1.3 ptmalloc
本篇文章中描述全都是ptmalloc相关特性,不同的动态分配器机制不同,请不要混淆
大部分动态分配器(当然包括ptmalloc)都提供了如下功能:
1. malloc() - allocate some memory
2. free() - free a prior allocated chunk
And some auxiliary functions:
3. realloc() - change the size of an allocation
4. calloc() - allocate and zero-out memory
1.3.1 ptmalloc工作原理
在某种历史原因下,ptmalloc并不是直接用mmap申请内存
1. ptmalloc 初始化申请的内存通常都在程序data segment +随机某个值的位置。
2. ptmalloc 初始化时,使用brk和sbrk系统调用来申请空间
sbrk(NULL) : 返回data segment的末尾
sbrk(delta) : 从data segment的末尾扩张delta字节
brk(NULL): 返回data segment + 随机某个值的位置
brk(addr):将data segment + 随机某个值 扩展delta字节
3. ptmalloc 对于小内存,通常都用第2步申请来的空间,较大的内存都通过mmap申请。
1.4 dangers of heap
因为咱是做安全的,当然要考虑的是heap有什么风险啦,话又说回来,heap的安全性通常是和性能呈现一定程度的对抗关系的,看下图:
首先动态分配器其存在的必要性无需多言。
1. 应用开发者希望他们的应用能更方便更快速,他们希望动态分配器分配内存的速度也更快!
2. 分配器的开发者回了回应诉求,开始优化动态分配器的运行速度
3. 安全研究人员发现某种优化会导致严重的安全隐患后,通知分配器开发者
4. 分配器开发者认为安全不重要,速度更重要(很合理,毕竟分配器好坏与否,分配器开发者的收益 取决于使用分配器的人)
5. 安全研究人员利用这些风险,并通过媒体揭露风险
6. 分配器开发者在大众压力下决定修复这个漏洞
7. 重回第1步
闭环了(😀
说回正题,heap的主要风险有以下四种:
1. Forgetting to free memory. 忘记释放内存
leads to resource exhaustion 导致资源耗尽
2. Forgetting that we have freed memory. 忘记我们已经释放了内存
using free memory 我们常说的uaf
freeing free memory 我们常说的double free
3. Corrupting metadata used by the allocator to keep track of heap state. 破坏管理堆的元数据(大概率是堆溢出)
conceptually similar to corruption internal function state on the stack 概念上和破坏栈上的函数状态类似, 即我们常说的堆风水
4. memory disclosure. 即信息泄露
某些chunk中可能保存了敏感信息(如密码),如果未及时清零,可能导致关键信息泄露,或者泄露地址
2. tcache
其实之前的blog也讲过,参考glibc 2.31 常见利用手法中tcache利用篇
的章节。
tcache
,是ptmalloc分配器中的Thread Local Caching
,它的诞生是为了加速 单线程中小内存的重复申请。
tcache bin
是一个先进先出的单向链表,不过多复述。
2.1 tcache free过程(glibc2.31)
2.2 tcache allocation过程(glibc2.31)
tcache在malloc过程中不会检查,这给了我们可乘之机。
2.3 tcache double free(glibc2.31)
在tcache double free的成立条件是释放内存块a后,仍然能够修改a->key=0x1234,绕过double free的校验
int main(){
unsigned long long * a;
a = malloc(128);
printf("Original malloc: %p\n",a);
printf("Freeing: %p\n",a);
free(a);
printf("Corrupting key of: %p\n",a);
a[1] = 0x1234;
printf("Double-freeing: %p\n",a);
free(a);
printf("malloc 1: %p\n",malloc(128));
printf("malloc 2: %p\n",malloc(128));
printf("malloc 3: %p\n",malloc(128));
}
2.4 tcache poisoning(glibc2.31)
所谓的tcache poisoning,实际是释放内存块a后,仍然能够修改a->next=xxxx,使其执行某个我们设置的内存块
int main(){
char stack_buffer[16];
unsigned long long *a;
unsigned long long *b;
a = malloc(16);
b = malloc(16);
free(b);
free(a);
a[0] = stack_buffer;
printf("Stack buffer: %p\n",stack_buffer);
printf("First malloc: %p\n",malloc(16));
printf("Second malloc: %p\n",malloc(16));
}
3. Chunks and Metadata
正如tcache上面提到的分配和释放规律,我们可以知道ptmalloc使用了很多metadata
(global metadata:tcache structure,还有每个chunk本身的metadata)来管理它分配/释放的内存。
通俗意义上来说chunk = metadata + 我们申请后使用的内存
,如下图所示:
3.1 overlapping metadata
为了节约内存,申请类似0x18大小的内存,实质上跟申请0x10的chunk类似,但是它会复用下一个chunk的prev_size字段。
3.2 freed chunks metadata
前面提到,每个chunk本身也是有metadata的,主要体现在chunk被释放之后
3.2.1 Different Caches
目前ptmalloc按照以下5个部分划分cache
1. 64 singly-linked tcache bins for allocations of size 16 to 1032 (functionally "covers" fastbins and smallbins)
2. 10 singly-linked "fast" bins for allocations of size up to 160 bytes
3. 1 doubly-linked "unsorted" bin to quickly stash free()d chunks that don't fit into tcache or fastbins
当tcache bin和fastbin里都没有时,会从unsorted bin里找。如果找到一个大小和需求一样的chunk,返回它;如果没找到,unsorted bin里的所有chunk都会被归类到small bin和large bin里,然后选一个大小比需求大的,切割它,然后归类剩余部分的chunk后返回。
4. 64 doubly-linked "small" bins for allocations up to 512 bytes
5. doubly-linked "large" bins (anything over 512 bytes) that contain different-sized chunks
3.2.2 tcache freed chunks
一个tcache被释放后,它会新增一部分metadata,如下图所示:
3.2.3 largebin freed Chunks
双向链表被释放后,其metadata是长这样的:
3.2.4 The Wilderness
Wilderness
其实指的就是ptmalloc申请的一大块内存 的 剩余部分。
3.3 Metadata Corruption
破坏metadata的目的通常是为了能够 任意地址写/申请一个重叠的内存(能够修改metadata),从而完成利用。
历史上出现过的metadata corruption方法有很多:
1、The Unlink Attack
2、Poison Null Byte
3、House of Force
4、House of Spirit
具体的实施方法等后续利用需要时,我们再来分析,因为随着glibc版本升级变得越来越安全,很多攻击方法现在已经不能用了。
然而,新的攻击方法一直在诞生,学无止境啊。
题外话:setbuf的作用
相信大家在做ctf题目的时候,尤其是heap类型的题目,在题目开头总是会有几个函数
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
这些函数的作用就一个禁止libc中对于描述符的buffer,否则像printf/scanf会在函数内部调用malloc获取输入/输出缓冲区,影响做题