十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

零基础入门CTF PWN:从栈溢出到ROP的完整学习路线

零基础入门CTF PWN:从栈溢出到ROP的完整学习路线 CTF 里的 PWN 方向向来是新手劝退率最高的一个方向。我收到过太多私信开头基本都是“老师我没学过汇编能不能学 PWN”“栈溢出到底要掌握到什么程度才行”。说实话PWN 确实有门槛但它真正劝退人的地方不是知识点太难而是市面上的资料要么一上来就堆 ROP 链要么把工具当黑盒用看完之后你依然不知道一道题从哪下手。所以我花了不少时间把自己从零学 PWN、刷了几百道题的经验重新梳理了一遍录成了这套 35 集的 CTF-PWN 教程目标只有一个让完全零基础的小白也能顺着一条清晰的路线把 PWN 学明白而不是看了一堆视频之后依然对着题目干瞪眼。这套教程整体走的是“理论够用即可、实操必须跟得上”的路线。学完之后你能独立完成 BUUCTF 上绝大多数简单题和中等题能看懂别人写的 writeup能自己写 exp 打通远程容器。接下来我就把这套教程的设计思路、核心知识点、实操流程和学习过程中最容易踩的坑全部摊开聊一聊。1. 内容整体设计与思路拆解1.1 市面上的教程为什么学不进去三个常见痛点我在筹备这套课程之前把当时能找到的 PWN 入门资料几乎都翻了一遍最后发现所谓“入门难”其实集中在三个问题上。第一个痛点是知识断层。很多教程默认你已经懂 Linux、懂汇编、懂 C 语言的内存模型结果新手在第 3 分钟就开始听不懂了。可实际上大部分小白连 ELF 文件是什么都不清楚更不用说栈帧、GOT、PLT 这些概念。第二个痛点是“重原理、轻实操”。视频里把栈溢出原理讲得头头是道一集四十分钟结果你自己拿到题的时候连 checksec 都不知道要敲什么。第三个痛点是缺少一条递进的学习路径知识点之间是散的学了格式化字符串不知道它跟栈溢出有什么关系学了 ret2text 不知道下一步该学什么。这套教程的出发点就是解决这三件事把前置知识补齐、把每一步操作落在键盘上、把知识点串联成一条可递进的路线。整门课我刻意避免“学术式”讲法所有原理都用最直白的方式说透配合实际运行、实际调试的画面让你亲眼看到栈是怎么被溢出的、返回地址是怎么被改写的。1.2 35 集的整体规划五个阶段层层递进整套 35 集被我分成了五个阶段每个阶段解决一类问题。下面这张表就是整套课程的骨架你可以把它当成 PWN 学习的路线图。阶段集数范围核心目标主要内容第一阶段第 1-4 集认识 CTF 与 PWN搭好环境PWN 是什么、CTF 比赛规则、Ubuntu 环境搭建、必备工具链安装第二阶段第 5-8 集补齐二进制基础ELF 文件结构、x86/x64 汇编入门、函数调用约定、GDB 调试基础第三阶段第 9-16 集掌握核心漏洞原理栈溢出、ROP 基础、ret2text/ret2shellcode/ret2libc、格式化字符串、整型溢出第四阶段第 17-24 集突破保护机制NX、PIE、Canary、RELRO 的原理与绕过方法逐步升级题目难度第五阶段第 25-35 集真题实战与综合提升BUUCTF 精选题目实战、动态容器远程攻防、比赛技巧、AI 辅助逆向分析这样安排的逻辑其实很简单先做工具再做原理最后玩综合。前三集你只需要跟着我把环境跑起来后面每集都有上一个阶段的延续。比如第三阶段讲栈溢出的时候用的还是第二阶段学过的 GDB 和汇编知识第四阶段讲 Canary 绕过的时候又依赖第三阶段对栈布局的理解。这种螺旋上升的结构能让每个新知识都长在旧知识上而不是悬在空中。1.3 这套教程和其他教程最大的区别我自己作为观众最反感的就是“讲课一时爽上手全抓瞎”。所以这套课我给自己定了三条硬规矩。第一每一集必须配一个能跑通的例题。这些题大多选自 BUUCTF 等公开平台你照着视频操作就能看到实际效果。第二所有命令、脚本都会完整展示甚至 exp 里的每一行都会有注释说明为什么要这么写。第三视频里会刻意演示一些“错误操作”——比如故意把 offset 算错、忘记注意字节序然后让观众看程序实际的报错和异常表现。我认为踩坑本身是最好的老师提前把常见的坑讲清楚比事后给你一百条注意事项管用得多。另外这套课的最后一集我还专门讲了一个比较前沿的话题怎么用 AI 辅助 PWN 题目分析。并不是说让 AI 直接帮你写 exp而是利用大模型快速理解反汇编代码、生成注释、减少重复劳动。现在 CTF 比赛里 AI 相关题目出现频率越来越高会合理利用工具本身也是一种能力。2. 核心知识点解析与实操要点2.1 认识四大保护机制checksec 输出的每个字母都别跳过很多新手拿到题目第一件事就是拖进 IDA 看伪代码这个习惯其实不够稳妥。拿到任何 PWN 题第一件事必须是运行 checksec 查看保护机制。你可以理解为你都不知道这栋房子的门锁是什么型号就开始撬锁肯定要翻车。四大保护机制需要记牢它们决定了你后面所有利用手法的选择。保护名称作用对利用的影响常见绕过思路NX栈不可执行不能直接在栈上执行 shellcode改用 ROP、ret2libc 或 mprotectPIE程序地址随机化函数地址每次加载都会变先泄露地址或利用非 PIE 段中的 gadgetCanary栈溢出检测溢出返回地址前会先碰到随机值触发即崩溃泄露 canary、逐字节爆破、FSOP 等高级手法RELROGOT 表保护部分/全部禁止修改 GOT选择 ret2dlresolve、修改函数指针等在教程里我不仅会讲每个机制的原理更会现场演示分别关掉这些保护编译同一个程序对比相同漏洞在不同保护下的利用难度。只有亲眼看过“没有 canary 时直接覆盖返回地址很顺畅开了 canary 一碰就段错误”你才能理解为什么 checksec 的结果那么重要。2.2 栈溢出原理从内存布局到劫持返回地址栈溢出是 PWN 的“基本功”也是整套课最核心的基石。我习惯用一个比方来解释栈就像你往一个盘架子上摞盘子每一个函数调用就是一摞盘子的一个盘子盘子里存着局部变量还有一张小纸条写着“这个盘子用完之后回哪儿去”这张小纸条就是返回地址。如果你往盘子里塞的东西超过了盘子容量多出来的部分就会溢到旁边的盘子上甚至把那张小纸条给盖住。C 语言里的 gets、strcpy 这类不安全的函数就是最容易“塞过头”的入口。比如下面这段代码void func() { char buf[0x20]; gets(buf); }输入的内容会从 buf 的起始地址开始往高地址方向写一旦超过 0x20 字节就会碰到栈上保存的返回地址。如果我们把返回地址覆盖成后门函数的地址那么 func 返回的时候程序就会“走错门”跳到我们指定的位置去执行。控制程序执行流就是 PWN 的核心目标。教程里我会带你用 GDB 实际查看溢出前后的栈变化用 cyclic 工具精确计算偏移量这样你就不需要死记硬背 offset 是怎么算出来的而是从原理上理解它。2.3 第一个必须写熟的利用脚本pwntools 核心用法PWN 题的“答题卡”就是 pwntools 脚本它是所有 CTF 选手最常用的 Python 库。新手刚开始不需要学很多把几个关键函数用熟就够了。from pwn import * context(oslinux, archamd64, log_leveldebug) # 本地调试用 process p process(./pwn) # 打远程题目时改用 remote # p remote(node.buuoj.cn, 12345) payload ba * (0x20 0x8) p64(0x401176) p.sendline(payload) p.interactive()这段代码里有几个细节必须重点强调。第一p64() 表示把整数按 64 位小端序打包成字节串x64 程序地址都是 8 字节对齐offset 之后必须用 8 字节数据去覆盖返回地址这是新手最容易出错的地方。第二sendline 和 send 的区别在于前者会自动补一个换行符gets 读到换行才会返回所以用 sendline 往往更符合题目逻辑。第三context 里的 arch 一定要跟题目架构一致32 位程序用 i38664 位程序用 amd64否则地址打包的长度就不对。2.4 三类必学题型ret2text、ret2shellcode、ret2libc当你能熟练控制返回地址之后接下来的问题就是往哪跳这就引出了三道最经典的入门题型。ret2text 最简单程序里本身就藏着一个后门函数你的任务就是找到它的地址并跳过去。这题练的是你的基本功找漏洞、算 offset、写 exp。ret2shellcode 稍微进阶程序没有现成的后门但你可以往可执行的内存里写一段 shellcode然后跳过去执行。这道题能帮你理解栈的可执行属性与 NX 保护的关系。ret2libc 则是真正的分水岭它要求你在 NX 开启的情况下调用系统库里的 system 函数拿到 shell。从这题开始你就正式进入“没有现成答案、需要自己拼利用链”的阶段了。这三类题在教程里各占了不少篇幅每道题我都会带着你从 checksec 开始一步步走到通掉远程容器。你可以把这三种题型当成 PWN 的“三板斧”先把它们练熟后面再学更高级的堆利用就会轻松很多。3. 实操过程与核心环节实现3.1 新手环境搭建5分钟跑通 Ubuntu pwntools gdb如果你想认真学 PWN我建议不要用 Windows 直接做题标准做法还是准备一个 Linux 环境。最省事的方式是装虚拟机或者用 WSL 也行我教程里用的是带图形界面的 Ubuntu 22.04新手用起来最省心。环境搭建主要分三步。第一步先更新系统并安装 Python3 和 pipsudo apt update sudo apt install python3 python3-pip gdb -y第二步安装 pwntools 和 gdb 增强插件。pwntools 是 PWN 的“瑞士军刀”一定要装好sudo pip3 install pwntoolsgdb 装了但默认界面太简陋建议装一个 pwndbg 插件。它会自动识别栈上的信息、标出返回地址位置调试 PWN 题会直观很多。第三步检查工具是否就绪。你可以在终端输入checksec如果能看到帮助信息说明 pwntools 已经安装成功再输入gdb -q进入调试器如果用 pwndbg 启动时能看到它加载环境就基本没问题了。另外逆向分析用的 IDA 或者 Ghidra 也需要提前准备教程里默认你已经有静态分析能力实在没有先用 Ghidra 顶上也行。3.2 手把手打通一道最简单的 PWN 题这里我挑一个最经典的入门题型带你把整条流程走一遍。假设题目给了一个叫 pwn 的可执行文件源码大概长这样#include stdio.h #include stdlib.h void win() { system(/bin/sh); } void vuln() { char buf[0x20]; gets(buf); } int main() { vuln(); return 0; }用 checksec 检查保护机制checksec --file./pwn大概率会看到一个没有 PIE、没有 Canary 的结果这就意味着我们可以直接把返回地址覆盖成 win 函数的地址。接着用 IDA 或者 Ghidra 打开程序找到 win 函数的地址。这里假设 win 的地址是 0x401176。下一步就是计算偏移量buf 缓冲区大小是 0x20再加上栈上保存的旧 rbp 占 8 字节所以返回地址距离 buf 起始位置正好是 0x28 字节。你要是不放心可以用 pwntools 的 cyclic 生成一串随机序列输入程序再用 GDB 看崩溃时的返回地址用cyclic -l反查偏移量两种方法得出的结果应该一致。最终 exp 脚本from pwn import * context(oslinux, archamd64, log_leveldebug) # 本地测试开启核心转储便于调试 p process(./pwn) payload ba * (0x20 0x8) p64(0x401176) p.sendline(payload) p.interactive()本地如果已经拿到了 shell说明 EXP 逻辑正确这时候再把它改成打远程题目。很多 CTF 平台上的 PWN 题都是通过动态容器分配的每个用户会得到一个独立的容器和对应的端口比如题目说明里写着你的服务地址是node.buuoj.cn:27001那么脚本里就把 process 换成 remotep remote(node.buuoj.cn, 27001) payload ... p.sendline(payload) p.interactive()3.3 动态容器环境下调试的三大注意事项第一次打远程题最容易出现“本地通了、远程就崩”的情况这一节必须单独拿出来讲。第一个注意点是端口会变。很多平台的动态容器在比赛期间或重新开启后会分配不同的端口别人 writeup 里的端口不是一成不变的必须看你自己当前环境里显示的实际地址端口。第二个注意点是程序运行环境可能不同。本地是 Ubuntu远端容器可能是 Debianlibc 版本不一致会直接导致 ret2libc 攻击里的地址对不上。这也是为什么我强烈建议新手先把 ret2text、ret2shellcode 这类不依赖 libc 的题目打熟练再进入 ret2libc 的领域。第三个注意点是输入输出缓冲问题。本地终端里你发送 payload 之后马上就能看到结果但远程程序如果用了setvbuf或者没有刷新缓冲区输出顺序可能和本地不太一样。处理方法是多观察程序的交互逻辑必要时在脚本里加sleep(1)或者改进交互代码。4. 常见问题与排查技巧实录4.1 新手最容易踩的五个坑我在带新人的过程中发现很多问题其实是重复出现的。下面这张表列出的五个坑几乎每个 PWN 新手都至少中过两三个。错误表现直接原因正确做法payload 发过去程序直接崩了offset 算错覆盖到了别的栈位置用 cyclic 工具精确计算偏移地址打包后长度不对没有按程序位数选择 p64 或 p32先确认架构再选择打包函数本地通远程不通libc 版本不一致导致 system 地址不同使用远端容器自带的 libc或用 LibcSearcher一直收不到 flag没有正确看待换行符gets 没返回改用 sendline 发送 payload不理解题目在干什么没有先梳理程序逻辑就开打先用 IDA 把 main 和漏洞函数读懂再动手这五个错误里第二个尤其典型。很多新手分不清 64 位和 32 位程序的区别拿 p64 打包一个 4 字节地址结果后面多了 4 个零字节一样会崩。遇到这种情况回头看看 context.arch 设置是不是正确。4.2 一道题卡了几个小时怎么排查做题卡住是常态不用慌关键是有一套自己的排查顺序。我的习惯是先看报错信息再看栈布局最后拆解 payload。如果程序本地崩溃了直接用 GDB 跑一遍 input用gdb ./pwn进入调试器再运行run payload.txt查看崩溃位置。pwndbg 一般会直接在栈上标出返回地址是多少、覆盖成了什么值。如果你看到返回地址是 0x6161616161616161那说明偏移量算得不对该用cyclic -l反查确切的 offset。如果程序没崩但也没有拿到 shell那就是覆盖成功了但跳错了地址。对照着 IDA 里的地址确认一遍注意可能是 stack alignment 问题——在调用 system 之前需要一个额外的 ret 来对齐栈这个细节在 x64 下尤其常见。如果远程连不上或者一直 timeout先确认容器是否还活着题目服务是否正常再确认自己的网络到目标主机是否可达。这些排查思路在教程里都会结合真实题目演示一遍看一遍之后你之后再碰到陌生报错就不会手足无措。4.3 刷题阶段如何高效积累从 writeup 到自己的 exp 模板库学会基础之后再想提升刷题是绕不开的路。我建议新手先认准 BUUCTF 这一个平台它的 PWN 方向题目难度梯度做得比较合理从入门到进阶都有而且大部分题都是以前比赛的真题质量有保障。刷题的时候不要闷头硬做每题给自己设个时间上限。比如简单题最多 1 小时中等题最多 3 小时超过时间就看 writeup。看 writeup 不是抄答案而是先看自己的思路卡在哪一步再看别人的突破口是什么最后自己重新写一遍 exp。这样做三题胜过盲目刷十题。另外我强烈建议你从早期就养成积累 exp 模板的习惯。把 ret2text、ret2shellcode、ret2libc、格式化字符串这些常见题型各存一个模板文件注释写清楚每种情况需要修改哪些参数。等后面做题多了你会发现写 exp 的速度会越来越快因为你都不用从零开始直接改模板就行。关于“AI 能不能帮你刷题”这个问题我的观点是可以用但要把握好尺度。比如让 AI 解释一段反汇编代码、给伪代码加注释、梳理程序的调用关系这些能帮你省大量时间。但是千万别让 AI 直接给你答案否则你连最基础的栈布局都记不牢到了比赛现场就原形毕露了。我个人在实际带人的过程中还有一个很深的体会PWN 入门最忌讳“收藏等于学会”。视频看再多不如自己动手把一个栈溢出题打通。这套教程所有例题都配了完整的源码、编译方式和 exp你完全可以跟着视频边看边敲。刚开始可能很慢一道题要折腾一晚上但只要亲手打通几道题理解就会上一个台阶。后面再遇到新的题目你会逐渐找到“原来这题就是套那个思路”的感觉到那个时候你就算是真正入门了。最后再给大家一个小建议别怕用 GDB。很多新手觉得调试器复杂、命令记不住其实 PWN 的调试有一个规律九成的题目你只需要反复用到run、cyclic、查看栈、查看寄存器这几条命令。把基本的调试流程跑顺PWN 就不再是玄学而是一个逻辑清晰的解谜游戏。
返回列表