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

原创
安全 黑客攻防
本文将教您如何分析一种特殊类型的恶意PDF文件:它们可以利用内嵌JavaScript解释器的安全漏洞。

【51CTO.com独家特稿】随着恶意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

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

四、尾部

上面就是绘制一个页面所需的全部对象。但是仅有这些内容还不足以使阅读程序(即显示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文档页面内容#p#

六、添加有效载荷

因为我们想要分析带有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")
#p#

八、安全漏洞的利用

去年,许多恶意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)。

 
图3 JavaScript堆喷射

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

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

九、分析恶意PDF文档

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

 
图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所示。

 
图7   利用collectEmailInfo

十、结束语

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

【51CTO.COM 独家特稿,转载请注明出处及作者!】

【编辑推荐】

  1. Adobe曝重大安全漏洞,小心打开PDF文件
  2. 看PDF和Flash中毒后快速解决方法
责任编辑:许凤丽 来源: 51CTO.com
相关推荐

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2019-01-07 15:29:07

HadoopYarn架构调度器

2017-07-02 18:04:53

块加密算法AES算法

2012-05-21 10:06:26

FrameworkCocoa

2021-07-20 15:20:02

FlatBuffers阿里云Java

2022-09-26 09:01:15

语言数据JavaScript

2020-12-09 09:59:40

Redis原理实战

2018-11-09 16:24:25

物联网云计算云系统

2019-11-11 14:51:19

Java数据结构Properties

2012-02-21 13:55:45

JavaScript

2021-04-27 08:54:43

ConcurrentH数据结构JDK8

2022-12-02 09:13:28

SeataAT模式

2022-01-11 07:52:22

CSS 技巧代码重构

2009-11-30 16:46:29

学习Linux

2019-12-04 10:13:58

Kubernetes存储Docker

2022-10-31 09:00:24

Promise数组参数

2009-11-18 13:30:37

Oracle Sequ

2022-11-09 08:06:15

GreatSQLMGR模式

2010-07-26 12:57:12

OPhone游戏开发
点赞
收藏

51CTO技术栈公众号