UAF/use after free
UAF 的本质是复用未清零的指针,使已经释放到 bin 当中的堆块内容可以被修改或者打印出来,依据泄露的 fd 和 bk 指针或者在堆中布置 system(‘/bin/sh’);去调用获取 shell
UAF的概述
UAF 是 Use After Free 的缩写,中文叫 “释放后使用”,是堆漏洞利用的重要方式
当我们 free 适当大小的chunk时,我们 free 掉的chunk会先被放入bins,在我们再次申请适合大小的chunk 时,系统会有限从我们的 bins 中取出之前free掉的chunk返回给我们,我们此时会有两种情况
- 1)free后空间置0
- 2)free后空间不置0
简单来说,就是:程序在释放了一块内存之后,又去访问 / 使用这块已经被释放的内存空间。__
你可以把内存想象成酒店房间:
- 你(程序)入住了一个房间(内存),拿到钥匙(指针);
- 你退房(free/delete),房间归还给酒店(操作系统),此时酒店管理员并没有回收你的钥匙;
- 之后你还拿着这间房子得钥匙,之后又去开这个房间的门(访问指针)
此时这个房间可能已经分配给别人(其他程序 / 变量),当你去访问/修改,就会导致不可预知的后果。
在程序中,UAF常有以下几种情况:
- 内存块被释放后,其对应的指针被设置为 NULL,然后再次使用,自然程序会崩溃。
- **内存块被释放后,其对应的指针没有被设置为 NULL **,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
- **内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
**而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
Dangling Pointer
悬空指针是计算机编程中的一个常见且危险的问题,它指的是一个指针仍然保留着之前指向的内存地址,但是这片内存区域可能已经被释放或者不再有效,从而可能导致程序在使用该指针时出现未定义行为。
作为堆入门,我们先来了解一部分 gdb 的使用命令:
heap 命令
这个命令是 pwndbg 中查看堆内存布局的核心工具,能帮你直观看到 chunk 的分配、释放、大小、状态等关键信息。先分配 / 释放堆块,之后可以用 heap 命令去看堆分布的情况
输出示例(以 fastbin 为例):
1 | All heap chunks |
堆结构:
例如某fastbin地址位0x804b000,
那么 0x0000000就是addr,0x00000011就是size
1 | 0x804865b就是fd,0x804b018就是bk |

bins 命令
glibc 的堆管理器会把释放的空闲 chunk 按大小分类存到不同的 bin 中,bins 命令就是专门列出这些 bin 的详细信息
1 | # 在 pwndbg 中直接输入,查看所有类型 bin 的完整信息 |
1 | Fastbins |
例)actf_2019_babyheap(uaf堆复用)

1 | void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) |



我们发现在delete中,free之后并没有置0,这很有可能存在UAF漏洞

反汇编核心代码解析
if( n5>=0 && n5 < ::ns )
“要检查的这个 chunk 指针(n5)不是空 / 负地址,并且这个 chunk 指针的内存地址,小于 0x70 尺寸 fastbin 链表头的全局地址” → 本质是验证 chunk 指针是否在堆内存的合法范围,而非 libc 内部的全局数据区。c
1 | if ( *(&ptr + n5) ) |
| add | 先申请 0x10 chunk(记为 chunkA)→ 再申请 size 大小 chunk(记为 chunkB);chunkA 前 8 字节存 chunkB 地址,后 8 字节存 print 函数指针 |
|---|---|
| delete | 存在 UAF 漏洞:free chunkA/chunkB 后未清空指针,指针仍指向已释放的堆块 |
| show | 执行 chunkA 后 8 字节的函数指针,参数为 chunkA 前 8 字节的 chunkB 地址 |
(((&ptr + n5) + 1))((&ptr + n5));c**
1 | if ( *(&ptr + n5) ) // 判断堆块指针是否非空 |
C 语言中,&ptr + n5 等价于 &ptr[n5],*(&ptr + n5) 等价于 ptr[n5](数组下标本质是指针偏移的语法糖)。先把代码替换为等价写法:
1 | if (ptr[n5]) // 条件判断:ptr数组第n5个元素是否非空 |
1 | (*(ptr[n5] + 1))(*ptr[n5]) |
解题思路:
delect函数中存在明显的uaf漏洞,同时add函数中又是先申请一个大小为0x10的chunk,然后再申请大小为size的chunk,且show函数是通过执行大小为0x10的chunk的后八位存储的函数来打印chunk中的内容的,于是我们可以利用fastbin的性质来达成篡改大小为0x10的chunk的后八位进而达成执行system(‘/bin/sh’)
首先我们申请两个chunk,然后将这两个chunkfree掉,这样我们就有两个大小为0x10的fastbin了,此时再申请一个大小为0x10的chunk即可
exp:UAF
1 | from pwn import * |
通过 add 分配两个 0x10 大小的 chunkA,用 delete 释放(利用 UAF 保留指针),让它们进入 fastbin;再通过 add 复用 fastbin 中的堆块,将 chunkA 的 “数据指针” 改为 /bin/sh 地址、“函数指针” 改为 system 地址;最后调用 show 执行函数指针,触发 system(“/bin/sh”) 获取 shell。
更新: 2026-04-23 14:09:23
原文: https://www.yuque.com/idcm/wnemg9/aqvnkv3oxnxgodvv