UAF本质是堆块重用后的内容错位,需配合调试符号、禁用ASLR、观察fastbin链表及内存内容变化来定位,单纯寄存器或栈帧检查无法发现。

能排查,但必须配合堆调试技巧和内存状态观察,单纯用 print 或 info registers 看不到 UAF 的本质——它发生在堆块重用后的内容错位,不是寄存器或栈帧异常。
确认程序启用了调试符号并禁用堆随机化
UAF 是堆行为错误,gdb 要看到真实地址和 chunk 结构,得先让内存布局可预测:
- 编译时加
-g(保留符号),不加-O2(避免变量优化消失) - 运行前执行
setarch $(uname -m) -R ./a.out,关闭 ASLR;否则每次malloc地址跳变,无法复现 fastbin 复用 - 在
gdb里用set follow-fork-mode child,确保子进程也被跟踪(很多 UAF 出现在 fork 后的子进程中)
用 x/ 和 heap 命令观察 fastbin 链表变化
UAF 常依赖 fastbin 的 LIFO 特性:释放后指针不置零,再 malloc 同尺寸会原地复用。关键不是“有没有 free”,而是“free 后那块内存被谁填了”:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 在
free(p)后立刻执行x/4gx &p,确认p本身(指针变量)没变,还是原来地址 - 用
info proc mappings找到堆基址(比如0x804c000),然后x/20gx 0x804c000看 chunk header:已释放 chunk 的size字段末位为 1(表示 in-use=0),且fd指针指向下一个空闲 chunk - 如果用的是
libc2.23+,可加载libc6-dbg并用heap命令(需source /usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/libc.so.6-gdb.py)直接显示 fastbins 链表
在疑似 UAF 点下断点,检查内存内容是否已被覆盖
UAF 的崩溃往往延迟发生——比如 free(a) 后没出事,但后续调用 printf_note 时去读 a->func_ptr,结果读到的是别人写进去的 shellcode 地址:
- 在使用指针前下断点:
break *0x8048567(假设是call [eax]的地址),然后x/2i $eip确认指令 - 用
x/4xw $eax查看该指针指向的前 16 字节,对比之前malloc时写入的内容(比如结构体第一个字段是不是你预期的函数地址) - 若发现
$eax指向的内存里出现了意外的值(如0x08048620是magic地址,而原本该是0x080484a0的print_note_content),就是 UAF 已被利用的铁证 - 用
watch *(int*)0x804c040监视某 chunk 内存地址,看谁在改它(注意 watchpoint 会显著拖慢运行)
配合 Valgrind 或 AddressSanitizer 定位首次非法访问
gdb 擅长分析“已经发生的 UAF”,但很难告诉你“第一次访问已释放内存”在哪——因为那时程序可能还没崩溃:
- 用
valgrind --tool=memcheck --track-origins=yes ./a.out,它会在第一次free后又被读/写时直接报Invalid read of size 4,并给出调用栈 - 更推荐
gcc -fsanitize=address -g编译,ASan 会在 UAF 发生瞬间中断,并高亮显示被释放的 chunk 分配/释放位置,比gdb单步快十倍 - 注意:ASan 会重排内存布局,
gdb里看到的地址和 ASan 报错地址不一致,别混用;调试阶段优先用 ASan 定位,再切回gdb看汇编细节
真正难的不是发现 UAF,而是确认那个被复用的 chunk 是否恰好被你控制——比如 fastbin 复用时,你写入的 8 字节刚好覆盖了结构体里的函数指针,这种边界对齐和 size 选择,gdb 里多看两眼 x/20gx 输出就清楚了。

















