PDF文件解析與PDF惡代分析中的一些坑
一、簡介
最近在做文檔類的惡代檢測,寫個總結(jié)。
本篇文章負(fù)責(zé)介紹pdf文檔的格式以及惡代分析中需要注意的問題以及相應(yīng)工具推薦。希望能給各位做惡代分析時提供一些幫助。
后序會更新一些其他文檔格式解析與惡代分析內(nèi)容等,歡迎各位關(guān)注。
二、PDF文件格式介紹
PDF(便攜式文件格式,Portable Document Format)是由Adobe Systems于1993年基于文件交換所發(fā)展出的一種文件格式。Adobe公司素有“漏洞之王”的美譽,所以學(xué)習(xí)PDF文件格式對研究分析漏洞具有極大幫助。PDF格式較為復(fù)雜,本文以研究漏洞的目的分析PDF格式,探尋如何找出并分析PDF中存在的惡意代碼,而并非做一個詳細(xì)的PDF parser解析器,因此會省略對不相關(guān)關(guān)鍵字的介紹,請各位留意。
PDF的結(jié)構(gòu)可以從文件結(jié)構(gòu)和邏輯結(jié)構(gòu)兩個方面來理解。PDF的文件結(jié)構(gòu)指的是其文件物理組織方式,邏輯結(jié)構(gòu)則指的是其內(nèi)容的邏輯組織方式。
1. PDF的文件結(jié)構(gòu)
PDF文件格式包含以下4個部分:
- 文件頭——指明了該文件所遵從的PDF規(guī)范的版本號,它出現(xiàn)在PDF文件的第一行。
- 文件體——又稱對象集合,PDF文件的主要部分,由一系列對象組成。
- 交叉引用表——對對象進(jìn)行隨機存取而設(shè)立的一個間接對象的地址索引表。(實際以偏移+索引的方式儲存對象地址,下文會提及)
- 文件尾——聲明了交叉引用表的地址,即指明了文件體的根對象(Catalog),從而能夠找到PDF文件中各個對象體的位置,達(dá)到隨機訪問。另外還保存了PDF文件的加密等安全信息。
2. PDF文件格式圖示:
3. PDF文件的邏輯結(jié)構(gòu)
本段主要介紹PDF文件體的讀取方式。
作為一種結(jié)構(gòu)化的文件格式,一個PDF文檔是由一些稱為“對象”的模塊組成的。每個對象都有數(shù)字標(biāo)號,這樣的話可以這些對象就可以被其他的對象所引用。這些對象不需要按照順序出現(xiàn)在PDF文檔里面,出現(xiàn)的順序可以是任意的,比如一個PDF文件有3頁,第3頁可以出現(xiàn)在第1頁以前,對象按照順序出現(xiàn)唯一的好處就是能夠增加文件的可讀性,對象的信息以偏移+索引的形式保存在交叉引用表內(nèi)。
文件尾說明了根對象的對象號,并且說明交叉引用表的位置,通過對交叉引用表的查詢可以找到目錄對象(Catalog)。這個目錄對象是該PDF文檔的根對象,包含PDF文檔的大綱(outline)和頁面組對象(pages)引用。大綱對象是指PDF文件的書簽樹;頁面組對象(pages)包含該文件的頁面數(shù),各個頁面對象(page)的對象號。
4. PDF的層級結(jié)構(gòu)圖示:
頁面(page)對象為PDF中最重要的對象,包含如何顯示該頁面的信息,例如使用的字體,包含的內(nèi)容(文字,圖片等),頁面的大小。里面的信息可以直接給出,當(dāng)然里面的子項更多的是對其他對象的引用,真正的信息存放在其他對象里面。頁面中包含的信息是包含在一個稱為流(stream)的對象里,這個流的長度(字節(jié)數(shù))必須直接給出或指向另外一個對象(包含一個整數(shù)值,表明這個流的長度)。
可見stream流對象我們惡代分析需要獲取的重點。
5. 頁面信息圖示:
理解了上面的內(nèi)容之后,我們可以得出針對惡代分析的PDF文件的大致解析思路:
當(dāng)然,也可以采取針對PDF層級結(jié)構(gòu)的文檔解析方式,見仁見智,因人而異。
三、以二進(jìn)制文本解析Pdf文檔結(jié)構(gòu)
PDF文件是一種文本和二進(jìn)制混排的格式,但是Adobe更愿意讓人把它當(dāng)成二進(jìn)制的文件,所以,PDF文件可以直接拖入16進(jìn)制編輯器中打開。前面我們介紹了PDF的文件結(jié)構(gòu)以及邏輯結(jié)構(gòu),現(xiàn)在我們在16進(jìn)制編輯器中打開PDF文件,更直白的展示PDF的關(guān)鍵字段以及文件結(jié)構(gòu)。
- %PDF-1.6 #文件頭+版本號,16進(jìn)制讀取文件0x25 0x50 0x44 0x46開頭即證明是pdf文件
- %çóÏÓ #下面就是很多的Object對象
- 2 0 obj #Object對象,其中2是Obj順序號,0是Obj的版本號,obj也是對象開始的標(biāo)志
- << #<<>>之間為Object對象的字典內(nèi)容,包含關(guān)鍵字
- [/ICCBased 3 0 R]
- >>
- Endobj #Object結(jié)束關(guān)鍵字
- 7 0 obj
- <<
- /Filter
- /FlateDecode #流對象的壓縮方式為/FlateDecode
- /Length 148 #流對象的長度
- >>
- Stream #流對象
- #文件內(nèi)容信息,注:此處為直觀從而手動填寫的
- Endstream #流對象結(jié)束標(biāo)志
- Endobj
- 8 0 obj
- <<
- /Contents 7 0 R #頁面內(nèi)容對象的對象號為7
- /MediaBox [0 0 595.2 841.68] #頁面顯示大小,以像素為單位
- /PageIndex 1
- /Parent 1 0 R #其父對象號為1以及Pages對象
- /Resources #該頁包含的資源
- <</Font <</F4 4 0 R >> #字體的類型
- /Shading <<>>
- /XObject <<>> #外部對象
- /ColorSpace <</CS1 2 0 R>>
- >>
- /Type /Page
- >>
- Endobj
- 1 0 obj
- <<
- /Count 1 #頁碼數(shù)量為1
- /Kids [8 0 R ] #kids對象說明它的子頁對象為8
- /Type /Pages
- >>
- Endobj
- 13 0 obj
- <<
- /Author (? Cryin')
- /CreationDate (D:20100926145832+08'00')
- /Title (? PDF文件格式分析)
- >>
- endobj
- Xref #表示交叉引用表開始
- 0 14 #0表明引用表描述的對象編號從0開始,8說明共有8個對象#此行在交叉引用表中可出現(xiàn)多個
- 0000000000 65536 f #一般pdf都是以這行開始交叉引用表的,起始地址0和產(chǎn)生號
- 0000003195 00000 n #表示對象1,就是catalog,3195為偏移地址n表示對象在使用
- 0000000018 00000 n
- 0000000051 00000 n
- 0000003464 00000 n
- 0000000000 00000 f
- 0000004282 00000 n
- 0000002728 00000 n
- 0000002992 00000 n
- 0000003256 00000 n
- 0000003892 00000 n
- 0000003620 00000 n
- 0000008660 00000 n
- 0000008712 00000 n
- Trailer #說明文件尾對象開始
- <</Size 14 #14說明PDF文件對象數(shù)目
- /Root 12 0 R #說明跟對象號為12
- /Info 13 0 R>>
- startxref
- 8980 #8980為交叉引用表的偏移地址,此處為十進(jìn)制表示
- %%EOF #文件結(jié)束標(biāo)志
對于對象的額外解釋:如果一個樣本文件的交叉引用表格式如下
- xref
- 0 5
- 0000000000 00000 n #第1行
- 0000004996 00000 n #第2行
- 0000000022 00000 n #第3行
- 0000005101 00000 n #第4行
- 0000004976 00000 n #第5行
- 0000004996 00000 n #第n行
- 4 0 obj
- Xxxxx
- endobj
即交叉引用表中第五行順序數(shù)為4的對象,其偏移為4976
四、Pdf文件混淆
如圖,下面的樣本進(jìn)行了混淆
- %PDF-1.5
- 1 0 obj
- <</#54#79P#65 R 0 5
- O#70e#6e#41c#74i#6fn 3 Pages C#61ta#6c#6f#67>>
- endobj
解釋:<<>>代表obj對象之間的字典內(nèi)容,保存了流的關(guān)鍵字和特征信息,因此去除混淆是必要的第一步操作,pdf文件的混淆只出現(xiàn)在這里#54代表0x54,上面的內(nèi)容去除混淆之后即為
- 1 0 obj
- <</TyPe R 0 5 OpenAction 3 Pages
- Catalog>>
- endobj
五、關(guān)鍵字
下面介紹了PDF文件解析時所需要的關(guān)鍵字
- obj #obj對象開始
- endobj #obj對象結(jié)束
- stream #stream流對象開始
- endstream #stream流對象結(jié)束
- xref #交叉引用表開始
- trailer #文件尾對象開始
- startxref #交叉引用表結(jié)束
- /Page #文件頁數(shù)
- /Encrypt #是否加密
- /ObjStm #objectstreams的數(shù)量,objectstreams可包含其他Object對象,即嵌套
- /JS #代表javascript嵌有JavaScript代碼,可直接提取惡意代碼
- /JavaScript #代表javascript嵌有JavaScript代碼,可直接提取惡意代碼
- /AA #以下三個為特定特征,打開對象自動執(zhí)行
- /OpenAction
- /AcroForm
- /URI #內(nèi)嵌url鏈接
- /Filter #/Filter字段出現(xiàn),表示了下面的stream流進(jìn)行了加密
- /RichMedia #富文本
- /Launch #執(zhí)行Action的次數(shù)與OpenAction字段關(guān)聯(lián)
- #/xxxx 帶斜杠的關(guān)鍵字包含在<<>>字典內(nèi)部
六、流的提取
/Filter關(guān)鍵字之后保存了stream流的編碼信息一共包括以下幾種:
- /FlateDecode
- /ASCIIHexDecode
- /ASCII85Decode
- /LZWDecode
- /DCTDecode
- /RunLengthDecode
- /CCITTFaxDecode
- /JBIG2Decode
- /JPXDecode
- /Crypt
一共包括上面幾種編碼方式,按常見順序進(jìn)行了排序,可以級聯(lián)編碼。例如:
- 0 0 obj
- <</Filter
- [/FlateDecode /ASCIIHexDecode]
- /Length 14278>>
表示流先經(jīng)過了ASCIIHexDecode再經(jīng)過了FlateDecode編碼解密是即先對流進(jìn)行FlateDecode解碼再對流進(jìn)行ASCIIHexDecode解碼目前遇到2種級聯(lián)編碼樣本(如上),可能會有更多級聯(lián)編碼方式(3級或以上)解碼后能夠觸發(fā)攻擊的流對象為javascript腳本或者圖片對象,常見的惡意攻擊代碼儲存在javascript腳本中。
下面的圖片是提取自樣本中的PDF steam流文件中的js腳本,已經(jīng)很明顯是攻擊代碼了:
七、一些坑
PDF的惡意攻擊樣本毫無疑問會使用一些特殊手段對抗殺軟的掃描檢查,下面統(tǒng)計了一下惡意樣本常見的規(guī)避行為:
1. 交叉引用表
(1) 坑1 引用表偏移不正確
- Xref #表示交叉引用表開始
- 0 2 #0表明引用表描述的對象編號從0開始,8說明共有8個對象
- 0000000000 65536 f #一般pdf都是以這行開始交叉引用表的,起始地址0和產(chǎn)生號
- 0000003195 00000 n #表示對象1,就是catalog,3195為偏移地址n表示對象在使用
- startxref
- 8980 #8980為交叉引用表的偏移地址,此處為十進(jìn)制表示
- %%EOF #文件結(jié)束標(biāo)志
上面有提到過交叉引用表的偏移地址為固定數(shù)值,推測adobe的parser是從文件尾開始解析,獲得交叉引用表的偏移地址(Xref中X在文檔中所在的位置即為偏移地址),找到交叉引用表再定位到各個對象,實際測試發(fā)現(xiàn)偏移地址可以不正確8980偏移地址實際可能為任意地址。
(2) 坑2 引用表可以有多個
- xref
- 0 4
- 0000000000 65535 f
- 0000000000 65536 n
- 0000039095 00000 n
- 0000000015 00000 n
- trailer
- <</ID
- [<386e381fac5d8245e24ee620741d0d06><b15c4bebcdae6f2210f09460b841e7a3>]/Root
- 26 0 R/Size 28/Info 27 0 R>>
- startxref
- 39630
- %%EOF
- <</Filter/FlateDecode/Length
- 6960/Subtype/Type1C>>
- Stream
- Ddd
- endstream
- xref
- 20 1
- 0000040341 00000 n
- 26 4
- 0000040380 00000 n
- 0000040484 00000 n
- 0000040677 00000 n
- 0000040734 00000 n
- 55 2
- 0000172790 00000 n
- 0000172925 00000 n
- trailer
- <</Root 26 0 R/Info 27 0
- R/ID[<386E381FAC5D8245E24EE620741D0D06><39FE58436C8CC909F538F88909F1EE55>]/Size
- 63/Prev 39630>>
- startxref
- 173446
- %%EOF
樣本如上,正常來講,一個文檔只存在一個%EOF結(jié)束符,但是這個樣本里出現(xiàn)了兩個
2. 字符串長度
(1) 坑1 流對象長度可以直接跟對象
正常一個字典語句中/Length之后的數(shù)值代表stream~endstream兩個關(guān)鍵字之間流的長度,如下
- 7 0 obj
- <</Filter/FlateDecode/Length 6960/Subtype/Type1C>>
- stream
但測試發(fā)現(xiàn)流的長度可以是obj對象
- 2 0 obj
- << /Length 4 0 R /Filter
- /FlateDecode >>
- #對應(yīng)的obj對象中包含的長度如下
- 4 0 obj
- 4880
- endobj
所以stream流對象壓縮前的實際長度為4880雖然是PDF格式的正規(guī)使用方法,但同時也是規(guī)避殺軟的一種手法。
(2) 坑2 流對象長度可以為任意值
- 7 0 obj
- <</Filter/FlateDecode/Length
- 6960/Subtype/Type1C>>
- stream
同理,正常流對象長度為上圖,實際測試發(fā)現(xiàn)樣本
- 16 0 obj
- <<
- /Length ANIWAY_____LEN
- >>
- stream
WTF is ANIWAY_LEN??? 長度可以為填ascii字符???所以,/Length后面可以不跟數(shù)值stream流的實際長度實際==關(guān)鍵字endstream偏移-關(guān)鍵字stream偏移-包含的0x0D或0x0A
3. 解碼問題
(1) 坑1 javascript可以支持文本和八進(jìn)制
- 7 0 obj
- << /Type /Action
- /S /JavaScript
- /JS (\145\166\141\154\050\146\165\156)
- endobj
- /JS 16 0 R
- /S /JavaScript
- >>
- endobj
- 16 0 obj
- <<
- /Length ANIWAY_____LEN
- >>
- stream
- function urpl(k,sc){
- var c = "\x75";
- var kkc=k+c;
- var re = /MM/g;
- scsc = sc.replace(re,kc);
- return sc;
- }
- padding_0c = "MM0c0cMM0c0c";
- padding00="MM0000";
- padding_41 = "MM4141";
- var x1=0;
- var x2=0;
- var x3=0;
- endstream
- endobj
有JS編程基礎(chǔ)的肯定注意到了,因此在這里需要判斷javascript內(nèi)容在對象中還是在()內(nèi)亦或是否需要轉(zhuǎn)碼
(2) 坑2 編碼方式縮寫形式
- 1 0 obj
- <</Filter [/Fl /Fl] /Length 8331
- >>
- Stream
- xœíÜYXù
- ÷qÆÌ0K3ÆØRÆVdoUƒQ"[M!S(íRÓ¾0–h¥B*d•hß~5´©
- endstream
正常文件默認(rèn)/Filter關(guān)鍵字之后會出現(xiàn)xxdecode關(guān)鍵字表示stream流編碼方式,但測試發(fā)現(xiàn)樣本可以沒有xxdecode關(guān)鍵字,但同樣進(jìn)行了編碼處理,如上/Fl字段即為/FlateDecode的簡寫,對應(yīng)表如下:
- /FlateDecode /Fl
- /ASCIIHexDecode /AHx
- /ASCII85Decode /A85
- /LZWDecode /LZW
- /RunLengthDecode /RL
- /CCITTFaxDecode /CCF
- /JBIG2Decode #/JBIG2Decode其實和/DCTDecode解碼方式是一樣的
- /DCTDecode /DCT
(3) 坑3 編碼形式可以級聯(lián)
- 10 0 obj
- <</Filter
- [/FlateDecode /ASCIIHexDecode]
- /Length 14278>>
如上表示流先經(jīng)過了ASCIIHex加密再進(jìn)行了FlateDecode加密解碼時需要先進(jìn)行FlateDecode解密之后再進(jìn)行ASCIIHexDecode解密
八、常用分析工具推薦
介紹完惡代格式后,推薦一些惡代分析的基本工具
1. PdfStreamDumper
- stream流解析工具
- vb開源項目。工具存在一些bug,無法解析級聯(lián)編碼后的stream流,例如/Fl/Fl編碼就無法解析
2. PDFParser
- c++開源項目,pdf格式解析,邏輯比較清晰,可以參考
3. ParanoiDF
- python開源項目,惡代分析
- 此外還有一些很贊的開源項目如pyew、peepdf等等,不一一貼地址了。
4. References
- PDF, let me count the way...
- Cryin/PDFTear
- Adobe PDF 官方文檔
- Malicious Document PDF analysis in 5 steps
- C#實現(xiàn)的PDF解析器