深入浅出攻防恶意PDF文档实战

日期: 2009-07-23 作者:宇文 来源:TechTarget中国 英文

  随着恶意PDF文件日益增多,人们对这种文档的恶意代码分析技术也越来越感兴趣。本文将教您如何分析一种特殊类型的恶意PDF文件:它们可以利用内嵌JavaScript解释器的安全漏洞。通过阅读本文,还有助于分析其他类型的恶意PDF文件,如利用PDF解析器内的安全漏洞的情形。虽然几乎所有的恶意PDF文档的攻击目标都是Windows操作系统,但是这里介绍的PDF语言是独立于操作系统的,它同时适用于在Windows、Linux和 OSX上的PDF文档。

  一、PDF中的Hello World

  现在,我们从手工制作一个最简单的PDF文档开始入手,该文档只是在一个页面中显示文字Hello World而已。您很可能从未见过如此简陋的文档,但是它很适合于本文的需要,因为我们只对一个PDF文档的内部构造感兴趣。我们的文档仅仅包含显示一个页面所必需的最基本元素,如果您为该文件添加更多的格式的话,可读性会更好一些。该文档的特性之一是,只包含有一些ASCII字符,因此即使使用记事本这样最简单的编辑器,同样也能阅读它的内容。另一个特性是,其中含有大量(多余的)空格和缩排,这使得这个PDF的结构更加突出。最后一个特性是,其中的内容没有进行压缩处理。

  二、头部

  每个PDF文档必须以标明其为PDF文档的一行代码(即幻数)开头;它还规定了用于描述这个文档的PDF语言规范版本号:%PDF-1.1。

  在一个PDF文档中,以符号%开头的行都是注释行,注释行的内容将被忽略,但是有两个例外:

文档的开头:%PDF-X.Y

文档的结尾:%%EOF

  三、对象

  在第一行之后,我们开始为我们的PDF文档添加对象,对象是PDF语言的基本元素。这些对象在文件中的出现顺序对页面显示时的布局没有任何影响。不过为简单起见,我们将按照逻辑顺序来介绍这些对象。需要注意的是,PDF语言是大小写敏感的。

  我们首先介绍的是catalog(即目录)对象,它告诉PDF阅读程序(例如Adobe的Acrobat Reader),为了装配这个文档,需要从哪里开始查找对象:

1 0 obj
/Type /Catalog
/Outlines 2 0 R
/Pages 3 0 R

  这实际上是一个间接对象,因为它具有一个编号,并且可以通过该编号来引用该对象。其语法很简单:一个编号、一个版本号、单词obj、对象本身,最后是单词endobj,如下所示:

1 0 obj
object
endobj

  通过联合使用对象编号和版本号,我们就能够唯一地引用一个对象。

  我们第一个对象catalog的类型是字典类型,字典类型在pdf文档中非常常见。该类型以符号<<开头,并以符合>>作为结束,如下所示:

dictionary content

  字典的元素由键和值两部分组成,也就是说一个元素就是一个名/值对,即数据有一个名称,还有一个与之相对应的值;字典不仅可以存放元素,而且还能存放对象甚至其他字典。大部分字典都是利用第一个元素来声明自身的类型,该元素以/type为键,其后跟一个类型本身的名称(对本例而言就是/Catalog)为值:

(/Type /Catalog)

  对象catalog必须给出在这个PDF中能找到的页面(对应于pages对象)和大纲(对应于outline对象),如下:

/Outlines 2 0 R
/Pages 3 0 R

2 0 R和3 0 R 分别表示引用间接对象2和间接对象3。间接对象2描述大纲,间接对象3描述页面。

  下面开始为我们的PDF文档添加第二个间接对象:

2 0 obj
/Type /Outlines
/Count 0
endobj

  通过前面对间接对象1的说明,您现在应该对这个对象的语法并不陌生了。这个对象是一个/Outlines类型的字典。它具有一个键为/Count、值为0的元素,这意味着这个PDF文档没有大纲。我们可以通过编号2和版本0来引用这个对象。

  让我们总结一下我们的PDF文档已有的内容:

PDF标识行

间接对象1:catalog

间接对象2:outline

  在添加文字页面之前,让我们演示PDF语言的另一个特性。我们的1号对象catalog引用了我们的2号对象outline,如图1所示。

引用间接对象


图1   引用间接对象

  PDF语言还允许我们把2号对象直接嵌入到1号对象中,如图2所示。



图2   被嵌入到对象中的间接对象

  事实上,outline对象的长度只有一行,并且对语义也没有什么影响,现在只是为了可读性才加上。先不管它,我们继续组装我们的PDF文档。我们前面定义了catalog(目录)和outlines(大纲)对象,接下来还得定义我们的页面。

  除/Kids元素之外,下面的代码应该很容易理解。Kids 元素是一个页面列表;一个列表必须用方括弧括住。因此依据这个Pages对象来看,我们的文档中只有一个页面;这个页面的具体规定,见间接对象4(注意引用4 0 R ):

3 0 obj
/Type /Pages
/Kids [4 0 R]
/Count 1
endobj

  要描述页面,我们必须规定页面的内容、用于显示这个页面的资源以及页面的大小。这些任务可由下面的代码来完成:

4 0 obj
/Type /Page
/Parent 3 0 R
/MediaBox [0 0 612 792]
/Contents 5 0 R
/Resources <<
/ProcSet [/PDF /Text]
/Font << /F1 6 0 R >>
endobj

  页面内容是由间接对象5来规定的。/MediaBox 是页面的尺寸。这个页面所用的资源是字体和PDF文字绘制例程。我们在间接对象6中将字体规定为[F1]。

  间接对象5中存放的是页面内容,它是一种特殊的对象,即流对象。流对象可以用来保存由单词stream和endstream包围的对象内容。流对象的好处是允许使用多种类型的编码技术(在PDF语言中称为过滤器),例如压缩(例如zlib FlateDecode编码)。考虑到易读性,我们没有在这个流中实施压缩处理:

5 0 obj
/Length 43 >>
stream
BT /F1 24 Tf 100 700 Td (Hello World) Tj ET
endstream
endobj

  这个流的内容是一组PDF文字绘制指令。这些指令是由BT和ET括起来的,实际上就是命令绘制例程做下面的事情:

  使用大小为24的F1字体

  转到100 700位置处

  绘制文字:Hello World

  在PDF语言中,字符串必须用圆括号括起来。

  我们的PDF文档已经基本上组装好了。我们需要的最后一个对象是font(字体)对象:

6 0 obj
/Type /Font
Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /MacRomanEncoding
endobj

  现在,阅读这个结构您应该没有问题了。

  四、尾部

  上面就是绘制一个页面所需的全部对象。但是仅有这些内容还不足以使阅读程序(即显示pdf文档的程序,如Adobe的Acrobat Reader)来读取和显示我们的PDF文档。绘制例程需要知道文档描述起始于哪个对象(即root对象),以及每个对象的索引之类的技术细节。

  每个对象的索引称为交叉引用xref,它描述每个间接对象的编号、版本和绝对的文件位置。PDF文档中的第一个索引必须从版本为65535的0号对象开始:

  标识符xref后面的第一个数字是第一个间接对象(这里是0号对象)的编号,第二个数字是xref表(7个表项)的大小。

  第一栏是间接对象的绝对位置。第二行的值12表明间接对象1的起始地址距文件开头为12字节。第二栏是版本,第三栏指出对象正在使用(用n表示)还是已经释放(用f表示)。

定义交叉引用之后,我们在尾部中定义root对象:

trailer

/Size 7

/Root 1 0 R

  不难看出,这是一个字典。最后,我们需要利用xref元素的绝对位置和幻数%%EOF来结束这个PDF文档:

startxref
644
%%EOF

  其中,644是在这个PDF文件内的xref的绝对位置。

  五、PDF文档基础知识的回顾

  我们一旦了解了PDF语言的语法和语义,就能轻松构建一个简单的PDF文档。为了便于阅读,我们在清单1中给出了完整的PDF文档。

%PDF-1.1
1 0 obj
/Type /Catalog /Outlines 2 0 R /Pages 3 0 R
endobj
2 0 obj
/Type /Outlines /Count 0
endobj
3 0 obj
/Type /Pages /Kids [4 0 R] /Count 1
endobj
4 0 obj
/Type /Page /Parent 3 0 R /MediaBox [0 0 612 792] /Contents 5 0 R
/Resources <<            
/ProcSet [/PDF /Text]            
/Font << /F1 6 0 R >>
endobj
5 0 obj
/Length 43 >>
stream
BT /F1 24 Tf 100 700 Td (Hello World) Tj ET
endstream
endobj
6 0 obj
/Type /Font /
Subtype /Type1 
/Name /F1 
/BaseFont /Helvetica 
/Encoding /MacRomanEncoding
endobj
xref
0 7
0000000000 65535 f
0000000012 00000 n
0000000089 00000 n
0000000145 00000 n
0000000214 00000 n
0000000419 00000 n
0000000520 00000 n
trailer
/Size 7 /Root 1 0 R
startxref
644
%%EOF

  清单 1  完整的PDF文档页面内容

  六、添加有效载荷

  因为我们想要分析带有JavaScript有效载荷的恶意PDF文档,因此需要了解如何添加JavaScript代码并设法使其运行。PDF语言支持为事件关联相应的动作。举例来说,当某个页面被查看的时候,可以执行相应的动作(例如访问一个网站)。我们感兴趣的是在打开一个PDF文档的时候执行某个动作。通过为catalog对象添加一个/OpenAction键,我们可以让PDF 文档在打开时无需人工介入就执行某个动作。

1 0 obj
/Type /Catalog
/Outlines 2 0 R
/Pages 3 0 R
/OpenAction 7 0 R
endobj

  当打开我们的PDF文档的时候,间接对象7所规定的动作将被执行。我们可以规定一个URI动作。一个URI动作能够自动地打开一个URI,在我们这个例子中是一个URL:

7 0 obj
/Type /Action
/S /URI
/URI (https://DidierStevens.com)
endobj
ss 

  七、内嵌的 JavaScript

  PDF语言支持内嵌的 JavaScript。然而,这个JavaScript引擎在与底层操作系统的交互方面能力非常有限,所以根本没法干坏事。举例来说,嵌入到一个PDF文档的JavaScript代码不能访问任何文件。所以,恶意PDF文档必须利用某些安全漏洞才能摆脱JavaScript引擎的限制来执行任意的代码。我们可以使用下面的JavaScript动作,在PDF文档打开时添加并执行一些JavaScript脚本:

7 0 obj
/Type /Action
/S /JavaScript
/JS (console.println(“Hello”))
endobj

  下面的代码将执行一个向JavaScript调试控制台显示Hello的脚本:

console.println(“Hello”)

  八、安全漏洞的利用

  去年,许多恶意PDF文档利用了PDF的util.printf方法的JavaScript安全漏洞来发动攻击。Core Security Technologies发表了一个通报,其中含有一个PoC,如下所示:

var num = 12999999999999999999888888…..
util.printf(„%45000f”,num)

  当这个JavaScript将被嵌入到一个PDF文档并在(在 Windows XP SP2上使用Adobe Acrobat Reader 8.1.2)打开的时候,它将试图执行地址0x30303030的代码而造成访问越界。这意味着,通过缓冲区溢出,执行PoC将跳转至地址0x30303030(0x30是ASCII字符0的十六进制表示法)(即PoC一执行,控制流程(系统控制权)就交给0x30303030处的指令)。因此要想利用该漏洞,我们需要编写我们的程序(shellcode),当然该程序需要从0x30303030处开始执行。

  使用内嵌的JavaScript的问题是,我们不能直接写内存。Heap Spraying(堆喷射)是一种较易获得任意代码执行Exploit的技术手段。每当我们在JavaScript中声明字符串并为其赋值时,这个字符串就会被写到一段内存中,这段内存是从堆中分配的,所谓堆就是专门预留给程序变量的一部分内存。 我们没有影响被使用的那些内存,所以我们不能命令JavaScript使用地址0x30303030的内存。但是如果我们分配大量的字符串,那么很可能其中的一个字符串分配的内存中包含了地址0x30303030。 分配许许多多的字符串称为Heap Spraying(堆喷射)。

  如果我们在堆喷射之后执行我们的PoC,就很有可能得到一个从地址0x30303030之前的某处开始、从地址0x30303030之后的某处终止的字符串,这样的话,该字符串中(起始于地址0x30303030)那些字节就会被CPU当作机器代码语句来执行。

  但是,如何让我们指定的字符串包含用来利用起始于地址0x30303030的机器指令的正确语句呢? 同样,我们也无法直接完成这个任务;我们需要一种迂回战术。

  如果我们设法使一个字符串被CPU当作机器代码程序(shellcode)来解释的话,那么CPU将开始执行我们从地址0x30303030开始的程序。不过这个方法不太理想;我们的程序必须从它的第一条指令开始执行,而不是从中间的某个地方开始执行。为了解决这个问题,我们需要在程序前面填充大量NOP 指令。 我们在用于堆喷射的字符串中存储这个NOP-sled,继之以我们shellcode。 NOP-sled是一个特殊程序,它的特性是每个指令的长度都是单字,而且每个指令都没有时间的操作(NOP,即空操作 ),那就是说 CPU不断执行下一个NOP指令,如此下去直到它到达我们的shellcode并且执行它(滑下NOP-sled)。

  下面是一个堆喷射的范例,实际取自一个带有NOP-sled和shellcode的恶意PDF文档(参见 图 3)。

JavaScript堆喷射
 
图3 JavaScript堆喷射

  Sccs是带有shellcode的字符串,bgbl是带有NOP-code的字符串。

  因为shellcode常常必须很小,所以它将通过网络下载另一个程序(恶意软件)并执行它。对于pdf文档来说,还有一种方法可用。第二阶段的程序可以嵌入到PDF文档,而shellcode可以从PDF文档提取并且执行。

  九、分析恶意PDF文档

  事实上,所有的pdf文档都包含非ASCII字符,因此我们需要使用一个十六进制编辑器来分析它们。我们打开一个可疑的PDF文档,并搜索字符串JavaScript(参见图 4)。

JavaScript 对象
 
图4    JavaScript 对象

虽然只是有一点用于格式化对象的空格,但是您应该认出PDF对象的结构:对象31是一个JavaScript动作/S /JavaScript,脚本本身没有包含在这个对象中,但是可以在对象32(注意引用3 0 R)中找到。 搜索字符串“31 0 R”,我们发现对象16引用了对象31“/AA <> ”,以及一个页面/Type /Page ,如图5所示。

页对象
 
图5 页对象

  /AA 是一个注释动作,这意味着当这个页面被查看的时候这个动作就会执行。因此,我们知道:当这个PDF文档被打开的时候,它将执行一个JavaScript脚本。 让我们看看这个脚本(对象32 )的样子。

  对象32是一个流对象,而且它是经过压缩的(/Filter [/FlateDecode]),见图 6。

流对象
 
图6  流对象

  为了对它进行解压,我们可以提取二进制流(1154字节长),并通过一个简单的Perl或者Python程序对它进行解压。使用Python语言,我们只需要导入zlib,然后就可以对数据进行解压了,假设我们已经将我们的二进制流存储在data中了:

import zlib
decompressed = zlib.decompress(data)

  然而有一点非常清楚:那就是解压后的脚本是恶意的,它会对函数collectEmailInfo中的一个安全漏洞加以利用,如图7所示。

利用collectEmailInfo
 
图7   利用collectEmailInfo

  十、结束语

  随着恶意PDF文件日益增多,人们对这种文档的恶意代码分析技术也越来越感兴趣。本文向读者详细介绍了如何分析一种特殊类型的恶意PDF文件:它们可以利用内嵌JavaScript解释器的安全漏洞。当然,有了本文的基础,在分析其他类型的恶意PDF文件,如利用PDF解析器内的安全漏洞的情形的时候,您也能触类旁通。需要说明的是,虽然几乎所有的恶意PDF文档的攻击目标都是Windows操作系统,但是这里介绍的PDF语言是独立于操作系统的,它同时适用于在Windows、Linux和OSX上的PDF文档。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

电子邮件地址不会被公开。 必填项已用*标注

敬请读者发表评论,本站保留删除与本文无关和不雅评论的权力。

作者

宇文
宇文

相关推荐