OLE 格式解析

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 提供了一个在单个文件中实现一个简单文件系统的解决方案。结构化存储定义了如何将一个文件当作是存储对象和流对象两种类型对象的分层集合。类似于文件系统,存储对象对应目录,而流对象对应文件。

OLE 格式解析

  • 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)。

OLE 格式解析

具体的各扇区类型如下,

扇区类型 单个元素长度 大小 用途
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 字节

OLE 格式解析

对于各扇区 值的含义,

OLE 格式解析

OLE 格式解析

一下是offvis 工具解析出的.doc 文件的ole header:

OLE 格式解析

其中,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 中保存。

OLE 格式解析

FAT 扇区

简而言之,FAT 用于分配扇区。

扇区 链表和扇区 分配主要由File Allocation Table(FAT) 来管理,如FAT[N] 包含扇区 #N下个扇区。每个数组元素是一个32 位的扇区 号。FAT 数组的扇区s

OLE 格式解析

FAT 扇区 的详细结构如下:

OLE 格式解析

每个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)

OLE 格式解析

Directory entry 扇区

directory entry(目录项)用于维护storage 对象和stream 对象的元数据。directory entry 数组包含多个目录项,该数组存储在一个目录扇区中。

一个storage 对象或一个stream 对象对应一个目录项,第一个目录项对应root storage 对象。Directory 扇区 维护的数组空间由FAT分配。directory entry 固定128 字节。

OLE 格式解析

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。

上一篇:Linux部署jar


下一篇:为嵌入式设备制作FAT Image工具