CTF Writeup写作指南:从解题记录到技术资产 1. 先想清楚一个反直觉的问题Writeup到底是写给谁看的很多刚入坑CTF的朋友第一次解出题目后第一反应是高兴第二反应就是打开文档随便把flag一贴或者把解题过程三两行带过就算交差。我见过太多这样的Writeup读完之后你根本不知道作者是怎么想到这一步的更别提复现了。甚至有人连flag都不贴只留下一句“比赛详情见官方公告”——这种写法基本等于没写。写Writeup的第一件事不是打开编辑器而是想明白一个问题这东西到底是写给谁看的如果你只写给自己看那随便记两笔就行反正自己以后能看懂。但大多数人写Writeup要么是为了发到社区交流要么是准备面试时作为技术能力的佐证要么是给自己的技术博客积累内容。只要读者不是你一个人你就必须默认一件残酷的事读者对你的代码不熟、对你的思路不熟、对你当时面对的环境一无所知。你脑子里觉得“这一步显然应该这样”读者却可能卡在入口处半小时。我自己带过不少新人打CTF见过最多的抱怨就是“我看师傅的Writeup怎么感觉师傅跳了好几步”这不是师傅故意藏私而是师傅在写的时候默认读者和自己共用同一套背景知识。所以写Writeup的底层逻辑其实是一次同理心训练——你要不断问自己“如果我是第一次接触这种题的人我缺什么信息”把这一步想通了Writeup的质量会立刻上一个台阶。从这个角度出发一篇合格的Writeup应该承担三个任务记录把解题路径完整保存下来确保半年后自己回看仍然能复现。教学让一个水平低于自己的人顺着文章思路也能走通。展示通过解题思路和工程细节让别人看出你的分析能力和专业度。想清楚这些再往下写你就知道哪些地方需要展开、哪些地方可以一笔带过、哪些细节绝对不能省了。2. 好题解的通用解剖图从题目背景到Flag的完整链路先给一个我自己长期使用的五段式结构。这个结构不是硬性规定但它覆盖了大部分CTF题解需要交代的信息闭环尤其适合解题类Writeup。2.1 题目信息与前置交代让读者知道你在面对什么动笔的第一步把题目的基本盘交代清楚。具体包括题目名称、所属赛事如果允许公开的话、题目分类、分值。给的文件清单每个文件是干嘛用的加壳还是未加壳架构是什么。题目描述或提示信息有时候提示里藏了很重要的缩小搜索范围的关键词。运行环境如果涉及远程服务端口、协议、交互方式写明白。别小看这部分它决定了文章后续展开的坐标系。我见过不少Writeup题目文件拿到了也不说清楚文件的MD5、大小、类型直接就上IDA分析。读者对着文章想复现连自己拿到的文件跟作者的是不是同一个都确认不了。我自己习惯在这一小节放一个文件清单列表附上简单的检查命令输出比如用file和checksec看二进制信息用binwalk或file看附件类型。这样别人复现时先按你的命令跑一遍环境对齐了后面的分析才有意义。2.2 题目分析从“读题”到“猜想”的思考过程这一部分是整篇Writeup里最体现功力的地方。不要直接贴“我用了某个工具然后出了flag”而是把你的分析链路展开。比如拿到一个二进制文件你按什么顺序做信息收集看文件类型ELFPE还是脚本看保护机制NX、PIE、Canary、Fortify……运行一下观察交互行为尤其是输入输出用strings扫一眼有没有可疑字符串拉到反汇编或反编译工具里看主流程每一步都值得补一句“为什么”。比如“这题是64位ELF开启了NX但没开PIE所以大概率是栈溢出且固定地址可用”这就是把工具输出翻译成攻击思路的过程。读者不是要看你跑了checksec而是要看你怎么从checksec的输出推导出后续的利用方向。对于Web题则要交代你从哪个入口下手源码泄露、目录扫描、参数污染还是依赖组件的已知漏洞为什么先选这个方向而不是别的这个“为什么”就是你的分析过程。这里我特别想强调一点很多新手在Writeup里只写“成功路径”不写“失败的尝试”。这非常可惜。实际上失败的尝试恰恰是读者最需要的内容——它们能帮读者建立搜索空间的感觉。你可以用简短篇幅交代“我先试了XX工具没有收获因为它针对的场景是YY而这题是ZZ”这一句话比写三行成功路径对读者的帮助更大。2.3 解题步骤分步拆解每一大步都要能独立验证这是文章的主体也是篇幅最长的部分。核心原则每一大步做完之后读者应该能看到一个确定的中间状态。举例来说一个pwn题的利用流程完整的分步应该长这样构造payload包含padding长度、返回地址、ROP链。本地用gdb验证是否能劫持控制流。在远程复现看返回结果是否符合预期。拿到shell或读取flag。对应的你的Writeup里每一步之间要有“验证点”本地跑一把的截图、输出的十六进制内容、gdb里崩溃时的寄存器状态……这些中间产物是读者复现时判断自己是否走偏的锚点。没有这些锚点读者只有两种结局要么顺利走完但不知道为什么要么卡住之后完全无法定位问题。同时代码贴出来要能直接跑。如果你用了脚本建议在脚本旁边标注依赖库和运行方式比如“需要Python 3.8安装pwntools运行命令python3 exp.py”。不要在脚本里用莫名其妙的变量命名也不要把环境相关的路径硬编码进去。2.4 原理分析回到漏洞本质讲清楚“为什么能成”很多Writeup解完题就戛然而止代码贴完flag贴完不再多解释一句。这在我看来是丢了写作价值的一半。打个比方前面那部分像是在演示一遍魔术但这部分是在揭秘——把魔术师的每一个手法、观众的每一个心理盲区都拆开来讲。比如格式化字符串漏洞题你不仅要贴出利用脚本更要点出“为什么%s可以泄露栈上的libc地址因为我们输入的数据也存放在栈上所以通过参数偏移能回溯到预先埋好的指针”。原理分析写得好的话一篇Writeup就能从单纯的一份解题记录升级成一篇微型技术教学。读者看完之后遇到同类题型可以迁移思路这才叫真正吸收了。2.5 收获与总结提炼可复用的方法论这一部分不必长但应当有。比如这题教会了你一个通用的绕过技巧、一个工具的高阶用法、一种快速定位漏洞的模式都可以提炼成一两句话。如果你做过系列化Writeup比如复现同一赛事的几道题还可以把多题之间共性的解题框架串起来这样文章的“迁移价值”会更大。不过要注意收获与总结不是学习心得报告不用喊口号也不用写“这次比赛让我明白了……”这种空话只记录技术上新鲜的东西就够。3. 不同题型的“展开重点”完全不同千万别写成一个模子这是最容易被忽视的一点。很多人的Writeup不管什么题型都套同一个结构但读者读起来会感觉“对味但不过瘾”原因就在于没有根据题型调整信息的疏密分布。3.1 PWN题环境复现和利用脚本解说必须双管齐下PWN题天然依赖本地环境。同样的二进制在不同的glibc版本下偏移完全不同。因此一个合格的PWN题Writeup至少要把以下信息交代完整题目给的二进制文件的哈希值MD5或SHA256即可确保读者拿到的是同一个文件。运行环境内核、libc版本、是否使用docker或pwn环境仓库。这几个参数直接决定你能不能复现也是读者最先想对齐的信息。保护机制信息最好直接粘贴checksec完整输出而不是只贴结论。漏洞定位过程你是通过代码审计、fuzzing还是diff找到的入口对应的函数名和关键行号都标出来。利用脚本要分段解释脚本里的每一个关键地址、偏移是多少这些数字是怎么算出来的。举个实际例子如果你用LibcSearcher查libc一定要说明查到的libc版本和偏移量。很多新手在这块会翻车——本地能打通远程打不通最后发现是远程靶机的libc版本和本地不同。如果Writeup里不写清楚你是怎么处理和解决这个差异的读者就少了一次重要的经验传递。3.2 Reverse题从入口到关键算法的还原过程是灵魂逆向题Writeup最忌讳直接甩出解密脚本。读者真正需要的是你怎么从反汇编的汪洋大海里找到关键函数、你怎么识别出加密算法的模式、中间有哪些关键的动静态对照验证。写这类Writeup时我会带上Identity信息比如使用IDA还是Ghidra是否加载了FindCrypt插件检测到哪些加密常量。关键函数的反汇编/反编译代码片段注意只贴核心片段不要贴几千行。算法识别过程。比如看到一堆位移和异或运算如何判断这可能是一个自实现的流密码或者看到S盒结构如何初步猜测是AES或类AES的变形。动静态结合你在哪个函数下断点输入什么测试数据观测到了哪个中间值从而确认了某个假设。3.3 Web题从入口到利用链的推理过程就是全部价值Web题和PWN的差别在于Web题的信息收集和分析推理权重更大环境复现的反而不太依赖本地。那么Writeup的重点就应该放在攻击链的推演上。我建议用“请求-响应”的粒度来组织写作你发了什么请求拿到了什么响应响应里的哪一行信息给了你下一步灵感。这个循环多重复几次读者就能看到你的推理过程。最怕的是直接甩一个利用工具跟最终结果这等于把最重要的思考过程扔掉了。比如你通过目录扫描发现了备份文件下载下来后审计出某个参数存在注入点再通过报错信息确认是布尔盲注最后写脚本逐位爆破出数据。每一步都值得展开尤其是“怎么从报错差异猜到是布尔盲注”的推理环节往往才是读者真正需要的东西。3.4 Crypto题把数学原理和代码实现一一对应Crypto题Writeup的常见问题是“给了脚本但讲不清数学原理”。反过来讲光讲数学原理但不给可运行代码也是空中楼阁。两者必须对应起来。我一般这样组织Crypto题的内容先说明密码体制的已知条件加密脚本、密文、公钥参数。分析加密脚本标注出哪个变量是敏感信息、哪个环节可以化简。推导攻击方法。这里宁可慢一点把公式变换步骤写全也不要直接跳到最终代码。再贴代码并在代码注释里回链到上面的公式步骤。举个例子如果是一道RSA的Wiener Attack题你应该先交代“d很小满足Wiener Attack的使用条件”再讲连分数逼近的原理再说明怎么调用owariin或自己实现连分数展开最后才是跑脚本拿flag。这样爱钻原理的读者和只想快速复现的读者都能各取所需。3.5 Misc题流量和取证类的关键在“异常点发现”的叙述Misc题的开放度最高没有统一套路但多数情况下核心就一件事在一堆看似正常的数据里发现那个不正常的地方。写作时需要把“你是怎么注意到异常的”这个过程写清楚。比如从pcap包里筛HTTP请求时发现某个请求的User-Agent格外长促使用tshark把它导出来看结果发现是base64编码的压缩包。这个“注意到”的一刻就是你全文的高潮应当详细说不要一笔带过。工具命令的输出也记得保留这些能够帮助读者对齐信息。比如你在流量包中找到了多少条可疑TCP流、某段数据是PNG头等用输出截图简单注释比纯文字描述更有效。4. 让图说话从截图到流程图的实操技法很多Writeup读起来干巴巴的一个原因是图太少、图表太糙。要记住看图比读字省力读者是来学东西的不是来做阅读理解练习的。但图不是随便贴的图表的质量直接影响整篇文件的观感。说说我自己的经验。4.1 截图的三条铁律高分辨率把终端窗口拉大、调高字体再截图。缩小截图或者模糊截图都是灾难。控制信息量截“关键输出”就够了不要整屏截取。比如你运行exp之后系统输出几百行但只有最后三行是关键那就截那三行。标注重点用图像处理软件简单画个红框或加个箭头指向关键寄存器、关键输出的flag位置。不要嫌麻烦这个动作能帮读者节省大量寻找时间。4.2 用流程图表达攻击链路而不是用大段文字一整套攻击流程如果用文字描述读者很容易迷路。我习惯用简单的流程图辅助说明比如入口点 → 漏洞点 → 中间跳板 → 最终利用画流程图的工具随意Graphviz、draw.io之类都可以但注意不要画太复杂的图。图的作用是给读者建立全局观不是炫技。五六个节点的图是上限。我自己常用Graphviz写一段很小的dot脚本生成攻击链图看起来干净也方便后续改。有时候也用工具直接手画。对于更复杂的逻辑关系、比如数据流走向我建议配合表格来说明。4.3 什么情况适合用表格表格特别适合三类信息参数对比比如同一漏洞在不同环境下偏移的变化对照表。步骤状态表分步列出“当前输入/预期输出/实际输出”的验证记录。文件清单文件名、类型、作用、检查结果。表格的好处是它把分散在文字里的信息重新组织成可横向对比的格式读者扫一眼就能抓住差异点。5. 那些年我踩过的Writeup坑五类真实事故复盘写得多了踩的坑也就多了。有些坑我在读者评论里反复看到干脆集中盘点一下当作反面教材。5.1 只贴最终利用代码不贴过程读者看完等于没看这是最常见的问题。一篇Writeup写着“打开IDA发现栈溢出写exp一发入魂”——怎么发现栈溢出的保护全貌怎么样缓冲区多大怎么控制返回地址的全都不说。这种写作方式的本质是作者默认读者和自己具有完全相同的分析速度与路径但实际上这是不成立的。你要相信哪怕你只在每个关键决策点上多写两句话给读者带来的体验改善都是巨大的。5.2 不标版本号和依赖环境导致“跑不起来”我曾经看过一个Writeup脚本里用了某个特定版本的工具特性但没有标注版本号。我本地复现时工具行为完全不同浪费了大量时间排查才发现是版本差异。所以现在我自己写Writeup有一条死规矩凡是涉及工具尽量标注版本或至少标注使用的大版本凡是涉及代码必须说明依赖库和运行环境。这是对读者最基本的尊重。5.3 贴代码不留注释关键地址来历不明很多新手以为贴代码就是交差其实代码只是载体代码里的注释才是信息量的核心。我自己贴exp时关键行必加注释这行padding是根据什么算的、这个地址是怎么泄漏出来的、这条ROP链分别调用了什么函数。加上注释的代码才是“能教学的代码”。不加注释的代码别人只能拿到一个执行结果却拿不到你的分析过程。5.4 拿flag之后戛然而止没有总结成方法论写Writeup本质上是一种知识管理行为。如果只记录“这一题怎么解”那这些知识就是点状的换一道新题就作废。但如果你能提炼成一种套路、一种模式知识就能迁移。我在前文提到了收获总结部分这里再补一句这部分不用写很长但要有意识地把“这一题的具体方法”升维成“这一类题的处理框架”这样你的Writeup就不只是一篇记录而是变成一份可以复用的笔记资产了。5.5 过度装饰排版忽略了内容本身也有一些Writeup走入了另一个极端排版花里胡哨各种主题、各种标注、各种高亮结果核心内容只有两百字。我的建议是排版以清晰为主不要为了“好看”牺牲“好读”。该加粗的术语加粗该加的代码块代码块保持统一风格即可。真正吸引读者的是内容密度不是样式复杂度。6. 从解题记录到技术资产长期运营一篇Writeup的方法Writeup写多了之后你会慢慢发现它的价值不只是给某一道题留下记录它可以沉淀成一套你自己的技术资产对你的学习、求职和技术品牌都有持续的帮助。我自己采用的方法是把Writeup看成一个持续迭代的项目来管理。6.1 建立个人Writeup库的统一模板你会发现当你写了几十篇之后再回头更新旧文或搜索内容时有一个统一的模板会方便得多。不需要多复杂一组章节标题顺序是“信息收集→分析过程→利用步骤→原理→总结”就够了。这样你在新写一篇时不用纠结结构专注填内容。更重要的是当读者熟悉了你的模板之后阅读门槛会大幅降低他们会很快定位到自己想看的部分。6.2 用索引页管理自己的题解目录题目多了以后建议做一个索引页分门别类PWN、Reverse、Web、Crypto、Misc。每个分类下列出题目名称、难度、涉及的技巧点、链接。这个索引页对你个人复盘和对外展示都很有价值。反过来写题解的时候也会倒逼你更系统地做分类训练。一段时间后你会发现哪个分类自己练得少、哪个分类已经形成了自己的路径依赖从而有针对性地补强。6.3 定期回顾旧文迭代更新技术是会变的。今天的libc版本和你半年前用的可能就不同了某道题你当初用了一个很笨的解法半年后再看你可能会想到更好的路径。所以我会定期回头翻自己写的旧Writeup能优化的就顺手改掉不能改的就补一条“更新说明”留在文末。这个习惯让我的技术库质量能持续保持在一个比较高的水平。6.4 把Writeup当成面试素材库的一部分CTF经历在面试中能不能发挥作用很大程度取决于你能不能清晰、结构化地表达自己的思考过程。而Writeup恰恰就是这种表达能力的最佳训练场。我自己面试别人时候的经验是让候选人讲一道自己做过的最有成就感的题目。有些人讲得条理清晰、细节饱满一听就是真做过并且真思考过有些人讲得支离破碎、关键细节语焉不详。这两类人的差距在他们写的Writeup里其实早就暴露了。所以不管你现在是刚接触CTF的新人还是已经打了一段时间比赛的老人我都建议真正重视起Writeup这件事。它就是你的技术笔记、教学材料、复盘工具和个人品牌的统一体。只要坚持用正确的方法写你的进步速度会比你想象得快很多。