ByteScope

OLE2 / CFB 檔案格式

Compound File Binary 格式——.doc/.xls/.ppt 與 .msi 安裝檔底層的舊式容器。

MIME 類型
application/x-ole-storage
副檔名
.doc, .xls, .ppt, .msi
分類
文件

Magic number(檔案簽章)

0x00000000D0CF11E0A1B11AE1········CFB / 'docfile' magic

關於 OLE2 / CFB 格式

Compound File Binary 格式是把一整個檔案系統塞進單一檔案:它有磁區、有配置表、有一棵由具名串流組成的目錄樹,甚至還有第二套專門給極小串流用的迷你配置機制。微軟設計它,是為了讓應用程式能在一份文件裡存放許多獨立的片段,而不必每次存檔都整個重寫;從 Word 97 文件、Windows Installer 套件到 Outlook 的 .msg 檔,到現在都還跑在它上面。要讀它得順著一串磁區編號走,而不是沿著位元組往前掃,這也是為什麼 .doc 的 hex dump 看起來像雜訊——即使裡面的文字根本沒壓縮。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
08Header signature永遠是 D0 CF 11 E0 A1 B1 1A E1。前四個位元組是刻意的雙關——D0CF11E 用 leetspeak 讀出來就是 'docfile'。它完全不暗示副檔名:.doc、.xls、.ppt、.msi 和 .msg 開頭都是同樣這 8 個位元組。
816Header CLSID保留的類別識別碼,規格要求全部填零。這裡出現非零值,通常代表這是手工拼出來或刻意做壞的檔案,而不是什麼合法的變體。
242Minor version小端序,實際流通的檔案幾乎清一色是 0x003E。它沒有任何功能上的影響,讀取端會直接忽略。
262Major version0x0003 或 0x0004,而這個欄位有影響:版本 3 用 512 位元組的磁區與 32 位元的串流大小,版本 4 用 4096 位元組的磁區,串流可以超過 4 GiB。幾乎所有 Office 文件都是版本 3。
282Byte order markFE FF,代表小端序。大端序從來沒有真的被規範過,所以實務上這是個常數——當你懷疑檔案整個位移掉了,它是個好用的第二道檢查。
302Sector shift磁區大小取以 2 為底的對數:主版本 3 搭 512 位元組磁區是 0x0009,版本 4 搭 4096 位元組磁區是 0x000C。第 N 個磁區從位元組位移 (N + 1) 乘以磁區大小的地方開始,因為磁區編號是從那 512 位元組的標頭之後才起算。
322Mini sector shift迷你磁區大小取以 2 為底的對數,規格強制為 0x0006,也就是 64 位元組。迷你串流之所以存在,是為了不讓一個放了好幾百個微小屬性串流的檔案,在每一個上面浪費半 KB。
346Reserved6 個位元組,寫入時必須填零,讀取時必須忽略。它們存在的唯一理由是把後面的欄位對齊到 4 位元組邊界。
404Number of directory sectors存放目錄項目的磁區數量。版本 3 的檔案這裡必須寫 0,改為透過配置表順著目錄鏈走;只有版本 4 會用到這個數值。
444Number of FAT sectors有多少個磁區被拿去放檔案配置表。每個 FAT 磁區可放「磁區大小除以 4」個「下一個磁區」指標,所以 512 位元組的磁區可對應 128 個磁區,這張表會隨文件線性成長。
484First directory sector目錄鏈起始的磁區編號。第一筆目錄項目一定是根項目,名稱是 UTF-16 的 'Root Entry',它的子樹列出檔案裡的每一個串流——想知道裡面到底裝了什麼,就是追這個欄位。
524Transaction signature支援交易式存檔的實作會把它遞增,其他情況一律是 0。實務上它是個好用的指紋,可以推測是哪個應用程式寫出這個檔案的。
564Mini stream cutoff size規格強制為 0x00001000,也就是 4096。比這個短的串流會透過迷你 FAT 存進迷你串流;達到或超過的串流則獨佔完整磁區。同一個檔案裡兩條不同的配置路徑,就由這一個數字決定。
604First mini FAT sector迷你配置表的起點;文件裡完全沒有小串流時,則是鏈結結束標記 FFFFFFFE。
644Number of mini FAT sectors那條鏈有幾個磁區。整份文件都是大串流時,這裡是 0 很正常。
684First DIFAT sector當標頭裡那 109 個欄位用完之後,雙重間接配置表要從哪裡接下去。FFFFFFFE 代表從來沒接下去過,512 位元組磁區、大約 7 MB 以下的檔案都是這種情況。
724Number of DIFAT sectors那條延續鏈的長度。在 DIFAT 裡位置本身就是意義:第 i 格對應一段固定的 FAT 索引區間,所以讀取端要是碰到讀不出來的格子就直接跳過、而不是留一個佔位,後面每一個磁區編號都會被默默讀錯。
76436DIFAT array109 個 4 位元組的磁區編號,指名前 109 個 FAT 磁區,剛好把標頭填滿到 512 位元組。沒用到的格子填 FFFFFFFF。這就是為什麼小型 compound file 不需要自己的 DIFAT 磁區——標頭已經把整份對應完了。

怎麼跟長得像的格式分開

容易搞混的怎麼分
Word 文件(docx)Office 的世代分界。兩者可能躺在同一個叫「文件」的資料夾裡,也都能用 Word 打開,但 2007 年以前的 .doc 是 D0 CF 11 E0,.docx 是 50 4B 03 04。副檔名不能當證據:Office 很樂意把舊格式的檔案存成 .docx 這個名字,反過來也一樣,而且開檔時它看的是位元組。去讀位移 0。
doc/xls/ppt/msi這四種共用同樣的 8 位元組 magic,光看標頭完全分不出來——容器不會透露它裝了什麼。答案在位移 48 通往的那棵目錄樹:有名為 'WordDocument' 的串流就是 .doc、'Workbook' 或更舊的 'Book' 是 .xls、'PowerPoint Document' 是 .ppt,而一堆名稱像被打亂的 base-64 的串流,代表這是 Windows Installer 套件。
msgOutlook 的 .msg 也是 compound file,magic 一模一樣,所以偵測器會判成 OLE2 就停手。真正能認出它的,是目錄裡一大堆名為 __substg1.0_ 後接屬性標籤與型別的串流,加上 __properties_version1.0——那是 MAPI 的屬性儲存配置,不是文件配置。
SQLite 資料庫值得放在一起講,因為它們是你真的會遇到的兩種「一個檔案裝下整個檔案系統」。SQLite 開頭是 ASCII 字串 'SQLite format 3' 加一個 NUL;CFB 開頭是 D0 CF 11 E0。兩者都用固定大小的頁面和 free list,但 SQLite 的格式公開、有版本、文件寫得鉅細靡遺,CFB 則是被逆向工程了十年,微軟才把規格放出來。

常見的壞法

  • FAT 鏈斷掉。因為串流是一串磁區編號組成的鏈結串列,不是連續的一段,所以配置表裡壞掉一筆並不會把串流截斷——而是把它接到別人的磁區去。文件照樣打得開,中間某一段變成另一段內容、或是一堆二進位垃圾,而檔案裡沒有任何檢查碼能發現。
  • 被截斷。512 位元組的標頭還在,所以每個工具都能正確辨識這個檔案、報出版本與磁區大小,然後在解析時失敗,因為位移 48 指名的那個目錄磁區已經落在檔尾之外。CFB 沒有尾標,所以不像 PNG 或 ZIP 那樣有便宜的檔尾檢查可用。
  • DIFAT 迴圈。雙重間接表裡的磁區編號沒有經過驗證,所以壞掉的檔案可以讓鏈結指回自己。粗心的解析器會永遠走下去;這是 compound file 讀取端典型的阻斷服務手法,防守方式是用「這個檔案最多可能有幾個磁區」來限制走訪長度,而不是相信位移 44 和 72 宣稱的數量。
  • 文字模式傳輸。CFB 每個欄位都是二進位,所以 ASCII 模式的換行轉換會讓磁區邊界整個偏移、把位移 24 之後的標頭弄壞——而位移 0 的 magic 裡沒有 0D 0A,毫髮無傷,陷阱正在這裡。檔案還是被辨識成 OLE2,卻完全讀不出東西。
  • 把版本 4 的檔案當成版本 3 讀。位移 26 的主版本決定磁區是 512 還是 4096 位元組。忽略它的話,每個磁區位移都差了 8 倍,結果不是乾脆地失敗,而是一串看起來很像樣、實際上完全錯位的資料。

這個格式現在還在用嗎

OLE2 是一個死不掉的舊容器,因為還有太多東西跑在上面。Office 在 2007 年轉向 ZIP 加 XML 的格式,2007 年以前的文件確實已經很老了,但有三樣東西讓 compound file 天天還在流通:Windows Installer 套件,它到現在還是 CFB,也還是 Windows 軟體的出貨方式;Outlook 的 .msg 檔,每個「把這封信存下來」按鈕產出的就是它;還有法務、政府與企業文件庫裡那一大片沒人會去轉檔的 .doc 與 .xls 典藏。它同時也是個長期存在的安全破口——舊 .doc 裡的巨集,加上這個格式本身的複雜度,讓 compound file 當了二十年的惡意程式最愛載體,所以郵件閘道看到 D0 CF 11 E0 一律起疑,不管副檔名是什麼。要做新東西的話,這個格式已經沒有任何優勢:OOXML 是 ZIP,用一般工具就能檢視,壓縮率好太多,而且是公開標準;CFB 的規格則是等到大家都逆向工程完了,微軟才以 [MS-CFB] 之名放出來。

在這個站上打開 OLE2 / CFB 檔

這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。

相關格式

容易和 OLE2 / CFB 搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
Word 文件(docx)50 4B 03 04.docx
Excel 活頁簿(xlsx)50 4B 03 04.xlsx
PowerPoint 簡報(pptx)50 4B 03 04.pptx
PDF25 50 44 46 2D.pdf

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 OLE2 / CFB?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。