PDF的多面性:一个Ruby脚本、ZIP文件与CTF挑战的奇妙结合

本文详细解析了justCTF竞赛中的“PDF is broken”挑战,探讨了PDF文件格式的独特特性,包括Ruby/PDF多语言文件、ZIP封装、二进制编辑与流对象解码,展示了如何利用工具修复文件并提取隐藏标志。

PDF的多面性:一个justCTF挑战

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命令报告挑战文件仅为“数据”。在十六进制编辑器中打开文件,我们看到它看起来像一个Ruby脚本:

1
2
3
4
5
6
7
8
require "json"
require "cgi"
require "socket"
=begin
%PDF-1.5
%ÐÔÅØ
% <code>file</code>  sometimes lies
% and  <code>readelf -p .note</code> might be useful later

第5行的PDF头嵌入在第4行开始的Ruby多行注释中,但这不是损坏的部分!几乎所有PDF查看器都会忽略%PDF-1.5头之前的所有内容。第7和8行是PDF注释,确认了我们从file命令看到的内容,以及一个我们稍后会提到的readelf提示。

Ruby脚本的其余部分嵌入在PDF对象流中——“9999 0 obj”行——,它可以包含PDF忽略的任意数据。但PDF的其余部分呢?这如何不影响Ruby脚本?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
9999 0 obj
<< /Length 1680 >>^Fstream
=end
port = 8080
if ARGV.length > 0 then
  port = ARGV[0].to_i
end
html=DATA.read().encode('UTF-8', 'binary', :invalid => :replace, :undef => :replace).split(/<\/html>/)[0]+"\n"
v=TCPServer.new('',port)
print "Server running at http://localhost:#{port}/\nTo listen on a different port, re-run with the desired port as a command-line argument.\n\n"

__END__

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流对象注释图。

1
2
3
4
5
5 0 obj
<< /Length 100 >>^Fstream
endstream
endobj

挑战PDF中的PDF流对象。

如提示所建议,PDF规范只允许六个空白字符:\0、\t、\n、\f、\r和空格。ZIP中的mutool版本被修改以也允许ACK(0x06)用作第七个空白字符!果然,在文件的第十二行我们看到:

1
>>^Fstream

那个“^F”是一个ACK字符,而PDF规范说那里应该是空白!所有PDF对象流都类似地损坏。这可以用以下方式修复:

1
bbe -e "s/\x06stream\n/\nstream\n/" -o challenge_fixed.pdf challenge.pdf

解决谜题

修复文件严格必要解决挑战吗?不,标志可以在PDF对象0x1337中使用十六进制编辑器找到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
4919 0 obj
<< /Length 100 /Filter /FlateDecode >>^Fstream
x<9c>^MËA^N@0^PFá}OñëÆÊÊ        <88>X;^Ba<9a>N<8c>N£#áöº~ßs<99>s^ONÅ6^Qd<95>/°<90>^[¤(öHû       }^L^V   k×E»d<85>fcM<8d>^[køôië<97><88>^N<98> ^G~}Õ\°L3^BßÅ^Z÷^CÛ<85>!Û
endstream
endobj
4919 1 obj
<< /Length 89827 /Filter [/FlateDecode /ASCIIHexDecode /DCTDecode] >>^Fstream
endstream
endobj

并手动解码流内容。Binwalk甚至会自动解码第一个流,因为它可以解码Flate压缩。那包含:

1
2
3
pip3 install polyfile
Also check out the `--html` option!
But you’ll need to “fix” this PDF first!

Binwalk不会自动扩展第二个流,因为它还编码了ASCIIHex和DCT PDF过滤器。一个随意的观察者,没有跟随所有线索且不熟悉PDF规范,可能甚至没有意识到PDF流对象0x1337的第二个版本存在!而那是带有标志的版本。当然,可能已经梳理了binwalk提取的数十个文件以手动解码标志,甚至直接从十六进制编辑器中的流内容,用PDF解码器的快速实现。但为什么要这样做,当Polyfile可以为您做呢?

1
polyfile challenge_fixed.pdf -html challenge_fixed.html

PolyFile的HTML输出为挑战PDF。

哦,嘿,那是PDF对象的分层表示,带有交互式十六进制查看器!我们如何转到对象0x1337的流?

我们立即看到PDF对象0x1337。

PolyFile可以自动解码对象。

最后,让我们看看对象0x1337的第二个版本,包含多重编码的标志:

PolyFile自动解码分层PDF过滤器以产生标志。

标志!

结论

PDF是一个非常…灵活的文件格式。仅仅因为PDF看起来损坏,并不意味着它是。仅仅因为PDF损坏,并不意味着PDF查看器会告诉您它是。PDF核心是一个容器格式,让您编码甚至不贡献于文档渲染的任意二进制块。而这些块可以用任意数量的编码堆叠,其中一些是PDF的定制特性。如果这感兴趣,请查看我们关于文件 treacherousy的演讲,以及我们驯服它们的工具,如Polyfile和PolyTracker。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计