PDF is Broken: a justCTF Challenge
Trail of Bits赞助了最近的justCTF竞赛,我们的工程师帮助设计了多个挑战,包括D0cker、Go-fs、Pinata、Oracles和25519。在本文中,我们将介绍另一个挑战,标题为“PDF is broken, and so is this file”。它通过一个不寻常的隐写谜题展示了PDF文件格式的一些特性。CTF挑战通常涉及在干草堆中寻找隐写针,这很少具有启发性,更不用说令人愉快了。LiveOverflow最近有一个关于文件格式技巧的优秀视频,并以类似的观点结束。因此,我们设计了这个挑战,以教授justCTF参与者一些PDF技巧,以及Trail of Bits的开源工具如何轻松处理这些取证挑战。
其中PDF是一个Web服务器,提供自身的副本
挑战中的PDF文件实际上是损坏的,但大多数PDF查看器通常只会将其渲染为空白页面而不报错。file命令将挑战报告为“data”。在十六进制编辑器中打开文件,我们看到它看起来像一个Ruby脚本:
|
|
第5行的PDF头嵌入在第4行开始的Ruby多行注释中,但这并不是损坏的部分!几乎所有PDF查看器都会忽略%PDF-1.5头之前的所有内容。第7行和第8行是PDF注释,确认了我们从file命令看到的内容,以及一个我们稍后会提到的readelf提示。
Ruby脚本的其余部分嵌入在PDF对象流中——“9999 0 obj”行——它可以包含PDF忽略的任意数据。但PDF的其余部分呢?它如何不影响Ruby脚本?
|
|
Ruby有一个特性,词法分析器会在__END__关键字处停止,并有效地忽略其后的一切。果然,这个奇特的PDF有这样一个符号,后面是封装PDF对象流的结尾和PDF的其余部分。
这是一个Ruby/PDF多语言文件,您可以使用类似的方法将任何PDF转换为这样的多语言文件。如果您的脚本足够短,您甚至不需要将其嵌入PDF流对象中。您可以将其全部附加在%PDF-1.5头之前。尽管一些PDF解析器如果在文件的前1024字节内找不到头会报错。
您没想过会这么容易,对吧?
所以让我们勇敢地尝试将PDF作为Ruby脚本运行。果然,它运行了一个Web服务器,提供一个带有“flag.zip”下载链接的网页。哇,这很容易,对吧?进一步检查Ruby脚本,您会发现下载是PDF文件本身重命名为.zip。是的,除了是Ruby脚本之外,这个PDF也是一个有效的ZIP文件。PoC||GTFO多年来一直使用这个技巧,这也可以通过运行binwalk -e在挑战PDF上观察到。
解压PDF会产生两个文件:一个MμPDF mutool二进制文件和false_flag.md,后者建议玩家通过mutool二进制文件运行损坏的PDF。
显然,这个版本的mutool被修改为正确渲染损坏的PDF,尽管它有什么“损坏”。CTF玩家是否应该逆向工程这个二进制文件来找出修改了什么?如果有人尝试了,或者他们尝试了上面嵌入为PDF注释的readelf线索,他们可能会注意到这一点:
您应该做的第一件事是:在十六进制编辑器中打开PDF。您可能需要“修复”PDF,以便它可以被普通的PDF阅读器解析。您可以逆向这个二进制文件来找出如何做到这一点,但使用它来渲染PDF、跟随线索并将原始PDF对象与“常规”PDF的对象进行比较可能更容易。您可能只需要用
bbe修复它!
二进制块编辑器(bbe)是一个类似于sed的实用程序,用于编辑二进制序列。这意味着导致PDF渲染为空白页面的任何问题都可以通过二进制正则表达式轻松修复。
深入探索
当我们使用修改版的mutool渲染PDF时,它会产生这个表面上无意义的模因蒙太奇:
使用修改版的mutool渲染的“损坏”挑战PDF。
在Google中搜索LMGTFY字符串将带您到Didier Stevens的优秀文章,详细描述PDF流格式,包括PDF对象如何编号和版本化。一个重要因素是,两个PDF对象可以有相同的编号但不同的版本。
页面上的第一个提示标识了PDF对象1337,所以那可能很重要。Stevens文章中的图表单独与损坏PDF的流对象的十六进制转储并列,清晰地描绘了更改的内容。
Didier Stevens的PDF流对象注释图。
|
|
挑战PDF中的PDF流对象。
正如提示所建议的,PDF规范只允许六种空白字符:\0、\t、\n、\f、\r和空格。ZIP中的mutool版本被修改为也允许ACK(0x06)作为第七种空白字符!果然,在文件的第十二行我们看到:
|
|
那个“^F”是一个ACK字符,而PDF规范说那里应该是空白!所有PDF对象流都类似地损坏了。这可以通过以下方式修复:
|
|
解决谜题
修复文件是否严格必要来解决挑战?不,可以使用十六进制编辑器在PDF对象0x1337中找到标志
|
|
并手动解码流内容。Binwalk甚至会自动解码第一个流,因为它可以解码Flate压缩。其中包含:
|
|
Binwalk不会自动扩展第二个流,因为它还使用ASCIIHex和DCT PDF过滤器编码。一个没有跟随所有线索且还不熟悉PDF规范的随意观察者可能甚至没有意识到PDF流对象0x1337的第二个版本存在!而那个就是包含标志的版本。当然,可以梳理binwalk提取的数十个文件来手动解码标志,甚至直接从十六进制编辑器中的流内容,通过快速实现PDF的解码器。但为什么要这样做,当Polyfile可以为您做呢?
|
|
挑战PDF的PolyFile HTML输出。
哦,嘿,那是PDF对象的分层表示,带有交互式十六进制查看器!我们去看对象0x1337的流怎么样?
我们立即看到PDF对象0x1337。
PolyFile可以自动解码对象。
最后,让我们看看对象0x1337的第二个版本,包含多层编码的标志:
PolyFile自动解码分层的PDF过滤器以产生标志。
标志!
结论
PDF是一个非常……灵活的文件格式。仅仅因为PDF看起来损坏,并不意味着它是。而仅仅因为PDF损坏,并不意味着PDF查看器会告诉您它是。PDF核心是一个容器格式,让您编码任意二进制块,这些块甚至不必贡献于文档的渲染。而这些块可以堆叠任意数量的编码,其中一些是PDF的定制特性。如果这让您感兴趣,请查看我们关于文件 treacherous 的演讲,以及我们驯服它们的工具,如Polyfile和PolyTracker。
如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News
页面内容 其中PDF是一个Web服务器,提供自身的副本 您没想过会这么容易,对吧? 深入探索 解决谜题 结论 最近文章 Trail of Bits的Buttercup在AIxCC挑战中获得第二名 Buttercup现已开源! AIxCC决赛:记录表 攻击者的提示注入工程:利用GitHub Copilot 作为新员工发现NVIDIA Triton中的内存损坏 © 2025 Trail of Bits。 使用Hugo和Mainroad主题生成。