OLE2 / CFB 檔案格式
Compound File Binary 格式——.doc/.xls/.ppt 與 .msi 安裝檔底層的舊式容器。
- MIME 類型
- application/x-ole-storage
- 副檔名
- .doc, .xls, .ppt, .msi
- 分類
- 文件
Magic number(檔案簽章)
| 0x00000000 | D0CF11E0A1B11AE1 | ········ | CFB / 'docfile' magic |
關於 OLE2 / CFB 格式
Compound File Binary 格式是把一整個檔案系統塞進單一檔案:它有磁區、有配置表、有一棵由具名串流組成的目錄樹,甚至還有第二套專門給極小串流用的迷你配置機制。微軟設計它,是為了讓應用程式能在一份文件裡存放許多獨立的片段,而不必每次存檔都整個重寫;從 Word 97 文件、Windows Installer 套件到 Outlook 的 .msg 檔,到現在都還跑在它上面。要讀它得順著一串磁區編號走,而不是沿著位元組往前掃,這也是為什麼 .doc 的 hex dump 看起來像雜訊——即使裡面的文字根本沒壓縮。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 8 | Header signature | 永遠是 D0 CF 11 E0 A1 B1 1A E1。前四個位元組是刻意的雙關——D0CF11E 用 leetspeak 讀出來就是 'docfile'。它完全不暗示副檔名:.doc、.xls、.ppt、.msi 和 .msg 開頭都是同樣這 8 個位元組。 |
| 8 | 16 | Header CLSID | 保留的類別識別碼,規格要求全部填零。這裡出現非零值,通常代表這是手工拼出來或刻意做壞的檔案,而不是什麼合法的變體。 |
| 24 | 2 | Minor version | 小端序,實際流通的檔案幾乎清一色是 0x003E。它沒有任何功能上的影響,讀取端會直接忽略。 |
| 26 | 2 | Major version | 0x0003 或 0x0004,而這個欄位有影響:版本 3 用 512 位元組的磁區與 32 位元的串流大小,版本 4 用 4096 位元組的磁區,串流可以超過 4 GiB。幾乎所有 Office 文件都是版本 3。 |
| 28 | 2 | Byte order mark | FE FF,代表小端序。大端序從來沒有真的被規範過,所以實務上這是個常數——當你懷疑檔案整個位移掉了,它是個好用的第二道檢查。 |
| 30 | 2 | Sector shift | 磁區大小取以 2 為底的對數:主版本 3 搭 512 位元組磁區是 0x0009,版本 4 搭 4096 位元組磁區是 0x000C。第 N 個磁區從位元組位移 (N + 1) 乘以磁區大小的地方開始,因為磁區編號是從那 512 位元組的標頭之後才起算。 |
| 32 | 2 | Mini sector shift | 迷你磁區大小取以 2 為底的對數,規格強制為 0x0006,也就是 64 位元組。迷你串流之所以存在,是為了不讓一個放了好幾百個微小屬性串流的檔案,在每一個上面浪費半 KB。 |
| 34 | 6 | Reserved | 6 個位元組,寫入時必須填零,讀取時必須忽略。它們存在的唯一理由是把後面的欄位對齊到 4 位元組邊界。 |
| 40 | 4 | Number of directory sectors | 存放目錄項目的磁區數量。版本 3 的檔案這裡必須寫 0,改為透過配置表順著目錄鏈走;只有版本 4 會用到這個數值。 |
| 44 | 4 | Number of FAT sectors | 有多少個磁區被拿去放檔案配置表。每個 FAT 磁區可放「磁區大小除以 4」個「下一個磁區」指標,所以 512 位元組的磁區可對應 128 個磁區,這張表會隨文件線性成長。 |
| 48 | 4 | First directory sector | 目錄鏈起始的磁區編號。第一筆目錄項目一定是根項目,名稱是 UTF-16 的 'Root Entry',它的子樹列出檔案裡的每一個串流——想知道裡面到底裝了什麼,就是追這個欄位。 |
| 52 | 4 | Transaction signature | 支援交易式存檔的實作會把它遞增,其他情況一律是 0。實務上它是個好用的指紋,可以推測是哪個應用程式寫出這個檔案的。 |
| 56 | 4 | Mini stream cutoff size | 規格強制為 0x00001000,也就是 4096。比這個短的串流會透過迷你 FAT 存進迷你串流;達到或超過的串流則獨佔完整磁區。同一個檔案裡兩條不同的配置路徑,就由這一個數字決定。 |
| 60 | 4 | First mini FAT sector | 迷你配置表的起點;文件裡完全沒有小串流時,則是鏈結結束標記 FFFFFFFE。 |
| 64 | 4 | Number of mini FAT sectors | 那條鏈有幾個磁區。整份文件都是大串流時,這裡是 0 很正常。 |
| 68 | 4 | First DIFAT sector | 當標頭裡那 109 個欄位用完之後,雙重間接配置表要從哪裡接下去。FFFFFFFE 代表從來沒接下去過,512 位元組磁區、大約 7 MB 以下的檔案都是這種情況。 |
| 72 | 4 | Number of DIFAT sectors | 那條延續鏈的長度。在 DIFAT 裡位置本身就是意義:第 i 格對應一段固定的 FAT 索引區間,所以讀取端要是碰到讀不出來的格子就直接跳過、而不是留一個佔位,後面每一個磁區編號都會被默默讀錯。 |
| 76 | 436 | DIFAT array | 109 個 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 套件。 |
| msg | Outlook 的 .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 |
| 25 50 44 46 2D |