OLE 2.0(CFB)
CFB(Microsoft Compound File Binary)復合文件二進制的文件格式實現,也稱OLE(Object Linking and Embedding)即對象鏈接和嵌入或COM(Component Object Model)組件對象模型結構化存儲復合文件實現二進制文件格式,這樣的格式簡稱Compound File。簡單的說,OLE 結構化存儲的標准Windows COM 實現稱為CF 復合文件。
傳統文件系統難以將多種對象放入一個文檔文件中,CF 提供了一個在單個文件中實現一個簡單文件系統的解決方案。結構化存儲定義了如何將一個文件當作是存儲對象和流對象兩種類型對象的分層集合。類似於文件系統,存儲對象對應目錄,而流對象對應文件。
- storage object,存儲對象:類似於文件系統目錄,存儲對象可以包含其他存儲對象和流對象,並維護其下的子對象的位置、大小等信息。
- stream object,流對象:類似於文件系統文件,流對象包含連續順序存儲的用戶數據。存儲對象可以包含一個被叫做CLSID的 object class GUID,可以用來識別一個application 去讀寫存儲對象下的流對象。
- application,應用:參與原子事務的開始、傳輸和完成。
- 與事務管理器通信,開始和完成事務。
- 與事務管理器通信,將事務編排至到其他應用程序,或將事務從其他應用程序編排。
- 與資源管理器通信,提交資源工作請求。
數據結構
一個Compound File 分為多個長度相同的扇區(sector),扇區是磁盤的最小尋址單位,一個扇區 512字節。一部分扇區 元素可以組成一個鏈表,扇區號用於索引。如下,#0、#2、#4 構成一個鏈表。一個扇區鏈構成一個流,一個 扇區中只能存放一種類型的數據。
最小的復合文件CF 至少包含三個扇區:Header(512 bytes),一個FAT扇區(512 bytes),以及一個directory 扇區(512 bytes)。
具體的各扇區類型如下,
扇區類型 | 單個元素長度 | 大小 | 用途 |
---|---|---|---|
Header | 非固定 | 512 字節(實際有效76 字節,剩余436 字節用於DIFAT) | 包含這個復合文件的初始元數據,由offset 0 處開始 |
DIFAT | 4字節(共128 個元素) | 4109 + 419 | 用於FAT 尋址 |
FAT | 4字節(共128 個元素) | 4 * 128 = 512 字節 | OLE 中的主要Allocator,用於mini FAT、directory、mini stream 等扇區尋址 |
Mini FAT | 4字節(共128 個元素) | 512 字節 | mini stream 用戶數據的Allocator |
Directory | 128字節 | 128 * 4 = 512 字節 | 包含storage 對象或stream 對象的元數據 |
用戶自定義數據(即mini stream) | 非固定 | stream 對象的用戶自定義數據 | |
Rang Lock | 非固定 | 用於管理對復合文件的並發訪問的單個扇區。此扇區 必須包含文件偏移量 0x7FFFFFFF。 | |
Unallocated Free |
非固定 | OLE 的未分配空間 |
通常,每個扇區 512 字節
對於各扇區 值的含義,
Header
一下是offvis 工具解析出的.doc 文件的ole header:
其中,ByteOrder 指定了復合文件中所有的整型區都是以小端little-endian 存儲,包含UTF16編碼的signature,不過用戶自定義數據流不受該限制。
最后剩余4 * 109 = 436 bytes 空間用於存儲DIFAT 扇區地址。
DIFAT --> FAT-->|-mini FAT
|-directory
|-mini stream
DIFAT 扇區
Double-indirect file allocation table (DIFAT) 數組用於存儲FAT 扇區,用於FAT 尋址。每個數組元素也是一個4 字節的FAT 扇區號。
1個DIFAT 扇區分為512 字節(CFB_3),前4*127 字節用於FAT 尋址,最后4字節用於尋址下個DIFAT 扇區。DIFAT 起始地址在Header 中保存。
FAT 扇區
簡而言之,FAT 用於分配扇區。
扇區 鏈表和扇區 分配主要由File Allocation Table(FAT) 來管理,如FAT[N] 包含扇區 #N下個扇區。每個數組元素是一個32 位的扇區 號。FAT 數組的扇區s
FAT 扇區 的詳細結構如下:
每個FAT 扇區的偏移地址為:
offset = (sector_number + 1) x sector_size
所以#0 FAT 扇區 的offset 不是0,而是sector_size。
Mini FAT 扇區
mini stream:為了解決扇區空間浪費的問題,將內部stream object 的用戶定義數據部分均分為多個mini stream。
與FAT 數組類似,只是mini FAT 數組存的是mini stream 的mini 扇區號,而不是Compound file 的扇區號。mini 扇區的地址存儲在FAT 標准鏈中,而此mini 扇區鏈的起始地址存在Header 中(first mini 扇區 num)
Directory entry 扇區
directory entry(目錄項)用於維護storage 對象和stream 對象的元數據。directory entry 數組包含多個目錄項,該數組存儲在一個目錄扇區中。
一個storage 對象或一個stream 對象對應一個目錄項,第一個目錄項對應root storage 對象。Directory 扇區 維護的數組空間由FAT分配。directory entry 固定128 字節。
starting sector location:stream 對象的首扇區地址。
* stream object:first 扇區 地址。
* root storage object:mini stream 的first 扇區 地址。
* storage object:全0 填充。
stream size:stream 對象中用戶自定義數據大小。
* stream object:用戶自定義數據大小。
* root storage object:mini stream 的大小。
* storage object:全0 填充。
Directory Entry Name (64 bytes): 此字段必須包含以UTF-16編碼的storage 或stream 名的Unicode字符串,且名稱必須以UTF-16終止空字符結束。
Directory Entry Name Length (2 bytes): directory entry name 的unicode 字符串長度,限制最大不超過64,單位字節。
Object type:如下
Type | Value |
---|---|
Unknown or unallocated | 0x00 |
Storage Object | 0x01 |
Stream Object | 0x02 |
Root Storage Object | 0x05 |
Left Sibling ID (4 bytes): 左兄弟stream id
Right Sibling ID (4 bytes): 右兄弟stream id
Child ID (4 bytes): 子對象stream id
Root Directory entry
作為第一個directory entry,對應root storage object,它是所有對象的root,它存儲了mini stream 的大小和第一個mini 扇區。
其他 Directory entry
對應普通storage object 、stream object,或者未分配對象。
cuteoff size:在mini stream 中存在的文件的截止大小(通常為4,096字節)。
紅黑樹
控制層次結構的一層中的每一組兄弟對象(存儲對象下的所有子對象)都表示為紅黑樹。這一組兄弟對象的父對象有一個指向樹頂的指針。紅黑樹是一種特殊的二叉搜索樹,其中每個節點都有一個紅色或黑色的顏色屬性。紅黑樹允許在存儲對象下的子對象列表中進行高效搜索。紅黑樹上的約束允許二叉樹大致平衡,因此插入、刪除和搜索操作是高效的。
用戶自定義數據扇區
FAT 或mini FAT 中 user-defined data 扇區s 構成一條鏈,每條鏈有一個單獨的directory entry 關聯,並維護其stream 對象的元數據,如大小、名字等。
Range Lock 扇區
位於文件的 0x7FFFFF00-0x7FFFFFFF 位置,用於支持並發、事務等CFB特性。Range lock 扇區 不允許存儲用戶自定義數據。Header、FAT、DIFAT、mini FAT、directory 鏈不允許存儲range lock 扇區 的地址。
復合文檔CF的大小限制
因為兼容性原因,CFB-3 (512 字節的扇區)的復合文檔的大小不允許超過2GB。
常規最大sector 地址 MAXREGSECT = 0xFFFFFFFA,但是range lock 的起始地址是0x7FFFFF00,所以一個CFB_3 復合文件一般最大2GB。