
第一次在CTF比赛里看到题目列表别人都在聊Web、Crypto、Misc我却连“Pwn”三个字母怎么读都拿不准。点击去是一堆看不懂的汇编、奇奇怪怪的十六进制还有一个让你“连上去打个什么东西”的端口。那股挫败感我想每一个初学者都体会过。后来自己啃资料、踩坑、看内存、写脚本才慢慢把pwn这个方向从“玄学”变成了一套有迹可循的方法论。这篇文字就是我当时最想看到的那篇“起手式”不指望一篇讲完所有漏洞利用只把pwn到底是什么、它要解决什么问题、动手之前要准备哪些知识一次性讲清楚。pwn这个词听起来神秘本质却非常朴素你拿到一个程序它并不打算把秘密告诉你但程序本身有缺陷你需要借用这个缺陷迫使它自己把秘密交出来。这个“迫使程序就范”的过程就是pwn的核心。它适合任何对计算机底层工作原理有好奇心的人哪怕你目前只会一点C语言或者只写过Python都不妨碍你从现在开始进入这个方向。这篇是系列第一篇我会尽量把概念、流程、知识地图和工具链一次讲透后续文章我们再逐个击破具体的漏洞类型。1. 到底什么是pwn从CTF分类到二进制漏洞利用1.1 pwn这个奇怪名字的来历pwn这个词最初是在CTF圈子里传开的。它其实是英文单词“own”的谐音“own”在黑客语境里有“彻底掌控、完全拿下”的意思。所以pwn的发音大致是“泡恩”读作/pəʊn/跟“own”差不多。很多刚接触的朋友会读成“骗”或者“普文”都不太对本质上也无关紧要大家听得懂就行。真正的关键不在读音而在含义。CTF里的pwn指的是这么一类题目给你一个编译好的二进制程序服务器上跑着它的一个实例你需要找到程序里的漏洞构造输入数据让程序执行你期望的操作最终读取服务器上的flag文件。这个flag通常是一串字符串格式类似flag{...}提交给比赛平台就能得分。所以pwn题和漏洞利用exploitation几乎是同义词。它不像Web题那样关注网络应用的逻辑漏洞也不像逆向题那样只要读懂程序逻辑就好pwn要求你不仅要理解程序在“源代码层面”做了什么更要理解它在“内存层面”到底怎么运行、数据怎么排布、函数怎么调用、系统怎么管理进程。这种对程序运行机制的深入掌控就是pwn最迷人的地方。1.2 一道pwn题在比赛里实际长什么样我描述一个最典型的场景。你下载了一个压缩包解压后得到一个文件名字通常叫pwn1或者srop之类的没有后缀名。你用file命令看它输出类似$ file pwn1 pwn1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, stripped这是一个64位的Linux可执行文件。比赛平台会把同样的文件跑在一台服务器上并给你一个地址和端口例如node1.xxx.com:30001。你用nc连上去$ nc node1.xxx.com 30001 Welcome to my pwn challenge! Please enter your name:程序在等你输入。正常的输入会被程序吞掉然后程序可能正常退出。但如果你输入一串精心构造的特殊数据程序就可能崩溃甚至执行你注入的指令输出flag的内容。这里要特别说一下很多第一次接触pwn的朋友会问既然服务器上跑着同样的程序我能不能直接让它把flag打印出来不行。flag文件在服务器上程序本身并不会主动去读它。你的目标是利用漏洞让程序执行“读取flag并输出”这个原本不存在于程序逻辑里的操作。这就是为什么叫“利用”——你是在借用程序自身的机制做一件它设计者没打算让它做的事情。1.3 pwn与CTF其他方向的关系CTF比赛通常分为Web、Reverse逆向、Pwn、Crypto密码学、Misc杂项几大类。它们之间的关系用一张表格看更清楚方向核心任务需要的主要技能Web利用网站应用层漏洞编程、HTTP协议、数据库、框架知识Reverse逆向分析程序逻辑还原算法汇编、反编译工具、密码学基础Pwn利用程序内存漏洞执行任意操作汇编、系统机制、调试器、漏洞利用Crypto破解密码算法或协议数学、算法、编程Misc五花八门的综合问题编码、隐写、流量分析等从这个表能看出pwn是一个高度综合的方向。你不只要会看汇编理解程序逻辑还要理解操作系统如何加载程序、如何管理内存、如何调用系统调用你还要会写脚本把一次复杂的漏洞利用过程自动化。反过来如果你想在二进制安全、漏洞挖掘、软件安全这些方向深耕pwn就是你绕不开的地基。很多著名的安全研究员最初都是从CTF的pwn题开始练手逐渐过渡到真实世界的漏洞研究。2. 学习pwn的真实价值我为什么建议你投入时间2.1 从“用电脑”到“懂电脑”的认知跃迁如果你只会写业务代码你的视角停留在函数、类、接口这一层程序怎么被加载进内存、变量怎么存放、函数调用时发生了什么对你来说是一个黑盒。学过pwn之后你会被迫打开这个黑盒栈是什么、堆是什么、寄存器怎么工作、ret指令做了什么、ASLR和NX这些保护机制又是怎么回事。这不是空泛的理论。有一次我debug一个C语言程序崩溃在free()调用上报错信息含含糊糊。如果是以前我大概率会满地打滚试错。但因为学过堆利用我立刻意识到是堆块元数据被破坏了通过gdb查看chunk头、检查double free的迹象很快就定位到问题。这种能力就是pwn学习过程附赠的“内功”。2.2 CTF中pwn是高区分度题目从功利一点的角度看pwn题在CTF比赛中通常是分值最高、也是解出人数最少的一类。一场比赛里Web题可能有三五十人解出pwn题可能只有个位数的人能碰。这就意味着如果你在pwn方向有一定积累你在团队中的不可替代性会非常高。很多战队的主力输出位都是pwn方向出身的选手。更实际的是pwn学习过程中的调试能力、底层理解力和代码阅读能力是安全行业面试中非常看重的素质。做漏洞挖掘、漏洞分析、安全研发甚至做DevOps排查线上崩溃问题pwn训练带来的底子都能直接迁移过去。这不是说学了pwn就一定能找到好工作而是它确实打开了一扇通往更深层技术领域的大门。2.3 学习曲线陡峭但坡是能爬上去的我不回避一个问题pwn的入门门槛确实比Web和Misc要高。它要求你同时掌握C语言、汇编、操作系统原理和Python脚本编写听起来像是四个方向的知识。但你要知道“掌握”和“熟练”是两回事。你不需要先把所有汇编指令背熟再开始学pwn你只需要理解最基本的指令、栈帧结构、内存布局就能看懂一道最简单的栈溢出题目了。更多的细节是在一遍遍做题和调试中自然补齐的。我见过不少零基础的朋友担心自己C语言都没学明白不敢碰pwn。我的建议是C语言的指针和数组概念大致清楚就足够了剩下的交给实战。你会在第一次溢出实践中以最直观的方式理解“缓冲区”“越界”“覆盖返回地址”这些概念——那种“啊原来是这样”的感觉是看书看十遍都换不来的。3. 一道pwn题从接触到解出的完整链路先建立整体感觉3.1 拿到题目之后第一步不是打开IDA很多新手拿到题目就直接拖进IDA狂看反汇编结果看半天不知道看什么。正确的打开方式应该是从前置检查开始。解压题目后先在终端里做三件事file pwn1 # 看文件类型是32位还是64位 checksec pwn1 # 看开启哪些安全保护 ./pwn1 # 本地运行一次看程序行为checksec是pwn入门必须习惯使用的工具。它会告诉你程序开启了哪些保护机制栈上Canary保护、NX不可执行栈、PIE地址随机化、RELRO重定位表只读等。这些保护会直接决定你后续使用哪条利用路径。打个比方这就像你准备进一栋楼首先要搞清楚哪几扇门锁了、哪几扇窗户封了、有没有摄像头——你总不会闭着眼睛就冲进去。本地运行一遍程序也很重要。程序会打印什么提示、读取什么输入、输入之后有什么反应这些行为信息在分析漏洞时往往能提供很强的线索。3.2 漏洞分析静态定位与动态验证的组合拳接下来才是逆向分析。用IDA或Ghidra这类反编译工具打开程序寻找危险函数。所谓“危险函数”是那些不做边界检查的输入函数gets、strcpy、sprintf、scanf等。它们会把用户输入直接写入缓冲区如果输入超过缓冲区长度就会覆盖掉相邻的内存数据这通常就是漏洞的起点。但静态反编译只是告诉你“可能存在漏洞”地不地道还要动态验证。这时候需要启动gdb配合pwndbg插件在可疑函数处下断点输入精心构造的数据观察程序执行过程中的寄存器变化、栈内容、返回地址是否被改写。这一步的价值在于把抽象的“漏洞”变成看得见摸得着的内存现象也让你确漏洞利用的精确偏移量——比如输入多少字节能覆盖到返回地址。3.3 写出exploit用pwntools把攻击过程自动化确认漏洞并计算出偏移之后接下来的工作是用Python的pwntools库编写漏洞利用脚本简称exp。一个最基础的exp框架是这样的from pwn import * # 本地调试process传入二进制文件路径 p process(./pwn1) # 远程利用换成比赛提供的地址和端口 # p remote(node1.xxx.com, 30001) payload ba * 24 # 垃圾数据填充到返回地址之前 payload p64(0x401186) # 覆盖返回地址改成我们想跳转的地址 p.sendline(payload) p.interactive() # 拿到shell之后进行交互p64()是pwntools提供的函数它会把一个整数打包成64位小端字节序的字节串。这里涉及一个重要的底层细节x86和x86-64体系使用小端字节序也就是说一个数值的最低有效字节存放在内存的最低地址处。手工拼字节序很容易出错用pwntools的p64/p32函数可以省去很多麻烦这也是为什么pwntools几乎是pwn选手的标配。exp写好后先在本机打一遍。本地拿到了shell再把进程替换成远程地址拿到flag。整个流程跑通题目就算解出来了。3.4 一个适合新手的类比如果用一个生活场景来类比pwn题的全过程我觉得最接近的是“复刻一道没有配方的菜”。程序就像一个做好的菜你能品尝它运行它观察行为能用各种手段分析它的成分逆向、调试但你不知道它精确的配方程序逻辑。你要做的事情是通过不断的品尝和实验推算出它用了哪些食材、多少分量、什么烹调顺序最后自己动手做出一模一样的菜来。甚至你还得学会“巧妙地下错一勺盐”让这道菜按你的预期出现某种风味——而那个“错误的一勺盐”就是你的payload。4. 前置知识盘点这五块基础学到“够用”就行4.1 C语言不需要精通但指针必须弄明白pwn题虽然大量工作是在看汇编但程序本身大部分是用C写的。懂得C语言你就能快速理解程序整体思路这个程序接收输入、调用某个函数、存在一个循环……这些逻辑用反汇编看也能看明白但效率低得多。在C语言的各种知识点中最核心的是指针和数组的内存语义。你要清楚数组名和指针并不是一回事char buf[64]在栈上分配64字节空间buf[i]等价于*(buf i)越界写入会污染相邻内存。理解这一点就理解了栈溢出漏洞的根源。函数调用的栈帧知识也很关键不过这一块放到汇编部分一起说。结构体、动态内存分配malloc/free可以后面用到再补。4.2 汇编语言入门先记二十条指令足矣提到汇编很多新手先被吓退了觉得是一堆天书。实际上pwn入门阶段需要用到的汇编指令非常有限。常用的数据传送mov、有效地址加载lea、算术运算add/sub、比较与跳转cmp/jne/je、栈操作push/pop、函数调用与返回call/ret、零扩展和符号扩展movzx/movsx再加上leave基本就覆盖了大部分题目中你会遇到的指令。比单个指令更重要的是函数调用的栈帧结构。简单说每一次函数调用系统会为这个函数在栈上分配一块区域存储局部变量同时保存返回地址和上一层函数的栈底指针。函数用call调用子函数时返回地址会被压栈子函数执行完retCPU会取出这个地址让程序流回到调用点继续执行。pwn最经典的一类利用方式就是把这个返回地址改成攻击者可控的值。x86-64下还有一套参数传递约定需要知道函数前六个整数或指针参数按顺序放在rdi、rsi、rdx、rcx、r8、r9寄存器里多余的参数压栈传递。掌握了这套ABI规则你在构造ROP链后续文章会细讲时就知道应该往哪个寄存器里放什么值。4.3 操作系统与Linux基础从进程视角理解程序运行pwn是跑在Linux环境下的掌握基本的Linux命令行操作是硬前提。你需要熟练使用ls、cd、cat、chmod、nc这些命令会管理Python虚拟环境会看进程和文件权限。这些建议在日常中积累不必专门去啃厚厚的系统管理书。系统知识方面重点是虚拟内存的概念和进程内存布局。现代操作系统会给每个进程一个独立的虚拟地址空间不同段落在不同的区间代码段.text存放指令、数据段.data/.bss存放全局变量、堆区heap存放动态分配的内存、栈区stack存放函数调用的局部变量还有一个区域保存着动态链接库的映射。程序加载到内存后这些区域的地址范围大概是多少哪些方向是增长的是入门阶段就要反复接触的内容。此外你还得知道动态链接的基本概念程序调用的printf、gets等函数并不在程序本身里而是在libc.so这个共享库里程序运行时通过PLT/GOT机制来解析并跳转到这些外部函数。有一部分pwn题的核心就是利用GOT表保存的地址泄露libc基址从而计算system函数的地址。4.4 Python编程只用写脚本不用写工程写exp最好的语言是Python准确地说是Python配合pwntools库。你需要会的Python并不多变量、列表、字典、循环、条件判断、函数以及bytes和str类型的区别。这里特别提醒初学者pwntools中大量的数据操作是字节串bytes而不是字符串str两者在Python3中不能直接拼接这是新手写exp最容易遇到的报错。你还需要一点网络编程概念客户端如何连接服务器、如何发送和接收数据。pwntools的remote()、sendline()、recvuntil()、interactive()这几个接口封装了底层细节但只要理解了TCP连接中“发送请求-等待响应”的交互模型用起来就不容易懵。4.5 知识准备到什么程度才不算“没准备好”我把上面几块知识的“最低可用标准”整理成一张表知识块最低标准建议投入时间C语言理解指针与数组、函数调用、结构体、字符串处理函数2-4周边学边用汇编熟悉常用指令、栈帧机制、x86-64传参约定1-2周集中理解操作系统进程内存布局、动态链接基本概念、虚拟内存1周概念性理解Linux命令常用文件操作、权限、nc连接、日志查看随用随查Python基础语法 bytes/str区别 简单socket交互1周会写脚本即可请注意这张表不是说全部学完才能开始做题。我的建议是C语言和Linux达到最低标准后就可以开始接触最简单的栈溢出题目了。汇编、Python这些在实战中边做边补效率远高于系统性学完再动手。5. 环境搭建与工具链选型把起点铺平5.1 操作系统选择一套虚拟机就够了pwn题通常在Linux下运行所以必须在Linux环境里操作。最省心的方案是装一个Ubuntu虚拟机。Ubuntu 20.04或22.04都可以软件源丰富、社区资料多遇到问题搜一下基本都有答案。Kali Linux也可以它预装了大量安全工具但很多工具pwn阶段用不到个人觉得不是必须。不推荐直接拿物理机装双系统原因是pwn你需要频繁调试、可能会把系统环境搞乱用快照功能可以在实验中途随时回滚。我用VMware或VirtualBox跑一个Ubuntu虚拟机分配4GB以上内存快照在装完基础环境时打一个后面折腾坏了随时恢复。5.2 核心工具清单三件套加若干辅助我把常用的工具按用途分类整理如下工具用途说明pwntools编写exp的Python库pwn选手的标配封装了连接、收发、字节打包等操作gdb pwndbg动态调试pwndbg插件极大改善gdb的显示效果pwn入门强烈推荐Ghidra / IDA静态反编译免费用Ghidra老手常用IDA二选一先学一个checksec安全检查查看程序保护机制现在通常由pwntools联动调用file / readelf / objdump二进制基础分析查看文件类型、ELF结构、汇编代码ROPgadget / one_gadget利用辅助后续接触ROP时再安装初期不必纠结其中pwntools的安装很简单一行命令pip3 install pwntoolspwndbg的安装也很流程化git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh装好之后在gdb里输入start如果看到带色彩高亮、能直观显示栈和寄存器的界面说明插件生效了。如果不生效检查一下是否在用户目录下覆盖了.gdbinit文件pwndbg的setup脚本会处理这些但偶尔会因为之前的gdb配置冲突导致失败。Ghidra是NSA开源的免费逆向工具图形界面反编译效果在pwn入门阶段完全够用。IDA是商业软件功能更强免费版只能看x86的32位程序看不了x64的反汇编。入门阶段我的建议是先用Ghidra等确实体会到它的局限了再考虑要不要找IDACrack版。不要一上来就在工具上花太多时间纠结。5.3 最容易忽略的一步libc版本的一致性问题环境搭建阶段有一个非常容易踩的坑你的Ubuntu自带glibc和比赛服务器的glibc版本不一致导致本地能利用成功、远程打不通。这个问题的根源是很多漏洞利用技术特别是下一阶段会学到的ret2libc依赖于libc中特定函数和字符串的偏移地址。不同版本的libcsystem函数、/bin/sh字符串、__libc_start_main的偏移都不一样。如果本地Ubuntu是glibc 2.35而比赛服务器是glibc 2.27你本地计算好的地址拿到远程就是错的。入门阶段处理方式优先选择题目平台自带的docker镜像用和题目相同libc版本的环境进行本地调试。这一块以后我会单独写一篇详细说明现在就记住一句话本地能通不必然等于远程能通环境一致性是排查问题的第一优先级。5.4 一条命令搭好基础环境参考如果你刚装好Ubuntu虚拟机可以参考下面的步骤快速完成基础准备# 更新软件源并安装基础工具 sudo apt update sudo apt install -y python3 python3-pip gdb binutils file netcat-traditional # 安装pwntools pip3 install pwntools # 安装pwndbg git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh # 安装Ghidra可以官网下载也可以直接解压到指定目录装好后验证一下python3 -c from pwn import *; print(pwntools ok) gdb -q ./test_binary顺手给自己打一个虚拟机快照。这一步的意义在于后面你装了一堆工具、可能改乱了环境随时可以一键恢复到现在这个干净好用的状态。6. 新手最容易踩的坑我的经验教训与路线建议6.1 我见过的几个典型误区带过不少朋友入坑也看过很多新手的踩坑过程有几个问题出现频率特别高这里一次性说透。第一个误区是不看保护机制就直接写exp。新手下载题目后打开IDA分析半天好不容易找到一个溢出点兴冲冲地写payload往返回地址塞shellcode的地址结果发现NX保护开启栈根本不可执行程序直接段错误。每次看到这种情况我都想说动手分析之前花十秒钟跑一下checksec看清NX、Canary、PIE分别开没开能省掉半天的无效劳动。第二个误区是只做静态分析完全不用调试器。看反汇编能理解逻辑但程序实际运行时栈指针怎么移动、输入的数据覆盖到哪里、偏移到底是多少这些信息必须通过gdb的断点和观察才能准确掌握。静态分析是地图动态调试是实地测量两者缺一不可。第三个误区是本地通了就以为自己赢了直接打远程结果一打一个不吭声。我刚才提过libc版本和环境差异还有一个很常见的原因是exp里写死了本地进程路径或者使用了本地方便但远程不适用的交互方式。正确习惯是写exp时把本地和远程的切换写清楚远程打不通先回来看报错类型再逐项排查是接收不完整还是地址错误。第四个误区更隐性一上来就想学堆利用。有些朋友知道pwn有堆、栈、格式化字符串几个方向直接就要啃堆利用结果被堆结构、bin链表、double free这些概念搞到自闭。我的建议是栈溢出没玩熟之前不碰堆。栈是利用的基础也是建立“内存覆盖”直觉最快的路径。栈系列稳了再往堆那边走。6.2 一条我验证过的学习路线如果是零基础入门我建议按这个顺序推进每阶段配合对应的练习题阶段主题你要掌握的关键点第一步ret2text最基本的栈溢出覆盖返回地址跳到程序已有的后门函数第二步ret2shellcodeNX关闭时的shellcode注入理解栈的可执行性第三步ret2libc利用PLT/GOT泄露libc地址计算system和/bin/sh的偏移第四步格式化字符串利用printf的格式字符串漏洞实现任意读写第五步堆利用入门堆的chunk结构、fastbin、double free等基础利用方式每完成一个阶段找题目平台上对应难度的题去练手。国内有ctfshow等平台上面有从简单到困难的pwn题序列适合每个阶段结束后做强化验证。做题这件事上我的经验是宁可一道题做三天也不要一天换三题。一道题深入调试、彻底搞懂比浅尝辄止地刷十道题有价值得多。6.3 给初学者的一句话pwn入门这件事说难也难说简单也简单。难在它需要你打破沙锅问到底每一个“为什么”都要在内存层面找到答案简单在它的路径足够清晰一个漏洞类型一个漏洞类型地啃一片一篇地积累回头看时会发现自己已经走了很远。很多人不是学不会pwn而是在开始之前就被“这肯定很难”的想象劝退了。我至今还记得自己用第一次溢出成功getshell的那个深夜屏幕上跳出$提示符的时候整个人从椅子上跳起来。那种把一个看似坚不可摧的程序玩弄于股掌之间的快感是支撑我一路走下去的最大动力。希望你也能在踩过几次坑、掉过几次头发之后体会到这种妙不可言的感觉。下一篇文章我会从经典的栈溢出讲起手把手带着你搭好第一道题的exp框架把这条路上的坑挨个踩平。