刚接触攻防世界的时候我花了不少时间在Misc类题目上结果印象最深的不是那些花里胡哨的流量包反而是几道PDF题。你可能会觉得PDF能有什么搞头打开看一眼不就行了。直到有一次flag就藏在页面里一行几乎看不见的白色小字后面我才彻底改观PDF在杂项题里根本不是什么“文档”它就是个包装严实的快递盒。这篇文章想和你聊聊面对攻防世界里这类型“Misc PDF题”我总结出的一套从盲目试探到快速拿flag的完整流程也把踩过的坑一并摊开。无论你是刚入CTF的新手还是被PDF题卡过好几次的老哥按这个思路去拆至少不会对着文件发呆。1. 为什么Misc题总拿PDF当“快递盒”1.1 PDF本身就是个“文件套娃”PDF的全称是Portable Document Format它最初的设计目标是让同一份文件在任何设备上保持完全一致的外观。为了实现这个目标PDF文件里塞的东西非常多页面文字、字体文件、图像数据、元数据、脚本、注释甚至还能附带独立文件。你表面上看到的是一张张页面其实底层是一个结构复杂的容器。从布局上看PDF很像一间收纳柜有目录交叉引用表有柜子内部一个个小格子对象格子里的数据可以原样摆放也可以先压缩再塞进去。出题人最喜欢的就是这种“能塞好几层东西”的特性。页面显示出来的只是一个小小的渲染层而藏在对象里的可能是完整的zip压缩包、一张二维码图片、一段十六进制字符串甚至一个可执行脚本。说它是“文件套娃”是因为你在阅读器里看到的干净页面并不代表文件内部同样干净。PDF阅读器的职责只是找到页面树和内容流把它们渲染成视觉画面。至于那些没被页面引用的对象、注释、内嵌附件只要页面不显示阅读器就完全无视。CTF的Misc题目恰恰就爱把关键信息藏在这些“阅读器不关心”的位置。1.2 出题人喜欢在PDF里埋哪些坑以攻防世界Misc板块的PDF题为例常见的埋法可以分成这么几类可见内容陷阱页面里藏白色文字、把关键字符拆散打乱、用遮挡块盖住部分字符串肉眼扫页面非常容易漏。元数据藏毒作者、标题、自定义属性里直接写flag尤其是用新版编辑器生成的PDF自定义信息字段特别丰富。附加文件PDF规范支持EmbeddedFile文档里可以完整嵌一个压缩包、txt、图片等独立文件。对象流隐藏很多编辑器会把对象压缩到ObjStm流里阅读器不会渲染但里面很可能藏着关键线索。图片隐写PDF页面里的图像本身就可以是一张独立的图图片里再隐藏信息又回到了最经典的图片隐写思路。密码保护整个文件被加密需要破解口令或者从加密字典里找到漏洞提取出内部流。我自己遇到过最折腾的一次flag被拆成三截分别放在页脚、注释项和某个老版本字体的子集里单看任意一处都是无意义的乱码必须三处拼起来才完整。这个教训让我后来形成习惯拿到PDF题先默认它是容器完整拆一遍再说而不是急着翻页面。2. 解题前先把PDF的“骨架”摸清2.1 PDF文件的底层结构其实就四个部分一个合法的PDF文件从字节流的顺序看大致由四块组成头部通常以一行%PDF-1.x开头标明版本号。个别文件会在这一行前面塞垃圾字节甚至修改头部格式注意别被误导。主体一串对象每个对象有唯一编号形如1 0 obj ... endobj。对象内部可以放字典、数组、字符串、数值、流等数据。交叉引用表也叫做xref相当于整个文件的“目录”记录每个对象在文件里的偏移地址。尾部trailer里面指向Root、Info等关键对象最终由%%EOF收尾。如果某个PDF文件打开正常但缺少xref或trailer多半说明文件被人为改过要么为了干扰分析要么文件曾经损坏后被修复过。遇到这种情况不用慌用qpdf或mutool重新整理一遍让它重新生成交叉引用表很多线索反而会冒出来。理解这个结构后你再看PDF阅读器的行为它只负责渲染页面树和内容流。对象之间靠引用关系关联只要一个对象没有被页面树引用到阅读器就当它不存在。而我们找的flag往往恰好就住在这些“不被渲染”的对象里。2.2 压缩流无处不在但不一定要先解压PDF里的对象并不是全是明文。文本内容流、字体文件、图像数据都可能经过过滤器压缩。最常见的过滤器是/FlateDecode也就是zlib压缩。遇到这种压缩对象直接strings只会看到一堆乱码因为真正的字符串在压缩层之下。正确思路是先把流解出来再去看可读内容。但这里有个容易走偏的地方不是所有压缩流都需要立刻解压。字体文件的子集、色彩配置描述、大量矢量图形也常常以压缩流形式存在解出来是长篇大论未必有用。更高效的做法是先观察对象的字典入口看有没有可疑键名比如/Flag、/EmbeddedFile、/JavaScript、/Launch、/Annots。这些键名一旦出现基本就是出题人留下的高亮路标。2.3 在动手之前把工具备齐我平时处理PDF题固定用下面这组工具覆盖了从外部识别到内部解剖的完整链路工具主要用途备注file识别真实文件类型确认是不是“披着PDF皮的zip或gzip”strings提取可打印字符串加-n 6减少短噪音输出exiftool查看元数据PDF的Info字典信息都能读到binwalk扫描内嵌文件签名检测隐藏在文件中间的压缩包、图片等foremost按签名自动分离文件能把隐藏在流里的东西抠出来qpdf修复、解压、重写PDF--qdf --object-streamsdisable极其好用pdf-parser逐对象解析PDF结构Didier Stevens的经典脚本解剖利器pdfimages导出PDF内所有图片来自poppler-utils看图藏题必备安装很方便Kali里大部分自带如果用的是自己的Ubuntu一条命令也能装齐sudo apt update sudo apt install binwalk exiftool qpdf poppler-utils foremostpdf-parser是独立Python脚本需要单独下载也可以直接用pip install pdfminer.six替代一部分功能。工具备齐后建议把它们串成一个固定流水线先file识别再strings和exiftool扫外部然后binwalk和foremost分离内嵌物最后再用pdf-parser和qpdf深入对象层。这套顺序能在多数情况下覆盖全部线索。3. 一道攻防世界PDF题的完整实战拆解3.1 第一步先识别文件别急着双击打开假设你已经从攻防世界平台下载了一个PDF附件文件名可能叫flag.pdf或者misc_pdf.pdf。不管你多么好奇我拿到文件的第一个动作永远是file flag.pdf如果输出是PDF document, version 1.7那它确实是个PDF。但偶尔输出会变成gzip compressed data或Zip archive data这说明后缀名是障眼法题目其实让你解压缩包。这类题在Misc里属于开胃菜算是出题人的第一层“假动作”。确认是PDF后我通常不会立刻打开阅读器而是先跑一轮strings flag.pdf | head -50目的是快速浏览整体字节内容。正常PDF里会出现一堆/Type /Catalog、/Pages、/Font、/ProcSet之类的结构标识。如果在这个阶段直接看到flag、key、ctf这些字样那说明题目放水直接拿就行。但更多时候strings输出非常干净甚至看不到几条超过6个字符的字符串这时候就需要往下挖了。3.2 第二步搜元数据和内嵌文件实体先看“信封上的信息”用exiftool flag.pdfexiftool能读取PDF的Info字典包括Creator、Producer、Author、Description、CreateDate等字段。我之前在攻防世界里遇到过一道题flag被直接写在Author字段里格式是flag{this_is_not_real}属于出题人故意放水。不过也有反套路的时候Description里写了一大串看着像flag的字符实际是干扰项真正flag藏在别处。所以元数据信息可以信但不能全信。接下来上binwalkbinwalk flag.pdfbinwalk会按文件签名扫描整个PDF输出里如果出现Zlib compressed data、RAR archive data、PNG image之类的提示说明PDF内部可能藏着独立文件。这时用foremost直接分离foremost flag.pdf -o extracted分离出来的东西再按正常文件处理。我自己遇到过一道比较折腾的题PDF里嵌了一个zipzip里又是一个没有后缀名的文件用file识别后发现是PNG最后从PNG的图片隐写里提取flag。整整绕了四层每层的线索都是靠binwalk和foremost扫出来的。3.3 第三步对象级“解剖”把PDF拆到零件状态如果binwalk和foremost都没有发现说明题目不是简单嵌套而是藏在对象级结构里。这时候我会把原始文件复制一份避免后续修改破坏原始线索然后执行pdf-parser.py flag.pdf它会列出文件里所有对象包括编号、类型、引用关系和过滤器。一眼看过去重点关注这几个关键词/EmbeddedFile内嵌文件对象极大概率是压缩包、文档或图片附件。/JavaScript存在脚本可能是弹窗提示也可能是动态拼接出来的flag。/ObjStm对象流表示一组对象被压缩打包在一起普通查看器不渲染。/Metadata元数据XML可能把flag藏在XML注释里。/Annots注释对象可能藏在弹窗、超链接或者注释内容中。当看到某个对象带/Filter /FlateDecode时可以单独把这个对象的流导出再解压。pdf-parser提供了很直接的参数pdf-parser.py -f -o 5 flag.pdf其中-f表示按过滤器自动解码-o 5表示只处理第5号对象。解出来的内容会直接打印在终端。曾经有一次我就是用这个方式解出一个对象里面是一串看起来毫无规律的数字转成十六进制后依然是乱码再换成Base64解码最后出来一张包含flag的图片。整个链路非常典型。如果嫌命令行一个个试太慢可以先跑一步“把所有对象流打散”qpdf --qdf --object-streamsdisable flag.pdf flag-qdf.pdf这个命令会重新编码PDF把原本压进对象流里的对象全部展开成明文。处理完再跑一遍strings和pdf-parser很多藏得深的信息就自然冒出来了。我个人的日常流程就是先qpdf重写再重跑strings经常能发现第一轮扫描漏掉的关键对象。3.4 第四步处理页面里的“程序”和“图片”某些题目会进一步升级把内容藏进页面内容流里甚至要求动态执行才能看到结果。比如PDF里包含一段JavaScript打开文档时会弹窗输出flag的一部分。这种题我不会直接双击文档因为本地阅读器可能会执行脚本存在不可控风险。更稳的做法是用pdf-parser提取脚本内容人工去分析字符串拼接逻辑必要时再用脚本重写一个测试PDF来验证。另一种高频场景是在页面中放一张二维码图片或一张被裁剪过的图片。这类题的特点是把图片当独立对象嵌在PDF里。用pdfimages可以把所有图像全部导出pdfimages -all flag.pdf img导出后逐个查看有时候图片里是一张二维码扫出来就是flag有时候是需要进一步图片隐写分析的底图。这个方法值得成为固定步骤因为阅读器页面里显示的图片比例往往被缩放你肉眼看不清细节但原图对象是完整保存在的。导出原图后很多线索一目了然。如果最后拿到flag还需要处理比如倒序、十六进制混淆、Base64编码直接用Python脚本处理就行。下面这个模板可以按需改s }galf{tpircsavaj print(s[::-1]) # 或者处理十六进制字符串 hex_str 666c61677b746573747d print(bytes.fromhex(hex_str).decode())3.5 补充用Python脚本精准提取可疑压缩流工具是快但想要自动化批量搜索还是得写脚本。下面是我经常改用的模板它找出文件里所有stream ... endstream之间的数据尝试用zlib解压再搜索flag关键词import re import zlib with open(flag.pdf, rb) as f: data f.read() # 匹配所有 stream 与 endstream 之间的原始数据 for m in re.finditer(rbstream\r?\n(.*?)endstream, data, re.S): raw m.group(1).rstrip(b\r\n) try: dec zlib.decompress(raw) if bflag in dec.lower() or bctf in dec.lower(): print(dec) except Exception: pass这段脚本适用于那些没有把对象压进对象流的简单PDF。如果遇到对象流先用前面说的qpdf拆开再运行脚本命中率会高很多。脚本不是万能药它的意义在于帮你快速缩小搜索范围最后的关键判断仍然需要人来做。4. 实战中踩过的坑与排查技巧4.1 你可能会被这些细节骗到strings不是万能的。PDF里的字符串有些是UTF-16编码比如FEFF0066006C00610067strings默认只按ASCII提取超出一字节范围的内容很容易被跳过。看到这类十六进制块先转成ASCII或UTF-8再分析。压缩可能不止一层。有些对象解压之后里面又是一个对象流继续解压才有真实内容。不要以为“解压过一遍就万事大吉”我建议写个小循环解到不能再解为止。交叉引用表可能是假的。有些PDF故意改坏xref让常规解析工具报错但阅读器依然能打开因为阅读器会通过分析对象流自动修复。这种“坏目录”正好是出题人的障眼法。解决办法是用qpdf修复让它重建完整的交叉引用表再继续正常分析。别忘了页面内容流本身。PDF页面渲染所需的文字通常以BT到ET标记的形式存在内容流中。字符串可能被拆成多个TJ数组字符顺序非常混乱。这时用pdftotext先提取可见文本虽然顺序可能错乱但总比自己肉眼强pdftotext flag.pdf flag.txt4.2 高频问题速查表现象可能原因排查步骤打开PDF正常但strings扫不到东西flag藏在对象流或压缩流中先qpdf重写再strings和pdf-parserexiftool看到可疑字符但不完整元数据里有多语言或编码转换问题用exiftool -b导出原始值再配合hexdump分析binwalk扫出一堆zlib数据对象被压缩但未必都相关用Python脚本批量解压并搜索flag关键词PDF页面里有一张图看细节不清楚图片被缩放但原图对象完整用pdfimages导出原图再按图片隐写流程处理文件提示损坏但阅读器能打开xref交叉引用表被改动用qpdf修复后继续分析PDF打开要求密码文件被加密pdf-parser看Encrypt字典再考虑字典或暴力破解4.3 几个能让效率翻倍的习惯实操多了之后我养成了几个固定习惯对新人也挺有帮助原始文件永远只读不改。所有修复、重写、解压操作都放在副本上否则一个失误把原始线索覆盖了心态很容易崩。每做一步都把中间结果保存下来。比如strings flag.pdf strings.txt、exiftool flag.pdf exif.txt回头排查时可以反复对比不会因为终端刷屏丢信息。建立自己的题目笔记。攻防世界的Misc PDF题虽然花样多但套路迭代并不快做一次总结之后遇到同类题直接套模板速度能快不少。合理使用搜索引擎。不少题目改自国外CTF比赛搜一下原始writeup能省很多时间但前提是自己先认真拆过一遍。直接照抄别人的思路刷题效果会大打折扣。最后再分享一个体会。很多人一看到PDF就把它当普通文档觉得里面最多就是文字和图片实际上PDF是最容易被塞私货的格式之一。我在攻防世界刷Misc题越到后面越顺不是因为工具用得多花哨而是因为形成了固定的拆解习惯先看文件类型再扫正常内容然后分离内嵌文件最后解剖对象层。如果你现在也被PDF题卡住别急着乱翻按这套流程完整走一遍大概率会有收获。CTF杂项就是这样手法并不复杂拼的往往就是你比出题人更多想了一层。