【轉】PDF文件格式:基本結構【2020年更新】

       轉自《https://resources.infosecinstitute.com/topic/pdf-file-format-basic-structure/》

 

我們都知道有很多攻擊是攻擊者在 PDF 文檔中包含一些 shellcode。該 shellcode 在如何分析 PDF 文檔並將其呈現給用戶以在目標系統上執行惡意代碼時使用了某種漏洞。

下圖顯示了在流行的 PDF 閱讀器 Adob​​e Acrobat Reader DC 中發現的漏洞數量,該閱讀器於 2015 年發佈,成爲 2017 年 10 月結束對 Acrobat XI 的支持後唯一支持的 Acrobat Reader 版本。漏洞數量越來越多這些年。最重要的漏洞是代碼執行漏洞,攻擊者可以利用該漏洞在目標系統上執行任意代碼(如果 Acrobat Reader 尚未修補)。

圖 1: Adobe Acrobat Reader DC 漏洞

這是我們應該定期更新 PDF 閱讀器的一個重要指標,因爲最近發現的漏洞數量相當驚人。

PDF文件結構

每當我們想發現軟件中的新漏洞時,我們應該首先了解我們試圖發現新漏洞的協議或文件格式。在我們的案例中,我們首先應該詳細瞭解 PDF 文件格式。在本文中,我們將瞭解 PDF 文件格式及其內部結構。

PDF 是一種可移植的文檔格式,可用於呈現包含文本、圖像、多媒體元素、網頁鏈接等的文檔。它具有廣泛的功能。PDF 文件格式規範在此處公開提供,任何對 PDF 文件格式感興趣的人都可以使用。僅 PDF 文件格式就有將近 800 頁的文檔,因此閱讀這些文檔並不是一時興起的事情。

PDF 的功能不僅僅是文本:它可以包含圖像和其他多媒體元素、受密碼保護、執行 JavaScript 等等。PDF文件的基本結構如下圖所示:

圖 2: PDF 結構

每個 PDF 文檔都包含以下元素:

標題

這是 PDF 文件的第一行,指定文檔使用的 PDF 規範的版本號。如果我們想找出答案,我們可以使用十六進制編輯器或簡單地使用xxd命令,如下所示:

[普通]
#xxd temp.pdf | 頭-n 1
0000000:2550 4446 2d31 2e33 0a25 c4e5 f2e5 eba7 %PDF-1.3.%……
[/plain]

temp.pdf PDF 文檔使用 PDF 規範 1.3。'%' 字符是 PDF 中的註釋,因此上面的示例實際上表示第一行和第二行是註釋,對於所有 PDF 文檔都是如此。以下字節取自以下輸出:2550 4446 2d31 2e33 0a25 c4e5,對應於 ASCII 文本“%PDF-1.3.%”。下面是一些使用不可打印字符的 ASCII 字符(注意“.”點),它們通常用於告訴一些軟件產品該文件包含二進制數據,不應被視爲 7 位 ASCII文本。當前版本號的格式爲 1.N,其中 N 的範圍爲 0-7。

身體

在 PDF 文檔的正文中,有一些對象通常包括文本流、圖像、其他多媒體元素等。正文部分用於保存向用戶顯示的所有文檔數據。

外部參照表

這是交叉引用表,其中包含對文檔中所有對象的引用。交叉引用表的目的是允許隨機訪問文件中的對象,因此我們不需要閱讀整個 PDF 文檔來定位特定對象。每個對象由交叉​​引用表中的一個條目表示,該條目總是 20 字節長。讓我們舉個例子:

[普通]
XREF
0 1
0000000023 65535 F
3 1 0000025324
00000 00000000332 00001 00000034 00001 00003 4 0000025518 00002
4
0000025518 00002 000002500 0011 000000003324 00000201025001 000031 0000025001 00001 000000000000 0011 00003 4 0000025518 000000 00000000332 00001 0000000024 00001 0000000024 0011 0000002025001 000010N [/普通]





我們可以通過簡單地使用文本編輯器打開 PDF 並滾動到文檔底部來顯示 PDF 文檔的交叉引用表。在上面的示例中,我們可以看到我們有四個小節(請注意僅包含兩個數字的四行)。這些行中的第一個數字對應於對象編號,而第二行表示當前小節中的對象數量。每個對象由一個條目表示,該條目長 20 個字節(包括 CRLF)。

前 10 個字節是對象從 PDF 文檔開頭到該對象開頭的偏移量。接下來是一個空格分隔符,另一個數字指定對象的世代號。之後,還有另一個空格分隔符,後跟一個字母“f”或“n”,表示對象是空閒還是正在使用。

第一個對象的 ID 爲 0,並且始終包含一個生成編號爲 65535 的條目,該條目位於空閒對象列表的頭部(注意字母“f”表示空閒)。交叉引用表中的最後一個對象使用代號 0。

第二小節的對象 ID 爲 3 幷包含一個元素,即對象 3 從文檔開頭的偏移量 25324 字節處開始。第三小節有四個對象,其中第一個對象的 ID 爲 21,從文件開頭的偏移量 25518 開始。其他對象的後續編號爲 22、23 和 24。 

所有對象都標有“f”或“n”標誌。標誌“f”表示該對象可能仍然存在於文件中,但被標記爲空閒,因此不應使用它。這些對象包含對下一個空閒對象的引用以及對象再次變爲有效時要使用的代號。標誌“n”用於表示有效且已使用的對象,其中包含距文件開頭的偏移量和對象的世代號。

請注意,對象 0 指向表中的下一個空閒對象,即對象 23。由於對象 23 也是空閒的,因此它本身指向表中的下一個空閒對象,即對象 24。但對象 24 是表中的最後一個空閒對象。文件,所以它指向對象零。如果我們用每個對象編號來表示上述交叉引用表,它將如下所示:

[平原]
外部參照
0 1
0000000023 65535˚F
3 1
0000025324 00000Ñ
21 1
0000025518 00002Ñ
22 1
0000025632 00000Ñ
23 1
0000000024 00001˚F
24 1
0000000000 00001˚F
36 1
0000026900 00000Ñ
[/純]

當對象被釋放時,對象的代號會增加,因此如果對象再次變爲有效(將標誌從“f”更改爲“n”),代號仍然有效,而無需增加它。對象 23 的代號爲 1,所以如果再次生效,代號仍爲 1,但如果再次刪除,代號將增加到 2。

已增量更新的 PDF 文檔中通常存在多個小節,否則應僅存在一個以數字 0 開頭的小節。

預告片

PDF 預告片指定讀取 PDF 文檔的應用程序應如何找到交叉引用表和其他特殊對象。所有 PDF 閱讀器都應該從文件末尾開始閱讀 PDF。下面給出了一個示例預告片:
預告片
<<
/Size 22
/Root 2 0 R
/Info 1 0 R
>>
startxref
24212
%%EOF
PDF 文檔的最後一行包含“%%EOF”文件字符串的結尾。在文件標記的結尾之前,有一行帶有startxref字符串,它指定從文件開頭到交叉引用表的偏移量。在我們的例子中,交叉引用表從偏移量 24212 字節開始。在那之前是一個預告片字符串,它指定預告片部分的開始。預告部分的內容嵌入在 << 和 >> 字符中(這是一個接受鍵值對的字典)。 

我們可以看到預告片部分定義了幾個鍵,每個鍵都用於特定的操作。預告片部分可以指定以下鍵:

  • /Size [整數]:指定交叉引用表中的條目數(也計算更新部分中的對象)。使用的數字不應該是間接參考。
  • /Prev [integer]:指定從文件開頭到上一個交叉引用節的偏移量,如果有多個交叉引用節,則使用該偏移量。該數字應該是一個交叉引用。
  • /Root [dictionary]:指定文檔目錄對象的引用對象,這是一個特殊對象,包含指向不同種類的其他特殊對象的各種指針(稍後會詳細介紹)。
  • /Encrypt [dictionary]:指定文檔的加密字典。
  • /Info [dictionary]:指定文檔信息字典的引用對象。
  • /ID [array]:指定構成文件標識符的兩字節未加密字符串數組。
  • /XrefStm [integer]:指定解碼流中從文件開頭到交叉引用流的偏移量。這僅存在於混合引用文件中,如果我們還想打開文檔,即使應用程序不支持壓縮引用流,也會指定該文件。

我們必須記住,如果我們稍後更新 PDF 文檔,則可以修改初始結構。更新通常會在文件末尾附加其他元素。

增量更新

PDF 在設計時考慮了增量更新,因爲我們可以將一些對象附加到 PDF 文件的末尾,而無需重寫整個文件。因此,可以快速保存對 PDF 文檔的更改。PDF文檔的新結構如下圖所示:

圖 3: PDF 結構

我們可以看到 PDF 文檔仍然包含原始的標題、正文、交叉引用表和預告片。此外,PDF 文檔中還添加了其他正文、交叉引用和預告片部分。附加的交叉引用部分將僅包含已更改、替換或刪除的對象的條目。已刪除的對象將保留在文件中,但將標有“f”標誌。每個預告片都需要以“%%EOF”標籤結尾,並且應該包含 /Prev 條目,該條目指向前一個交叉引用部分。

在 PDF 版本 1.4 及更高版本中,我們可以指定文檔目錄字典中的版本條目以覆蓋 PDF 標題中的默認版本。

例子

讓我們展示一個簡單的 PDF 示例並對其進行分析。讓我們從這裏下載一個示例 PDF 文檔並對其進行分析。打開此 PDF 文檔後,它看起來如下所示:

圖 4: PDF 文檔示例

交叉參考和預告片部分如下圖所示:

圖 5:交叉引用和預告片部分

爲清楚起見,已減少交叉引用部分。交叉引用部分包含一個子部分,該部分本身包含 223 個對象。尾部部分從字節偏移 50291 開始,包括 223 個對象,其中根元素指向對象 221,信息元素指向對象 222。

在下一節中,我們將瞭解 PDF 結構的基本數據類型。

PDF 數據類型

PDF 文檔包含以下描述的八種基本類型的對象。這些類型是:布爾值、數字、字符串、名稱、數組、字典、流和空對象。可以標記對象,以便它們可以被其他對象引用。帶標籤的對象也稱爲間接對象。

布爾值

有兩個關鍵字:true和false表示布爾值。

數字

PDF 文檔中有兩種類型的數字:整數和實數。整數由一個或多個數字組成,可選地前面有一個加號或減號。整數對象的示例如下所示:

  • 123 +123 -123

實際值可以用一個或多個數字表示,帶有可選的符號和前導、尾隨或嵌入的小數點(句點)。下面是一個實數的例子:

  • 123.0 -123.0 +123.0 123. -.123

名稱

PDF 文檔中的名稱由 0x21 – 0x7E 範圍內的 ASCII 字符序列表示。例外是字符:%、(、)、<、>、[、]、{、}、/ 和 #,它們必須以斜線開頭。字符的另一種表示形式是它們的十六進制等效值,前面是字符“#”。名稱元素的長度有限制,可能只有 127 個字節長。

寫名字時,必須用斜線來介紹名字;斜槓不是名稱的一部分,而是一個前綴,表示後面是表示名稱的字符序列。如果我們想使用空格或任何其他特殊字符作爲名稱的一部分,則必須使用兩位十六進制表示法對其進行編碼。

名稱示例見下表:

圖 6: PDF 名稱(來源

字符串

PDF 文檔中的字符串表示爲由括號或尖括號包圍的一系列字節,但最大長度爲 65535 個字節。任何字符都可以用 ASCII 表示,也可以用八進制或十六進制表示。八進制表示要求字符以 ddd 的形式寫入,其中 ddd 是八進制數。十六進制表示要求字符以 <dd> 的形式寫入,其中 dd 是十六進制數。

下面是一個表示嵌入括號中的字符串的示例:

  • (我的字符串)

下面是一個表示嵌入尖括號中的字符串的示例(下面的十六進制表示與上面相同,讀作“mystring”):

  • <6d79737472696e67>

我們還可以在表示字符串時使用特殊的知名字符。它們是:n 表示換行,r 表示回車,t 表示水平製表符,b 表示退格,f 表示換頁,( 表示左括號,) 表示右括號和反斜槓。

數組

PDF 文檔中的數組表示爲一系列 PDF 對象,這些對象可能是不同的類型,並用方括號括起來。這就是爲什麼 PDF 文檔中的數組可以保存任何對象類型,如數字、字符串、字典甚至其他數組的原因。數組也可以有零個元素。數組用方括號表示。下面給出了一個數組的示例:

  • 123 123.0 真(我的字符串)/我的名字]

字典

PDF 文檔中的字典表示爲鍵/值對錶。鍵必須是名稱對象,而值可以是任何對象,包括另一個字典。字典中的最大條目數爲 4096 個條目。字典可以用雙尖括號 << 和 >> 括起來的條目來表示。下面給出了一個字典的例子:
<< /mykey1 123

     /mykey2 0.123

     /mykey3 << /mykey4 真

                         /mykey5 (我的字符串)

                    >>

>>

流對象由字節序列表示,長度可能不受限制,這就是爲什麼圖像和其他大數據塊通常表示爲流的原因。流對象由字典對象表示,後跟關鍵字流 ,後跟換行符和結束流。

流對象的示例如下所示:
<<

/類型 /頁面

     /長度 23 0 R

     /過濾器 /LZW解碼

>>

溪流

endstream
所有流對象應爲間接對象,流字典應爲直接對象。流字典指定流的確切字節數。在數據之後應該有一個換行符和 endstream 關​​鍵字。

所有流字典中使用的常用關鍵字如下(請注意,長度條目是強制性的):

  • 長度:PDF 文件的多少字節用於流數據。如果流包含過濾器條目,則長度應指定編碼數據的字節數。
  • 類型:字典描述的 PDF 對象的類型。
  • 過濾器:將在處理流數據時應用的過濾器的名稱。可以按照應用的順序指定多個過濾器。
  • DecodeParms:Filter 指定的過濾器使用的字典或字典數組。此值指定在應用過濾器時需要傳遞給過濾器的參數。如果過濾器使用默認值,則不需要這樣做。
  • F:指定包含流數據的文件。
  • FFilter:在處理流的外部文件中找到的數據時應用的過濾器的名稱。
  • FDecodeParms:由 FFilter 指定的過濾器使用的字典或字典數組。
  • DL:指定解碼流中的字節數。如果有足夠的磁盤空間可用於將流寫入文件,則可以使用此方法。
  • N:存儲在流中的間接對象的數量。
  • First:第一個壓縮對象在解碼流中的偏移量。
  • 擴展:指定對形成繼承樹的其他對象流的引用。

對象流中的流數據將包含 N 對整數,其中第一個整數表示對象編號,第二個整數表示該對象在解碼流中的偏移量。對象流中的對象是連續的,不需要按照對象編號的遞增順序存儲。字典中的第一個 條目標識對象流中的第一個對象。

我們不應該在對象流中存儲以下信息:

  • 流對象
  • 代號不等於 0 的對象
  • 文檔的加密字典
  • 對象流字典中長度條目的間接對象
  • 文檔目錄、線性化字典、頁面對象

在 PDF 1.5 中,交叉引用信息可以存儲在交叉引用流中而不是交叉引用表中。每個交叉引用流包含與交叉引用表和尾部等效的信息。

空對象

空對象由關鍵字“null”表示。

間接對象

首先,我們必須知道,PDF文檔中的任何對象都可以被標記爲間接對象。這爲對象提供了唯一的對象標識符,其他對象可以使用該標識符來引用間接對象。間接對象​​是用關鍵字“obj”和“endobj”表示的編號對象。endobj 必須出現在它自己的行中,但 obj 必須出現在對象 ID 行的末尾,即間接對象的第一行。對象 ID 行由對象編號、世代編號和關鍵字“obj”組成。間接對象​​的示例如下:
2 1 obj

12345

endobj
在上面的示例中,我們創建了一個新的間接對象,其中包含數字 12345 對象。通過將對象聲明爲間接對象,我們可以在 PDF 文檔交叉引用表中使用它,並在文檔中的任何頁面、字典等中重用它。由於每個間接對象在交叉引用表中都有自己的條目,因此可以非常快速地訪問間接對象。

間接賓語的賓語標識符由兩部分組成;第一部分是當前間接對象的對象編號。間接對象​​不需要在 PDF 文檔中按順序編號。第二部分是代號,對於新創建的文件中的所有對象,它都設置爲零。當對象更新時,此數字稍後會增加。

我們可以通過間接引用來引用間接對象,它由對象編號、世代編號和關鍵字R組成。要引用上述間接對象,我們必須編寫如下內容:

  • 2 1 轉

如果我們試圖引用一個未定義的對象,我們實際上是在引用一個空對象。

文件結構

PDF 文檔由 PDF 文件正文部分中包含的對象組成。PDF 文檔中的大多數對象都是字典。文檔的每一頁都由一個頁面對象表示,該對象是一個包含對頁面內容的引用的字典。頁面對象連接在一起並形成一個頁面樹,它在文檔目錄中使用間接引用進行聲明。

PDF文檔的整個結構可以用下圖表示[1]:

圖 7: PDF 文檔的結構(來源

在上圖中,我們可以看到文檔目錄包含對頁面樹、大綱層次結構、文章線程、命名目的地和交互表單的引用。我們不會詳細介紹每個部分的功能,但我們將只介紹最重要的部分,即頁面樹。

文件目錄

從上圖中我們可以看出,Document Catalog 是 PDF 文檔中對象的根。我們已經說過,是 Trailer PDF 部分中的 /Root 元素指定了文檔目錄。文檔目錄包含對定義文檔內容的其他對象的引用。它還包含聲明文檔將如何在屏幕上顯示的信息。文檔目錄中的條目如下:

  • /Type:目錄描述的 PDF 對象的類型(在我們的例子中,這是 Catalog,因爲這是文檔目錄對象)。
  • /Version:構建文檔所依據的 PDF 規範的版本。
  • /Extensions:本文檔中有關開發者擴展的信息。
  • /Pages:對作爲文檔頁面樹根的對象的間接引用。
  • /Dests:對作爲命名目標對象根的對象的間接引用。
  • /Outlines:對作爲文檔大綱層次結構根的大綱目錄對象的間接引用。
  • /Threads:對錶示文檔的文章線程的線程字典數組的間接引用。
  • /Metadata:對包含文檔元數據的元數據流的間接引用。

我們可以看到許多其他條目是文檔目錄的一部分,但這裏不會對其進行描述。讀者可以查看我們的資源以獲取詳細信息。文檔目錄的示例如下所示:
1 0 obj

<< /類型 /目錄

/第 2 頁 0 R

/PageMode /UseOutlines

/大綱 3 0 R

>>

結束對象

頁面樹

文檔的頁面是通過頁面樹訪問的,它定義了 PDF 文檔中的所有頁面。樹包含表示 PDF 文檔頁面的節點,這些節點可以有兩種類型:中間節點和葉節點。中間節點也稱爲頁樹節點,而葉節點稱爲頁對象。 

最簡單的頁面樹結構可以由一個直接引用所有頁面對象的頁面樹節點組成(因此所有頁面對象都是葉子)。

頁樹中的每個節點都必須具有以下條目:

  • /Type:此對象描述的 PDF 對象的類型(在我們的例子中是Pages ,因爲我們正在討論頁面樹節點)。
  • /Parent:應該存在於所有頁面樹節點中,除了根目錄,該條目不得存在。此條目指定其父項。
  • /Kids:應該出現在除葉子之外的所有頁面樹節點中,並指定可從當前節點直接訪問的所有子元素。
  • /Count:指定在後續頁樹中作爲該節點後代的葉子節點的數量。

我們必須記住,頁面樹與 PDF 文檔中的任何內容都沒有關係,例如頁面或章節。

頁面樹的基本示例如下所示:
2 0 obj

<< /類型 /頁數

/兒童 [ 4 0 R

10 0 轉

24 0 轉

]

/計數 3

>>

結束對象

4 0 對象

<< /類型 /頁面

>>

結束對象

10 0 對象

<< /類型 /頁面

>>

結束對象

24 0 對象

<< /類型 /頁面

>>

endobj
上面的頁樹定義了ID 爲 2 的Root對象,它有 3 個子對象,對象 4、10 和 20。我們還可以看到,頁樹的葉子是指定文檔單頁屬性的字典. 在爲每個文檔頁面定義屬性時,我們可以使用多個屬性。

我們已經瞭解了 PDF 文檔的基本結構及其數據類型。如果我們想開始在 PDF 閱讀器中發現漏洞,我們需要以 PDF 閱讀器無法處理並崩潰的方式更改 PDF 文檔。通常,如果我們可以讓 PDF 閱讀器崩潰,我們就發現了一個安全漏洞,我們可以使用它在目標機器上執行任意代碼。

一個例子

在本文中,我們將看一個非常簡單的 PDF 文檔示例。首先,我們需要創建 PDF 文檔,然後我們將嘗試對其進行分析。要創建 PDF 文檔,我們首先創建一個非常簡單的 .tex 文檔,其中包含下圖中可以看到的內容:

圖 8:簡單文檔

我們可以看到 .tex 文檔並沒有真正包含太多內容。首先,我們將文檔定義爲一篇文章,然後在開始 和結束文檔中包含文章的內容。我們將包含一個帶有標題(簡介)的新部分,幷包含靜態文本“Hello World!”。 

我們可以使用pdflatex命令將 .tex 文件編譯成 PDF 文件,並指定 .tex 文件的名稱作爲參數。生成的 PDF 如下圖所示:

圖 9:結果

我們可以看到,PDF 文檔確實包含的內容並不多,只有我們實際包含的文本,沒有圖片、JavaScript 或其他元素。

示例 1

讓我們看一下 PDF 文檔結構,它顯示在下面的輸出中:
%PDF-1.5

%ÐÔÅØ

3 0 對象 <<<

/長度 138

/過濾器 /FlateDecode

>>

溪流

端流

結束對象

10 0 對象 <<<

/長度1 1526

/長度2 7193

/長度3 0

/長度 8194

/過濾器 /FlateDecode

>>

溪流

端流

結束對象

12 0 對象 <<<

/長度1 1509

/長度2 9410

/長度3 0

/長度 10422

/過濾器 /FlateDecode

>>

溪流

端流

結束對象

15 0 對象 <<<

/製片人 (pdfTeX-1.40.12)

/創作者(特克斯)

/創建日期 (D:20121012175007+02'00')

/ModDate (D:20121012175007+02'00')

/被困 /假

/PTEX.Fullbanner(這是 pdfTeX,版本 3.1415926-2.3-1.40.12(TeX Live 2011)kpathsea 版本 6.0.1)

>> 結束對象

6 0 對象 <<<

/類型 /ObjStm

/N 10

/前 65

/長度 761

/過濾器 /FlateDecode

>>

溪流

端流

結束對象

16 0 對象 <<<

/類型 /外部參照

/索引 [0 17]

/尺碼 17

/W [1 2 1]

/根 14 0 R

/信息 15 0 R

/ID [<1DC2E3E09458C9B4BEC8B67F56B57B63> <1DC2E3E09458C9B4BEC8B67F56B57B63>]

/長度 60

/過濾器 /FlateDecode

>>

溪流

端流

結束對象

起始外部參照

20215

%%EOF
創建這樣一個簡單的 PDF 文檔需要很多必要的元素,所以我們可以想象一個非常複雜的 PDF 文檔的外觀。我們還需要記住,爲了清晰和簡潔,所有編碼的數據流都被刪除並替換爲三個點。

讓我們介紹每個 PDF 部分。標題如下圖所示:

圖 10: PDF 標題

身體可以在下圖中看到:

圖 11: PDF 正文

外部參照部分如下圖所示:

圖 11: PDF 外部參照

最後,預告片部分如下所示:

圖 12: PDF 預告片

我們介紹了 PDF 文檔的所有部分,但我們仍然需要進一步分析它們。PDF 文檔的 header 是標準的,我們真的不需要討論它,讓我們將 body 部分留到後面。 

這就是爲什麼我們必須首先看一下外部參照部分。我們可以看到文件開頭到外部參照表的偏移量爲20215字節,十六進制形式爲0x4ef7。如果我們看一下使用 xxd 工具可以獲得的文件的十六進制表示,我們可以看到下圖中顯示的內容:

圖 13:文件的十六進制表示

突出顯示的字節正好位於距文件開頭偏移 20125 個字節的開頭。前面的 0x0a 字節是新行,當前的 0x31 字節代表數字 1,這正是外部參照表的開頭。這就是爲什麼外部參照表用 ID 16 和代號 0 的間接對象表示的原因。(所有對象都應該是這種情況,因爲我們剛剛創建了 PDF 文檔並且還沒有更改任何對象。如果我們查看整個 PDF 文檔,我們可以看到這顯然是正確的;所有對象的世代號爲零。)

間接對象​​的 /Type 將此分類爲外部參照表。/Index 數組包含本節中每個子節的一對整數。第一個整數指定小節中的第一個對象編號,第二個整數指定小節中的條目數。在我們的示例中,對象編號爲零,本小節中有 17 個條目。這也由 /Size 指令指定。請注意,此數字比該小節中任何對象編號的最大數字大一。/W 屬性指定一個整數數組,表示交叉引用條目中字段的大小,表示字段爲一字節、二字節和一字節。

之後是 /Root 元素,它指定 PDF 文檔的目錄目錄爲對象編號 14。/Info 是包含在對象編號 15 中的 PDF 文檔的信息目錄。/ID 數組是必需的,因爲 Encrypt entry 存在幷包含構成文件標識符的兩個字符串。這兩個字符串用作加密算法的輸入。 

/Length 指定加密密鑰的長度(以位爲單位);該值應爲 40 到 o128 範圍內的 8 的倍數(默認值爲 40)。在我們的例子中,加密密鑰的長度是 60 位。/Filter 指定此文檔的安全處理程序的名稱;這也是用於加密文檔的安全處理程序。在我們的例子中,這是 FlateDecode,它使用 zlib/deflate 壓縮方法對數據進行編碼。

我們可以看到外部參照表的另一部分是壓縮的,所以我們無法真正讀取它。當然,我們可以對壓縮數據應用一些 zlib 解壓縮算法,但有更好的選擇。如果工具已經存在,我們爲什麼要爲此編寫程序?使用 pdftk,我們可以使用以下命令修復 PDF 損壞的外部參照表:

  • # pdftk in.pdf 輸出 out.pdf

之後,out.pdf 文件包含以下外部參照和預告片部分:

圖 14:外部參照和預告片

顯然,/Root 和 /Info 對象編號以及其他內容也發生了變化,但我們得到了定義外部參照表的預告片和外部參照關鍵字。我們可以看到外部參照表中有 14 個對象。

我們也可以繼續嘗試解碼其他部分,但這超出了本文的範圍。接下來,我們將檢查未編碼的文檔。

示例 2

讓我們看一下可在此處訪問的示例 PDF 文檔。一些流對象是加密的,但現在不那麼重要了。由於我們已經知道如何處理 PDF 文檔,所以我們不會在簡單的東西上丟失太多單詞。 

讓我們在 gvim 之類的文本編輯器中打開該 PDF,然後查看預告片部分。我們現在必須知道,所有 PDF 文檔都應該從頭到尾閱讀。預告片如下圖所示:

圖 15: PDF 預告片

讓我們也只展示幾個對象的外部參照(爲了清楚起見,其餘的都被丟棄了):

圖 16: PDF 外部參照

我們可以看到PDF文檔的/Root包含在ID爲221的對象中,並且在對象222中還有額外的信息。對象221是整個文檔中最重要的對象,所以我們來展示一下:

圖 17:對象 221

我們可以看到該對象確實是 Document Catalog。Page Tree 對象是 212,Outlines 對象是 213,Names 對象是 220,OpenAction 對象是 58。除了 Page Tree 對象我們還沒有討論任何其他類型,所以我們將繼續 Page Tree 討論只要。

ID 爲 212 的 Page Tree 對象如下圖所示:

圖 18:頁面樹對象

所以 212 對象包含 PDF 文檔的實際頁面。它包含 10 頁,完全正確(如果我們用任何 PDF 閱讀器打開 PDF 文件並檢查頁數,我們可以檢查出來)。 

我們知道 Kids 屬性指定了可以從當前節點直接訪問的所有子元素。在我們的例子中,有兩個直接子節點,其對象 ID 爲 66 和 135。對象 66 如下所示:

圖 19:對象 66

對象 66 包含 ID 爲 57、69、75、97、108 和 120 的其他子元素。

圖 20:對象 135

對象 135 進一步定義了對象 129、138、133 和 158。

如果我們計算所有元素,我們可以看到正好有 10 個元素,這意味着 10 頁中有 10 頁。這進一步意味着所有呈現的對象實際上都是 PDF 文檔的實際頁面,並且不包含任何其他子節點。 

所有呈現的對象都以類似方式聲明,因此我們不會依次查看每個對象。相反,我們只看一個對象,即對象 57。對象 57 包含如下聲明:

圖 21:對象 57

我們可以看到該對象的類型是/Page,這直接暗示這是一個葉子節點,呈現PDF文檔的其中一個頁面。該 PDF 頁面的內容可以在對象 62 中找到:

圖 22:對象 62

我們可以看到PDF頁面的實際內容是用FlateDecode編碼的,這只是一個簡單的zlib編碼算法。

結論

我們已經看到了如何構建 PDF 文檔的兩個示例。利用我們獲得的知識,我們可以開始生成不正確的 PDF 文檔並將它們提供給各種 PDF 閱讀器。如果某個 PDF 閱讀器在閱讀某個 PDF 文檔時崩潰,則該文檔包含 PDF 閱讀器無法處理的內容。這意味着存在漏洞的可能性,需要進一步研究。

最後,如果證明存在漏洞,我們甚至可以編寫一個包含惡意代碼的 PDF 文檔,當受害者在目標機器上使用易受攻擊的 PDF 閱讀器打開 PDF 文檔時執行該代碼。在這種情況下,整臺機器可能會受到威脅,因爲只要打開惡意 PDF 文檔就可以執行任意惡意代碼。

來源

漏洞統計信息

Adobe 支持政策:支持的產品版本,Adobe

文檔管理 — 可移植文檔格式 — 第 1 部分:PDF 1.7,Adobe (Archive.org)

參考:

[1]:PDF 文件格式,可訪問:http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章