用gdb定位buffer overflow崩溃点:先加-g -fno-stack-protector编译,启动gdb后在疑似函数设断点,运行并用p &buffer和x/16xb查看内存,崩溃后立即bt;若触发stack smashing,可用watch $rbp-8捕获canary被改瞬间,并结合x/4wx $rbp检查返回地址是否被覆盖为用户数据。

怎么用GDB定位buffer overflow的崩溃点
直接运行崩溃程序后在GDB里看backtrace,基本就能锁定溢出发生在哪个函数、哪行代码。关键不是“能不能跑起来”,而是“崩溃时栈帧是否还完整”——如果__stack_chk_fail被触发,说明canary已被破坏,但函数返回前的栈布局通常尚可分析。
常见错误现象:Segmentation fault 或 *** stack smashing detected *** 后直接退出,没打印调用栈;这时必须加-g重新编译,并确保没开-O2等优化(优化会打乱变量布局,让buffer地址难追踪)。
- 编译命令必须带
-g -fno-stack-protector(禁用canary)或-fstack-protector(保留canary用于观察) - 启动GDB后先
b func(在疑似溢出函数入口设断点),再r运行,输入触发数据 - 停住后用
p &buffer拿到缓冲区起始地址,再用x/16xb &buffer查看原始内存内容 - 继续
c直到崩溃,立刻执行backtrace,对比两次$rsp和$rbp值是否异常跳变
为什么x/4wx $rbp比info registers更有用
因为buffer overflow的本质是往栈上写超了,覆盖了保存的%rbp或返回地址。只看寄存器快照抓不到“被覆盖的过程”,但用x/4wx $rbp能直观看到$rbp下方8字节(x86_64下返回地址位置)是否已被污染成非地址值(比如全是0x90909090或用户输入的ASCII码)。
使用场景:你在strcpy(buffer, str)后立刻执行x/4wx $rbp,发现$rbp+8处的值变成0x61616161(即"aaaa"),就确认str至少写了4字节进返回地址区域。
-
x/4wx $rbp显示的是[%rbp]开始连续4个4字节单元(共16字节),其中$rbp+8就是返回地址存储位 - 若用
x/8wx $rbp-16,能同时看到buffer末尾、saved %rbp、return address三段,更利于判断溢出长度 - 注意32位和64位下偏移不同:x86_64中返回地址在
$rbp+8,x86中在$ebp+4
如何用watch捕获canary被修改的瞬间
当程序报__stack_chk_fail但你不知道谁动了canary时,watch是唯一能精准卡住“第一现场”的方法。canary在函数prologue里从%fs:0x28读出并存到$rbp-8(x86_64),所以观察这个地址即可。
容易踩的坑:用watch *(char*)$rbp-8会失败——GDB不支持寄存器参与地址计算的动态表达式;必须先算出具体地址再下观察点。
- 先在函数内断住,执行
p/x $rbp - 0x8得到canary地址(如0x7fffffffe4a8) - 再执行
watch *(char*)0x7fffffffe4a8(硬件观察点,开销小) - 然后
c,GDB会在任意指令向该地址写入时中断,x/i $rip就能看到是哪条mov或strcpy干的 - 若提示“Could not insert hardware watchpoint”,说明目标地址不在可写内存页,换用
watch *(char*)0x7fffffffe4a8 - 1扩大范围再试
为什么disass func要配合x/2i $rip看汇编
源码里一行strcpy(buffer, str)实际对应多条汇编指令,而溢出可能发生在其中某条内存写操作上(比如mov %rax,(%rdx))。只看disass输出无法知道当前执行到哪一指令,必须结合$rip实时定位。
性能影响:x/2i $rip只反汇编当前指令和下一条,比disass全函数快得多,适合单步时快速确认上下文。
- 在
strcpy调用前断住,用disass strcpy确认它是否是glibc的优化版本(如__strcpy_sse2),这会影响溢出行为 - 单步进入
strcpy后,每按一次n就执行x/2i $rip,观察%rdi(dst)和%rsi(src)寄存器值是否开始越界 - 若看到
mov %rsi,%rdi之后mov (%rsi),%al再mov %al,(%rdi)循环,说明正逐字节拷贝,此时info reg rsi rdi能帮你预判第几轮会溢出
真实调试中,最易被忽略的是栈地址每次运行都变(ASLR开启时),而很多人记下第一次p &buffer的地址后,第二次就直接x/16xb 0x7fffffffe424硬查——结果什么也看不到。必须每次重新p &buffer,或干脆用watch绕过地址不确定性。











