堆的攻击
堆溢出
原理
简介:
- 程序向堆块写入的字节数超过了堆块本身的字节数(不是用户申请的字节数,因为在内部用户申请的字节数会被转换为实际向系统申请的字节数,该数大于等于用户申请的字节数),可以覆盖到相邻的高地址的下一个堆块
条件:
- 向堆上写入数据
- 写入的数据大小没被良好的控制
与栈溢出等不同的是我们不可以直接通过堆溢出直接控制EIP,我们也就无法直接控制程序执行对应的程序,对于堆溢出:
- 覆盖下一个chunk的内容
- pre_size
- size中的三个bit
- NON_MAIN_ARENA
- IS_MAPPED
- PREV_INUSE
- the true chunk size
- chunk content,改变程序固有执行流
- 利用堆中unlink来实现任意地址写入或控制堆块的内容等效果来控制程序流
总结
- 寻找堆分配函数
- 通常来说调用malloc来分配,有些时候会使用calloc来分配,区别是calloc在分配后会自动清空,对于某些信息泄露的漏洞不好
- 还有realloc,就是相当于扩充内存块,realloc(ptr,size)
- size>原来的size
- chunk与top chunk相邻,直接扩展到目标大小
- 不相邻,就分配一个新内存块,并将数据复制过去,原来的那个释放掉
- <
- 相差的小于最小的chunk(32为16字节,64为32字节),则保持不变
- 大于等于,就切割原来的chunk为两个部分,free掉后面的那个
- size=0
- 相当于free(ptr)
- size=ptr的size,不做任何操作
- size>原来的size
- 寻找危险函数
- 输入
- gets
- scanf
- vscanf
- 输出
- sprintf
- 字符串
- strcpt,字符串复制
- strcat,字符串拼接
- brocpy,内存块复制
- 输入
- 确定填充长度
- 这里需要注意填充的长度并不是程序申请的长度,因为程序的长度还会在被转换为内部实际需要申请的长度
- 64位中 in_use_size=(用户请求大小 +16(pre_size+size)-8(借了下一个chunk的pre_size)) align to 16B
堆中的Off-By-One
就是溢出时刚好只多了一个字节,往往与边界验证不严和字符串操作有关,比如
- 使用循环时次数设置错误
- 字符串操作不合适,比如
stelen和strcpt不一致时,strlen不会考虑\x00但strcpy会拷贝
利用思路
- 溢出字节是可控制的人以字节:通过修改大小造成块之间出现重叠->泄露数据,或是覆盖数据
- 溢出字节是NULL字节:在size=0x100时,溢出NULL字节可以让
prev_in_use被清理,这样前面的那个块就会被认为是free的,这时- 可以使用
unlink处理 - 这是
pre_size就会启用,可以伪造它来造成块之间的重叠,这个方法的关键在于unlink没有检查按照pre_size找到的块与pre_size是否一致
- 可以使用
在新版本中加入了对方法二的check
1 | |
Chunk Extend and Overlapping
名词解释:
Chunk Extend:通过修改堆块大小,扩展其范围以覆盖相邻内存Overlapping:使多个堆块内存区域重叠,通过一个堆块修改另一个堆块的内容
条件:
- 程序存在堆漏洞
- 漏洞可以控制
chunk header(就是size那些数据)中的数据
原理:
- 该攻击方式能够实现的原因在于
ptmalloc对堆操作时的各种宏 - 简而言之,
ptmalloc通过chunk header的数据判断chunk的使用情况和对前后chunk进行定位,chunk extend就是通过控制size和pre_size来实现跨越块的操作从而overlapping的
实例:
对
inuse的fastbin进行extend就是通过第一个块的大小来控制第二个块的内容(演示都是在64位环境下)
通过修改第一个块的
size来在free时将两个块变成一个块,这样在第二次malloc时可以获得第二个块内容
1 | |
对
inuse的smallbin进行extend- 与
fastbin方法类似,不过small bin free时会被置于unsorted bin,而且释放时如果与top chunk相连,就会和并到top chunk里
- 与
1 | |
- 对
free的small bin进行extend
1 | |
- 通过
extend后向overlapping - 通过
extend前向overlapping
Chunk Extend作用
这种技术不能直接控制程序的执行流程,但是可以控制chunk的内容,如果chunk内存在字符串指针或函数指针之类的,就可以通过此来信息泄露和控制程序流
通过extend来overlapping,通过overlapping可以控制chunk中fd/bk指针从而实现fastbin attack等利用
unlink
顾名思义,脱链操作
unlink原理:
对chunk中内容进行布局,接触unlink操作来发成修改指针的目的
核心
1 | |
unlink前:

unlink后:

这样就实现了内存块P的脱链
原始的unlink
存在有物理空间连续的两个chunk(Q,nextchunk),Q在使用,Nextchunk释放。那么我们通过溢出等方式将 Nextchunk 的 fd 和 bk 指针修改为指定的值。则当我们 free(Q) 时
- glibc 判断这个块是 small chunk
- 判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并
- 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并
- 继而对 Nextchunk 采取 unlink 操作
那么在unlink时
首先是修改堆的数据(下面两句)
- FD=P->fd = target addr -12(也就是FD->bk=target addr)
- BK=P->bk = expect value(也就是BK=expect value)
然后写入(往target addr写入expect value)
- FD->bk = BK,即 *(target addr-12+12)=BK=expect value(就是*target_addr=expect value)
- BK->fd = FD,即 *(expect value +8) = FD = target addr-12(这里回破坏expect value+8的值,需要注意)
这样就实现了往target addr写入expect value
现代的unlink
(只截取了一小部分)
1 | |
所以想要伪造fake chunk就必须要满足
1 | |
所以我们伪造的
1 | |
才可以进行unlink
做题中遇到的unlink
当 got 表可写,并且没有查看堆块信息的函数的时候,并且存在堆溢出的漏洞,并且有一块比如 bss 上的地址来记录堆的指针的情况下,可以利用 unlink 来将free_got写成 puts_plt 来输出地址从而泄露 libcbase
具体操作(举个例子来说明)
- 首先分配三个堆块
- add(0x10,0x60,0x60)
- 然后构造aim=chunk2_ptr
- fd=aim-0x18
- bk=aim-0x10(这样是可以通过检查的,由于存在记录指针的 bss 段地址)
- 在第二个块里面写上p64(0)+p64(0x50)(chunk2size-0x10)+p64(fd)+p64(bk)+b’A’*(0x60-8*4)+p64(0x50)(pre_size)+p64(0x60)(下一个堆块的size)
- 然后 delete(3) 即可将 aim-0x18 写入 chunk2_ptr 的位置上,从而可以控制堆块指针,可以将其修改为 got 表地址,从而修改 got 表地址
原理就是在 delete(3) 是发现 pre_inuse 为 0,就会去寻找 prechunk,而 pre_size 被我们控制,就会找到我们伪造的chunk,就会对伪造的 chunk 进行 unlink
Use After Free
原理
内存块被释放后,其对应指针没有被设置成NULL
- 没有代码对内存块进行修改,那么这个内存块在下一次使用时程序很有可能可以正常运转
- 有代码对其修改,那么在下一次使用时会出现奇怪的问题
我们称释放后没有被设置成NULL的指针称为dangling pointer
Fastbin Attack
前提
- 存在堆溢出,use-after-free等能控制堆中内容的漏洞
- 漏洞发生在fastbin的chunk中
分类
- Fastbin Double Free
- House of Spirit
- Alloc to Stack
- Arbitrary Alloc
前两种侧重利用free释放掉真的chunk或伪造的chunk,然后再次申请chunk进行攻击,后两种侧重故意修改fd指针,直接用malloc申请指定位置的chunk进行攻击
原理
fastbin attack 存在的原因在于 fastbin 是使用单链表来维护释放的堆块的,并且由 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 位也不会被清空。
例子
1 | |
释放前
1 | |
三次free后
1 | |
可以看到此时的三个 chunk 组成了一个单向链表
1 | |
Fastbin Double Free
原理
fastbin中chunk可以被多次释放,因此可以在fastbin链表存在多次,这样导致多次从fastbin链表取出同一块堆块,相当于多个指针指向同一个堆块
条件
- fastbin的堆块释放后next_chunk的pre_inuse位不会清空
- fastbinfree时仅验证main_arena直接指向的块,对于链表后的块并没有验证
1 | |
示例
fastbin是FIFO机制
我们可以在chunk1释放后再释放chunk2,这样使main_arena指向chunk2,此时再次释放chunk1就不会被检测到
1 | |
第一次free

第二次free

第三次free

此时chunk1的fd指向了chunk2
接着malloc将第一个chunk1释放掉,再修改chunk1中fd指针为target_addr,这样Fastbin就变成了main_arena->chunk2->chunk1->target_addr,这样我们就可以分配到target_addr处的堆块,这样就可以在此进行读写
但int_malloc_会对即将分配位置的size域检查,如果size和当前fastbin链表应该有size不符就抛出异常
House Of Spirit
核心
该技术核心在于在目标位置伪造fastbin chunk,并将其释放,从而达到分配指定地址的chunk的目的
条件
想伪造fastbin chunk并放入fastbin链表中,那么就要绕过一些必要的检查
- fake chunk的ISMMAP位不能为1,因为mmap分配的chunk会被单独处理
- fake chunk的地址需要对齐
- fake chunk的size的大小需要满足fastbin的要求,同时需要对齐
- fask chunk的next chunk的大小不能小于
2*SIZE_SZ,同时不能大于av->system_mem - fake chunk对应的fastbin链表头不能是fake chunk,即不能构成double free情况
(以上注意事项源码里都有)
总结
该技术分配chunk到指定地址,其实不需要修改指定地址的任何内容,关键是要能够修改指定地址前后的内容使其能够绕过检测
Alloc to Stack
原理
关键在于劫持 fastbin 链表里的 chunk 的fd指针,把fd指针指向我们想要分配的栈上,从而控制栈上的一些关键数据,比如返回地址等
演示
该代码将 fake_chunk 置于栈中的称为 stack_chunk
1 | |
总结
通过该技术我们可以把 fastbin chunk 分配到栈中,从而控制返回地址等关键数据。要实现这一点我们需要劫持 fastbin 中 chunk 的 fd 域,把它指到栈上,当然同时需要栈上存在有满足条件的 size 值。
Arbirary Alloc
原理
与Alloc to Stack完全相同,唯一区别是目标不再是栈中,事实上只要目标地址存在合法size域(人为的还是自然的都无所谓),我们可以把chunk分配到任意的可写内存中,比如bss、heap、data、stack等
总结
Arbitrary Alloc在CTF中用的更加频繁。我们可以利用字节错位等方法来绕过size域的检查,实现任意地址分配chunk,最后效果也就相当于任意地址写任意值
Unsorted Bin Attack
概述
- Unsorted Bin Attack 被利用前提:控制Unsorted Bin Chunk的bk指针
- Unsorted Bin Attack 可以达到的效果是实现修改任意地址值为一个较大的数值
回顾
来源
- 当一个较大的chunk分割成两半时,若剩下的大于MINSIZE
,就会被放到unsorted bin中 - 释放一个不属于fastbin的chunk且其不与top chunk相邻时,就会首先被放到unsorted bin中
使用情况
- 遍历顺序:FIFO 插入时插入到unsorted bin头部,取出时从链表尾获取
- malloc 时,如果从fastbin和small bin中找不到对应大小的chunk,就会尝试从unsorted bin中寻找chunk,若满足用户,就返回给用户,否则放到对应的chunk中
Unsorted Bin Leak
Unsorted bin结构
双向链表

可以看到链表中的尾节点的 fd 指针会指向 main_arena 结构体内部。
Leak原理
- 泄露
fd指针,得到一个与main_arena有固定偏移的地址 - 这个地址相对于
libc基址是固定的偏移,从而实现对ASLR的绕过
获取main_arena和libc基址偏移
_malloc_trim函数得出
将.so文件放到IDA中,找到__malloc_trim函数,就可以获得偏移了

通过_malloc_hook算出
main_arena和_malloc_hook的地址差是0x10,而大多数libc都可以直接查出_malloc_hook的地址,这样就可以减少工作量
pwntools:
1 | |
实现leak
- 一般需要有
UAF,将一个 chunk 放入Unsorted Bin中后再打出其fd。一般的笔记管理题都会有show的功能,对处于链表尾的节点show就可以获得 libc 的基地址了 - CTF中的利用,堆往往是刚初始化的,所以
Unsorted Bin中是干净的,当里面只有一个bin时,该bin的fd和bk都会指向main_arena - 当链表尾无法访问时,可以访问链表头,对链表头的
printf往往可以把fd和bk一起输出出来,但在64位环境下,高地址往往是\x00截断printf的输出,无法做到有效leak
Unsorted Bin Attack原理
当将一个 unsorted bin 取出时,会将bck->fd位置写入本unsorted bin的位置
1 | |
也就是说如果我们控制了bk的值,我们就能将unsorted_chunk写到任意地址
例子

- 初始:
- unsorted bin 的
fd、bk都指向本身
- unsorted bin 的
- free(p)
- 由于p大小不属于fast bin,所以其会被先放到unsorted bin中
- 修改p[1]
- 修改后,原来在unsorted bin中的p的bk就会指向target-0x10处的伪造的chunk,Taget valus处于伪造的chunk的fd处
- 申请400大小的chunk
- 此时申请的chunk在small bin范围内,但bin中没有chunk,所以去unsorted bin中找,发现其不为空,于是取出最后一个chunk
- 结果
- 修改target处的值为unsorted bin链表头地址
- 可以修改任意地址但是修改的值却不受我们的控制
- 作用
- 我们通过修改循环的次数来使得程序可以执行多次循环。
- 我们可以修改 heap 中的 global_max_fast 来使得更大的 chunk 可以被视为 fast bin,这样我们就可以去执行一些 fast bin attack 了。
总结
通过控制unsortedbin里的空闲chunk->bk,来实现任意地址写
操作:
- 将
bk修改成为target_addr-0x10 - 在取出
chunk的时候,即可往target_addr处写入unsortedbin链表头的地址,也就是main_arena + 0x58(glibc < 2.26)或main_arena + 0x68(glibc ≥ 2.26)
Large Bin Attack
原理
主要利用chunk进入bin中的操作,在malloc时,遍历unsorted bin,对每一个chunk,若无法exact-fit分配(精准匹配分配)或不满足切割分配的要求,就会将chunk置入大小对应的bin中,而此过程缺乏对large bin跳表指针(fd_nextsize和bk_nextsize)的检查
2.29版本及以下,根据unsorted bin大小不同
1 | |
unsorted bin小于链表中最小的chunk时候执行前一句,else后一句
当二者大小相同时执行
1 | |
所以此时无法利用(将新堆块插入到链表第二个位置,是固定的,不会影响fd_nextsize和bk_nextsize,所以无法利用)
所以有两种利用方法
在2.30版本新加入对large bin跳表的完整性的检查,使unsorted chunk大于链表中最小的chunk的时候利用失效,必须使unsorted bin小于链表中最小的chunk,通过
1 | |
实现将本chunk的地址写入到bk_nextsize+0x20处
例子
核心源码:
1 | |
例子:
1 | |
p2修改后

74行,结合2.23源码进行分析
1 | |
总结
主要利用两个chunk,大的p3,小的p2,通过伪造p2->bk,p2->bk_nextsize,分别指向target-0x10,target-0x20放到largebin里(在此之前largebin最好为空),而大堆块malloc后进入largebin,实现将p3头指针赋值给target1,target2
how2heap 中也说了,large bin attack 是未来更深入的利用。现在我们来总结一下利用的条件:
- 可以修改一个 large bin chunk 的 data
- 从 unsorted bin 中来的 large bin chunk 要紧跟在被构造过的 chunk 的后面
- 通过 large bin attack 可以辅助 Tcache Stash Unlink+ 攻击
- 可以修改 _IO_list_all 便于伪造 _IO_FILE 结构体进行 FSOP。
Tcache attack
overview
tcache中新增了两个结构体,tcache_entry和tcache_perthread_struct
1 | |
其中重要的两个函数tcache_get(),tcache_put()
1 | |
这两个函数再_int_free和__libc_malloc开头被调用
tcache在分配大小<=0x408并且给定大小的tcachebin没有满时掉哟个
一个tcachebin中最大快数mp_.tcache_count是7
在tcache_get中,仅检查了tc_idx,可以当作一个类似于fastbin的单独链表,只是它的检查没有fastbin那么复杂,仅检查tcache->entries[tc_idx] = e->next;
usage
- 释放
在free函数处理的最先部分,首先检查对齐情况和前后堆块的释放情况,然后就是tcache
申请
在
malloc中有多处会将内存块移入tcache中- 申请内存块符合
fastbin大小 且 在fastbin中找到可用空闲块,会将该fastbin链上的其他内存块移到tcache中 - 申请的内存块符合
smallbin大小 且 在smallbin内找到可用的空闲块时,会把该smallbin链上的其他内存块放入tcache中 - 当在
unsorted bin链上循环处理时,当找到大小合适的链时,并不直接返回,而是先放到tcache中
- 申请内存块符合
tcache取出:在内存申请的开始,会先根据申请大小看tcache中是否存在对应的内存块,如果存在就直接返回,否则再_int_malloc在循环处理
unsorted bin内存块时,如果达到放入unsortedbin块的最大数量,则立即返回。默认值0,表示不存在上限在循环处理
unsorted bin内存块后,如果之前曾放入过tcache,则会取出一个并返回
tcache poisoning
原理
通过覆盖tcache中的next,不需要伪造任何chunk即可实现malloc到任何地址
利用
tcache的next指针和fd指针在同一个位置
1 | |
总结
跟fastbin类似,将一个free在tcache中的块的next修改成目标地址,两次free就能得到目标地址的内存块
tcache dup
原理
tcache_put的检查不严谨,大幅提高性能的同时安全性也下降了许多
1 | |
但是2.29版本以后引进了对double free的检查
利用
直接两次free同一个块即可形成cycliced list
1 | |
tcache perthread corruption
原理
tcache_perthread_struct 是整个 tcache 的管理结构,如果能控制这个结构体,那么无论我们 malloc 的 size 是多少,地址都是可控的。
利用
假设此时有堆排布情况如下
1 | |
通过一些手段(比如 tcache posioning ),我们将其改为了
1 | |
这样我们就可以通过两次malloc就返回了 tcache_perthread_struct 的地址,就可以控制整个 tcache 了。
因为 tcache_perthread_struct 也在堆上,因此这种方法一般只需要 partial overwrite 就可以达到目的。
tcache house of spirit
原理
由于tcache_put()检查不严格,在释放的时候没有检查这个指针是否真的是对上malloc来的指针,我们通过伪造一个fake chunk将这个指针释放掉这个地址就会进入tcache中,而后再malloc就能获取这个地址
利用
1 | |
在free(a)后

smallbin unlink
在 smallbin 中包含有空闲块的时候,会同时将同大小的其他空闲块,放入 tcache 中,此时也会出现解链操作,但相比于 unlink 宏,缺少了链完整性校验。因此,原本 unlink 操作在该条件下也可以使用。
tcache stashing unlink attack
原理
在tcachebin没满,从smallbin中分配内存且smallbin中有空闲chunk时,在获取到一个smallbin后smallbin任然有空闲chunk,glibc会将smallbin中的chunk批量转移到tcache中
我们通过伪造smallbin的bk为target addr,其可以在任意地址上写一个libc地址(类似unsorted bin attack的效果),构造得当也可分配fake chunk到任意地址
利用
1 | |
libc leak
在以前的libc版本,我们只需要
1 | |
而在2.26之后,首先要将tcache填满,(否则会放入tcache中)
1 | |
一些
key
2.27版本加入key成员,其值为 tcache_perthread_struct( heap_base+0x10),位置在bk上
2.34以后key成员变成随机数
next
2.32加入safe-linking机制,此时不能直接伪造next,要加密后在覆盖
加密及解密
1 | |
- 如何伪造:
- 首先
free一个chunk到tcache - 由于
tcache中只有一个chunk,其fd为0,与pos>>12的值异或后仍然为pos>>12(>>是右移,比如0x12345678>>12=0x12345(12为2进制为3位16进制))(pos>>12貌似是堆基地址的高位) - 所以我们利用漏洞泄露出这个
pos>>12,在以后的伪造中,将我们要覆盖上去的地址^这个值的结果盖上去即可 - 或者说我们只要知道堆地址即可
- 首先