深入淺出實(shí)戰(zhàn)攻防惡意PDF文檔
原創(chuàng)【51CTO.com獨(dú)家特稿】隨著惡意PDF文件日益增多,人們對(duì)這種文檔的惡意代碼分析技術(shù)也越來越感興趣。本文將教您如何分析一種特殊類型的惡意PDF文件:它們可以利用內(nèi)嵌JavaScript解釋器的安全漏洞。通過閱讀本文,還有助于分析其他類型的惡意PDF文件,如利用PDF解析器內(nèi)的安全漏洞的情形。雖然幾乎所有的惡意PDF文檔的攻擊目標(biāo)都是Windows操作系統(tǒng),但是這里介紹的PDF語言是獨(dú)立于操作系統(tǒng)的,它同時(shí)適用于在Windows、Linux和OSX上的PDF文檔。
一、PDF中的Hello World
現(xiàn)在,我們從手工制作一個(gè)最簡單的PDF文檔開始入手,該文檔只是在一個(gè)頁面中顯示文字Hello World而已。您很可能從未見過如此簡陋的文檔,但是它很適合于本文的需要,因?yàn)槲覀冎粚?duì)一個(gè)PDF文檔的內(nèi)部構(gòu)造感興趣。我們的文檔僅僅包含顯示一個(gè)頁面所必需的最基本元素,如果您為該文件添加更多的格式的話,可讀性會(huì)更好一些。該文檔的特性之一是,只包含有一些ASCII字符,因此即使使用記事本這樣最簡單的編輯器,同樣也能閱讀它的內(nèi)容。另一個(gè)特性是,其中含有大量(多余的)空格和縮排,這使得這個(gè)PDF的結(jié)構(gòu)更加突出。最后一個(gè)特性是,其中的內(nèi)容沒有進(jìn)行壓縮處理。
二、頭部
每個(gè)PDF文檔必須以標(biāo)明其為PDF文檔的一行代碼(即幻數(shù))開頭;它還規(guī)定了用于描述這個(gè)文檔的PDF語言規(guī)范版本號(hào):%PDF-1.1。
在一個(gè)PDF文檔中,以符號(hào)%開頭的行都是注釋行,注釋行的內(nèi)容將被忽略,但是有兩個(gè)例外:
文檔的開頭:%PDF-X.Y
文檔的結(jié)尾:%%EOF
三、對(duì)象
在第一行之后,我們開始為我們的PDF文檔添加對(duì)象,對(duì)象是PDF語言的基本元素。這些對(duì)象在文件中的出現(xiàn)順序?qū)撁骘@示時(shí)的布局沒有任何影響。不過為簡單起見,我們將按照邏輯順序來介紹這些對(duì)象。需要注意的是,PDF語言是大小寫敏感的。
我們首先介紹的是catalog(即目錄)對(duì)象,它告訴PDF閱讀程序(例如Adobe的Acrobat Reader),為了裝配這個(gè)文檔,需要從哪里開始查找對(duì)象:
1 0 obj /Type /Catalog /Outlines 2 0 R /Pages 3 0 R |
這實(shí)際上是一個(gè)間接對(duì)象,因?yàn)樗哂幸粋€(gè)編號(hào),并且可以通過該編號(hào)來引用該對(duì)象。其語法很簡單:一個(gè)編號(hào)、一個(gè)版本號(hào)、單詞obj、對(duì)象本身,最后是單詞endobj,如下所示:
1 0 obj object endobj |
通過聯(lián)合使用對(duì)象編號(hào)和版本號(hào),我們就能夠唯一地引用一個(gè)對(duì)象。
我們第一個(gè)對(duì)象catalog的類型是字典類型,字典類型在pdf文檔中非常常見。該類型以符號(hào)<<開頭,并以符合>>作為結(jié)束,如下所示:
dictionary content |
字典的元素由鍵和值兩部分組成,也就是說一個(gè)元素就是一個(gè)名/值對(duì),即數(shù)據(jù)有一個(gè)名稱,還有一個(gè)與之相對(duì)應(yīng)的值;字典不僅可以存放元素,而且還能存放對(duì)象甚至其他字典。 大部分字典都是利用第一個(gè)元素來聲明自身的類型,該元素以/type為鍵,其后跟一個(gè)類型本身的名稱(對(duì)本例而言就是/Catalog)為值:
(/Type /Catalog) |
對(duì)象catalog必須給出在這個(gè)PDF中能找到的頁面(對(duì)應(yīng)于pages對(duì)象)和大綱(對(duì)應(yīng)于outline對(duì)象),如下:
/Outlines 2 0 R /Pages 3 0 R |
2 0 R和3 0 R 分別表示引用間接對(duì)象2和間接對(duì)象3。間接對(duì)象2描述大綱,間接對(duì)象3描述頁面。
下面開始為我們的PDF文檔添加第二個(gè)間接對(duì)象:
2 0 obj /Type /Outlines /Count 0 endobj |
通過前面對(duì)間接對(duì)象1的說明,您現(xiàn)在應(yīng)該對(duì)這個(gè)對(duì)象的語法并不陌生了。這個(gè)對(duì)象是一個(gè)/Outlines類型的字典。它具有一個(gè)鍵為/Count、值為0的元素,這意味著這個(gè)PDF文檔沒有大綱。我們可以通過編號(hào)2和版本0來引用這個(gè)對(duì)象。
讓我們總結(jié)一下我們的PDF文檔已有的內(nèi)容:
PDF標(biāo)識(shí)行
間接對(duì)象1:catalog
間接對(duì)象2:outline
在添加文字頁面之前,讓我們演示PDF語言的另一個(gè)特性。我們的1號(hào)對(duì)象catalog引用了我們的2號(hào)對(duì)象outline,如圖1所示。
![]() |
圖1 引用間接對(duì)象 |
PDF語言還允許我們把2號(hào)對(duì)象直接嵌入到1號(hào)對(duì)象中,如圖2所示。
![]() |
圖2 被嵌入到對(duì)象中的間接對(duì)象 |
事實(shí)上,outline對(duì)象的長度只有一行,并且對(duì)語義也沒有什么影響,現(xiàn)在只是為了可讀性才加上。先不管它,我們繼續(xù)組裝我們的PDF文檔。我們前面定義了catalog(目錄)和outlines(大綱)對(duì)象,接下來還得定義我們的頁面。
除/Kids元素之外,下面的代碼應(yīng)該很容易理解。Kids 元素是一個(gè)頁面列表;一個(gè)列表必須用方括弧括住。因此依據(jù)這個(gè)Pages對(duì)象來看,我們的文檔中只有一個(gè)頁面;這個(gè)頁面的具體規(guī)定,見間接對(duì)象4(注意引用4 0 R ):
3 0 obj /Type /Pages /Kids [4 0 R] /Count 1 endobj |
要描述頁面,我們必須規(guī)定頁面的內(nèi)容、用于顯示這個(gè)頁面的資源以及頁面的大小。這些任務(wù)可由下面的代碼來完成:
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 |
頁面內(nèi)容是由間接對(duì)象5來規(guī)定的。/MediaBox 是頁面的尺寸。這個(gè)頁面所用的資源是字體和PDF文字繪制例程。我們在間接對(duì)象6中將字體規(guī)定為[F1]。
間接對(duì)象5中存放的是頁面內(nèi)容,它是一種特殊的對(duì)象,即流對(duì)象。流對(duì)象可以用來保存由單詞stream和endstream包圍的對(duì)象內(nèi)容。流對(duì)象的好處是允許使用多種類型的編碼技術(shù)(在PDF語言中稱為過濾器),例如壓縮(例如zlib FlateDecode編碼)。考慮到易讀性,我們沒有在這個(gè)流中實(shí)施壓縮處理:
5 0 obj stream BT /F1 24 Tf 100 700 Td (Hello World) Tj ET endstream endobj |
這個(gè)流的內(nèi)容是一組PDF文字繪制指令。這些指令是由BT和ET括起來的,實(shí)際上就是命令繪制例程做下面的事情:
使用大小為24的F1字體
轉(zhuǎn)到100 700位置處
繪制文字:Hello World
在PDF語言中,字符串必須用圓括號(hào)括起來。
我們的PDF文檔已經(jīng)基本上組裝好了。我們需要的最后一個(gè)對(duì)象是font(字體)對(duì)象:
6 0 obj /Type /Font Subtype /Type1 /Name /F1 /BaseFont /Helvetica /Encoding /MacRomanEncoding endobj |
現(xiàn)在,閱讀這個(gè)結(jié)構(gòu)您應(yīng)該沒有問題了。#p#
四、尾部
上面就是繪制一個(gè)頁面所需的全部對(duì)象。但是僅有這些內(nèi)容還不足以使閱讀程序(即顯示pdf文檔的程序,如Adobe的Acrobat Reader)來讀取和顯示我們的PDF文檔。繪制例程需要知道文檔描述起始于哪個(gè)對(duì)象(即root對(duì)象),以及每個(gè)對(duì)象的索引之類的技術(shù)細(xì)節(jié)。
每個(gè)對(duì)象的索引稱為交叉引用xref,它描述每個(gè)間接對(duì)象的編號(hào)、版本和絕對(duì)的文件位置。PDF文檔中的第一個(gè)索引必須從版本為65535的0號(hào)對(duì)象開始:
標(biāo)識(shí)符xref后面的第一個(gè)數(shù)字是第一個(gè)間接對(duì)象(這里是0號(hào)對(duì)象)的編號(hào),第二個(gè)數(shù)字是xref表(7個(gè)表項(xiàng))的大小。
第一欄是間接對(duì)象的絕對(duì)位置。第二行的值12表明間接對(duì)象1的起始地址距文件開頭為12字節(jié)。第二欄是版本,第三欄指出對(duì)象正在使用(用n表示)還是已經(jīng)釋放(用f表示)。
定義交叉引用之后,我們在尾部中定義root對(duì)象:
trailer /Size 7 /Root 1 0 R |
不難看出,這是一個(gè)字典。最后,我們需要利用xref元素的絕對(duì)位置和幻數(shù)%%EOF來結(jié)束這個(gè)PDF文檔:
startxref 644 %%EOF |
其中,644是在這個(gè)PDF文件內(nèi)的xref的絕對(duì)位置。
五、PDF文檔基礎(chǔ)知識(shí)的回顧
我們一旦了解了PDF語言的語法和語義,就能輕松構(gòu)建一個(gè)簡單的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文檔頁面內(nèi)容#p#
六、添加有效載荷
因?yàn)槲覀兿胍治鰩в蠮avaScript有效載荷的惡意PDF文檔,因此需要了解如何添加JavaScript代碼并設(shè)法使其運(yùn)行。PDF語言支持為事件關(guān)聯(lián)相應(yīng)的動(dòng)作。舉例來說,當(dāng)某個(gè)頁面被查看的時(shí)候,可以執(zhí)行相應(yīng)的動(dòng)作(例如 訪問一個(gè)網(wǎng)站)。我們感興趣的是在打開一個(gè)PDF文檔的時(shí)候執(zhí)行某個(gè)動(dòng)作。通過為catalog對(duì)象添加一個(gè)/OpenAction鍵,我們可以讓PDF文檔在打開時(shí)無需人工介入就執(zhí)行某個(gè)動(dòng)作。
1 0 obj /Type /Catalog /Outlines 2 0 R /Pages 3 0 R /OpenAction 7 0 R endobj |
當(dāng)打開我們的PDF文檔的時(shí)候,間接對(duì)象7所規(guī)定的動(dòng)作將被執(zhí)行。我們可以規(guī)定一個(gè)URI動(dòng)作。一個(gè)URI動(dòng)作能夠自動(dòng)地打開一個(gè)URI,在我們這個(gè)例子中是一個(gè)URL:
7 0 obj /Type /Action /S /URI /URI (https://DidierStevens.com) endobj ss |
七、內(nèi)嵌的 JavaScript
PDF語言支持內(nèi)嵌的 JavaScript。然而,這個(gè)JavaScript引擎在與底層操作系統(tǒng)的交互方面能力非常有限,所以根本沒法干壞事。 舉例來說,嵌入到一個(gè)PDF文檔的JavaScript代碼不能訪問任何文件。所以,惡意PDF文檔必須利用某些安全漏洞才能擺脫JavaScript引擎的限制來執(zhí)行任意的代碼。我們可以使用下面的JavaScript動(dòng)作,在PDF文檔打開時(shí)添加并執(zhí)行一些JavaScript腳本:
7 0 obj /Type /Action /S /JavaScript /JS (console.println("Hello")) endobj |
下面的代碼將執(zhí)行一個(gè)向JavaScript調(diào)試控制臺(tái)顯示Hello的腳本:
console.println("Hello") |
八、安全漏洞的利用
去年,許多惡意PDF文檔利用了PDF的util.printf方法的JavaScript安全漏洞來發(fā)動(dòng)攻擊。Core Security Technologies發(fā)表了一個(gè)通報(bào),其中含有一個(gè)PoC,如下所示:
var num = 12999999999999999999888888..... util.printf(„%45000f”,num) |
當(dāng)這個(gè)JavaScript將被嵌入到一個(gè)PDF文檔并在(在 Windows XP SP2上使用Adobe Acrobat Reader 8.1.2)打開的時(shí)候,它將試圖執(zhí)行地址0x30303030的代碼而造成訪問越界。 這意味著,通過緩沖區(qū)溢出,執(zhí)行PoC將跳轉(zhuǎn)至地址0x30303030(0x30是ASCII字符0的十六進(jìn)制表示法)(即PoC一執(zhí)行,控制流程(系統(tǒng)控制權(quán))就交給0x30303030處的指令)。 因此要想利用該漏洞,我們需要編寫我們的程序(shellcode),當(dāng)然該程序需要從0x30303030處開始執(zhí)行。
使用內(nèi)嵌的JavaScript的問題是,我們不能直接寫內(nèi)存。Heap Spraying(堆噴射)是一種較易獲得任意代碼執(zhí)行Exploit的技術(shù)手段。 每當(dāng)我們在JavaScript中聲明字符串并為其賦值時(shí),這個(gè)字符串就會(huì)被寫到一段內(nèi)存中,這段內(nèi)存是從堆中分配的,所謂堆就是專門預(yù)留給程序變量的一部分內(nèi)存。 我們沒有影響被使用的那些內(nèi)存,所以我們不能命令JavaScript使用地址0x30303030的內(nèi)存。 但是如果我們分配大量的字符串,那么很可能其中的一個(gè)字符串分配的內(nèi)存中包含了地址0x30303030。 分配許許多多的字符串稱為Heap Spraying(堆噴射)。
如果我們在堆噴射之后執(zhí)行我們的PoC,就很有可能得到一個(gè)從地址0x30303030之前的某處開始、從地址0x30303030之后的某處終止的字符串,這樣的話,該字符串中(起始于地址0x30303030)那些字節(jié)就會(huì)被CPU當(dāng)作機(jī)器代碼語句來執(zhí)行。
但是,如何讓我們指定的字符串包含用來利用起始于地址0x30303030的機(jī)器指令的正確語句呢? 同樣,我們也無法直接完成這個(gè)任務(wù);我們需要一種迂回戰(zhàn)術(shù)。
如果我們設(shè)法使一個(gè)字符串被CPU當(dāng)作機(jī)器代碼程序(shellcode)來解釋的話,那么CPU將開始執(zhí)行我們從地址0x30303030開始的程序。 不過這個(gè)方法不太理想;我們的程序必須從它的第一條指令開始執(zhí)行,而不是從中間的某個(gè)地方開始執(zhí)行。為了解決這個(gè)問題,我們需要在程序前面填充大量NOP指令。 我們在用于堆噴射的字符串中存儲(chǔ)這個(gè)NOP-sled,繼之以我們shellcode。 NOP-sled是一個(gè)特殊程序,它的特性是每個(gè)指令的長度都是單字,而且每個(gè)指令都沒有時(shí)間的操作(NOP,即空操作 ),那就是說 CPU不斷執(zhí)行下一個(gè)NOP指令,如此下去直到它到達(dá)我們的shellcode并且執(zhí)行它(滑下NOP-sled)。
下面是一個(gè)堆噴射的范例,實(shí)際取自一個(gè)帶有NOP-sled和shellcode的惡意PDF文檔(參見 圖 3)。
![]() |
圖3 JavaScript堆噴射 |
Sccs是帶有shellcode的字符串,bgbl是帶有NOP-code的字符串。
因?yàn)閟hellcode常常必須很小,所以它將通過網(wǎng)絡(luò)下載另一個(gè)程序(惡意軟件)并執(zhí)行它。對(duì)于pdf文檔來說,還有一種方法可用。第二階段的程序可以嵌入到PDF文檔,而shellcode可以從PDF文檔提取并且執(zhí)行。
九、分析惡意PDF文檔
事實(shí)上,所有的pdf文檔都包含非ASCII字符,因此我們需要使用一個(gè)十六進(jìn)制編輯器來分析它們。我們打開一個(gè)可疑的PDF文檔,并搜索字符串JavaScript(參見圖 4)。
![]() |
圖4 JavaScript 對(duì)象 |
雖然只是有一點(diǎn)用于格式化對(duì)象的空格,但是您應(yīng)該認(rèn)出PDF對(duì)象的結(jié)構(gòu):對(duì)象31是一個(gè)JavaScript動(dòng)作/S /JavaScript,腳本本身沒有包含在這個(gè)對(duì)象中,但是可以在對(duì)象32(注意引用3 0 R)中找到。 搜索字符串“31 0 R”,我們發(fā)現(xiàn)對(duì)象16引用了對(duì)象31“/AA <> ”,以及一個(gè)頁面/Type /Page ,如圖5所示。
![]() |
圖5 頁對(duì)象 |
/AA 是一個(gè)注釋動(dòng)作,這意味著當(dāng)這個(gè)頁面被查看的時(shí)候這個(gè)動(dòng)作就會(huì)執(zhí)行。因此,我們知道:當(dāng)這個(gè)PDF文檔被打開的時(shí)候,它將執(zhí)行一個(gè)JavaScript腳本。 讓我們看看這個(gè)腳本(對(duì)象32 )的樣子。
對(duì)象32是一個(gè)流對(duì)象,而且它是經(jīng)過壓縮的(/Filter [/FlateDecode]),見圖 6。
![]() |
圖6 流對(duì)象 |
為了對(duì)它進(jìn)行解壓,我們可以提取二進(jìn)制流(1154字節(jié)長),并通過一個(gè)簡單的Perl或者Python程序?qū)λM(jìn)行解壓。使用Python語言,我們只需要導(dǎo)入zlib,然后就可以對(duì)數(shù)據(jù)進(jìn)行解壓了,假設(shè)我們已經(jīng)將我們的二進(jìn)制流存儲(chǔ)在data中了:
import zlib decompressed = zlib.decompress(data) |
然而有一點(diǎn)非常清楚:那就是解壓后的腳本是惡意的,它會(huì)對(duì)函數(shù)collectEmailInfo中的一個(gè)安全漏洞加以利用,如圖7所示。
![]() |
圖7 利用collectEmailInfo |
十、結(jié)束語
隨著惡意PDF文件日益增多,人們對(duì)這種文檔的惡意代碼分析技術(shù)也越來越感興趣。本文向讀者詳細(xì)介紹了如何分析一種特殊類型的惡意PDF文件:它們可以利用內(nèi)嵌JavaScript解釋器的安全漏洞。當(dāng)然,有了本文的基礎(chǔ),在分析其他類型的惡意PDF文件,如利用PDF解析器內(nèi)的安全漏洞的情形的時(shí)候,您也能觸類旁通。需要說明的是,雖然幾乎所有的惡意PDF文檔的攻擊目標(biāo)都是Windows操作系統(tǒng),但是這里介紹的PDF語言是獨(dú)立于操作系統(tǒng)的,它同時(shí)適用于在Windows、Linux和OSX上的PDF文檔。
【51CTO.COM 獨(dú)家特稿,轉(zhuǎn)載請注明出處及作者!】
【編輯推薦】