手把手带你搞懂堆利用套路
Poseidon 发表于 湖北 二进制安全 249浏览 · 2025-05-20 06:56

前言

堆利用一直是 CTF 和安全研究中最具挑战性、同时也最具魅力的方向之一。相较于传统的栈溢出,堆的利用手法更加多样,依赖内存分配器的内部机制,攻击思路往往更具“艺术性”。

我在学习堆利用的过程中,发现很多攻击技术彼此之间既独立又紧密相关,理解一个点常常需要对 glibc 的堆实现有一定的了解。因此,我决定把这些常见的堆利用方式——包括 UAF、fastbin dup 系列(如 into stack、consolidate、reverse into tcache)以及 unsafe unlink 等——整理成这篇笔记,一方面帮助自己梳理知识,另一方面也希望对刚入门或正在深入的同学有所帮助。

这篇文章默认你已经掌握了基本的 C 语言指针操作、glibc 的 malloc/free 使用,以及一些基础的内存漏洞概念。如果你也在学习堆利用,或者正在做相关的 CTF 题,希望这篇内容能为你带来一点启发。

UAF

free之后指针没有清零,结构体内存在函数指针。

简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况

内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。

内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转

内存块被释放后,其对应的指针没有被设置为NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题

而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为NULL的内存指针为dangling pointer。

first_fit

glibc 使用一种first-fit算法去选择一个free-chunk。如果存在一个free-chunk并且足够大(绕过fastbin)的话,malloc会优先选取这个chunk。这种机制就可以在被利用于use after free(简称 uaf) 的情形中.

UAF 漏洞简单来说就是第一次申请的内存释放之后,没有进行内存回收,下次申请的时候还能申请到这一块内存,导致我们可以用以前的内存指针来访问修改过的内存。

程序展示了一个 glibc 堆分配策略,first-fit。在分配内存时,malloc 先到 unsorted bin(或者 fastbins)中查找适合的被 free 的 chunk,如果没有,就会把 unsorted bin 中的所有 chunk 分别放入到所属的 bins 中,然后再去这些 bins 里去寻找适合的 chunk。可以看到第三次 malloc 的地址和第一次相同,即 malloc 找到了第一次 free 掉的 chunk,并把它重新分配。

fastbin_dup

fast bins为单链表存储。fast bins的存储采用后进先出(LIFO)的原则:后free的chunk会被添加到先free的chunk的后面;同理,通过malloc取出chunk时是先去取最新放进去的。free的时候如果是fast bin,就会检查链表顶是不是要释放的chunk_ptr。所以只要链表顶不是该chunk,就可以继续free,从而实现double free。

ptmalloc管理机制中,tcache的优先级是高于fastbin的,但是通过calloc函数申请的内存是跳过tcache的。fastbin dup技巧就是绕过tcache实现2.23版本下面的double free操作。

fastbin_dup_into_stack

通过欺骗malloc 来返回一个我们可控的区域的指针 。

fastbin_dup_consolidate

在分配large chunk的时候,首先会根据chunk的大小来获取对应的 large bin的index,然后判断fast bins中有没有chunk,如果有就调用 malloc_consolidate()合并fast bins中的chunk,然后放到unsorted bin 中。unsorted bin中的chunk 会按照大小放到small或large bins中。

fast_bin_reverse_into_tcache

原理

修改fastbin 释放的chunk的fd指针,指向伪造的chunk地址,实现任意地址覆盖。在从fast bin中malloc的时候取出一个chunk,会将剩余的chunk放回到tcahce中。而fd指针已经修改为fake_chunk_addr,所以fake_chunk也会进入tcache bin的尾部,再次malloc的时候就会申请出来。

有一点需要注意的是,放入 tcache bin的条件是tcache bin有空余,且fastbin取出后也有剩余。后者的判断方法是取出表头的fd指针指向的下一个chunk,判断是否为空。也就是从头部开始取的,再使用头插法插入 tcache bin。这样的话,排入tcache bin 后chunks的顺序就是与其在fastbin中是相反的,所以叫reverse。

unsafe_unlink

概述

双向链表中移除/添加一个chunk时,会发生断链的操作,这个断链的过程就叫做unlink。

注意事项:unlink不发生在fastbin和smallbin中,所以fastbin和smallbin容易产生漏洞。我们一般是通过已知的全局变量伪造一个已经free的chunk。

chunk在free的时候会进行合并空闲chunk的操作,有向前和向后两种。我们在事先分配的一个chunk中伪造一个空闲chunk——通过修改prev_inuse位来改变prev chunk的状态,再修改fd和bk指针绕过检查,这样高地址的chunk在free的时候就会认为prev chunk是空闲的,从而合并它。合并之后,p的指针会变为p-0x18。

image.png


chunk结构图

inuse chunk和free chunk的结构

image.png


malloc后返回的地址指向的是不加0x10(10进制的16,即2*sizeof(size_t))的头部数据的地址,而chunks真实的ptr是包含头部数据的地址,即fast bins等中fd指针(或者其他bins中的bk指针)指向malloc_ptr-0x10。

出现场景

malloc从恰好大小合适的largebin中获取chunk,从比malloc要求大的largebin中取chunk。

malloc_consolidate()函数用于将 fast bins 中的 chunk 与其物理相邻的chunk合并,并加入 unsorted bin 中。分为高地址(除top chunk)合并和低地址合并。

realloc向前扩展,合并物理相邻高地址空闲chunk。

freefree之后,与前后空闲的chunk进行合并。

如果chunk不是 mmap生成的,并且物理相邻的前一个或者下一个chunk处于空闲状态,就需要进行合并。同样分为高地址(除top chunk)合并和低地址合并两种。将合并后的 chunk 加入 unsorted bin 的双向循环链表中。如果合并后的 chunk 属于 large bins,将 chunk 的 fd_nextsize 和 bk_nextsize 设置为 NULL,因为在unsorted bin 中这两个字段无用。

tcahe

tcache_poisoning

原理:修改tcache bin 中chunk的next指针,使其被覆盖为任意地址。

概述

tcache 是 glibc 2.26 (ubuntu 17.10) 之后引入的一种技术(see commit),目的是提升堆管理的性能。但提升性能的同时舍弃了很多安全检查,也因此有了很多新的利用方式。

结构体

tcache 引入了两个新的结构体,tcache_entrytcache_perthread_struct

这其实和 fastbin 很像,但又不一样。

tcache_entry

tcache_entry 用于链接空闲的 chunk 结构体,其中的 next 指针指向下一个大小相同的 chunk。

需要注意的是这里的 next 指向 chunk 的 user data,而 fastbin 的 fd 指向 chunk 开头的地址。

而且,tcache_entry 会复用空闲 chunk 的 user data 部分。

tcache_perthread_struct

每个 thread 都会维护一个 tcache_perthread_struct,它是整个 tcache 的管理结构,一共有 TCACHE_MAX_BINS 个计数器和 TCACHE_MAX_BINS项 tcache_entry,其中

tcache_entry 用单向链表的方式链接了相同大小的处于空闲状态(free 后)的 chunk,这一点上和 fastbin 很像。

counts 记录了 tcache_entry 链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk。

image.png


tcache_stashing_unlink_attack

原理

malloc遍历unsorted bin找合适chunk的时候,如果不是恰好合适的大小,就会将其放入对应的small bin或者large bin。如果大小是small bin中的chunk,头插法插入对应链表。

calloc并不会首先从tcache bin中取chunk,而是遍历fast bin、small bin、large bin这些。

从small bin中取出一个chunk后,如果tcache bin有空余,会向剩余位置链入small bin中剩下的chunk。但是只检查了尾部一个的bk指针,并没有全部检查。

参考链接:

https://github.com/shellphish/how2heap/blob/master/glibc_2.35/tcache_poisoning.c

https://wiki.wgpsec.org/knowledge/ctf/how2heap.html

https://bbs.kanxue.com/thread-272416.htm#msg_header_h2_7

1 条评论
某人
表情
可输入 255