隨著惡意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,如下所示:
通過聯合使用對象編號和版本號,我們就能夠唯一地引用一個對象。
我們第一個對象catalog的類型是字典類型,字典類型在pdf文檔中非常常見。該類型以符號<<開頭,並以符合>>作為結束,如下所示:
字典的元素由鍵和值兩部分組成,也就是說一個元素就是一個名/值對,即數據有一個名稱,還有一個與之相對應的值;字典不僅可以存放元素,而且還能存 放對象甚至其他字典。 大部分字典都是利用第一個元素來聲明自身的類型,該元素以/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文檔:
其中,644是在這個PDF文件內的xref的絕對位置。
五、PDF文檔基礎知識的回顧
我們一旦瞭解了PDF語言的語法和語義,就能輕鬆構建一個簡單的PDF文檔。為了便於閱讀,我們在清單1中給出了完整的PDF文檔。
清單 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的腳本:
八、安全漏洞的利用
去年,許多惡意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 <</o 31 0 r>> 」,以及一個頁面/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文檔。
轉自:http://www.qqread.com/safe-tech/u470503_2.html