RTF嵌入对象识别需先定位\object块后紧跟的\objdata段,其内容为Base64或十六进制编码的OLE复合文档(签名D0CF11E0)或其他二进制流;解析时须按CLSID分流处理Native流,如Excel(00020820)、Word(00020906)等。

RTF嵌入对象的识别靠的是\objdata和\object控制字
RTF规范里,嵌入对象(如Excel表格、Word文档、图片)不是以二进制直接存,而是被Base64编码后放在\objdata段中,外层用\object控制字标记类型和属性。你读到{\object\oleobj...开头、紧接着\objdata的块,才是真正的嵌入内容起点。
常见错误是只扫\object就停,忽略后续\objdata——其实\object本身不带数据,只是元信息容器;真正要解析的原始OLE复合文档或EMF/WMF等二进制流,全在\objdata后面那一长串十六进制或Base64里。
-
\objdata后面紧跟的内容默认是十六进制(每两个字符为一个字节),但RTF 1.9+支持\objdata0(Base64)和\objdata1(十六进制),需先检查后缀数字 - 十六进制数据常含换行和空格,必须过滤掉所有非[0-9A-Fa-f]字符再解码
- Base64数据可能跨多行,且RTF不保证每行64字符对齐,不能依赖
std::base64_decode默认行为,得先拼接完整再解码
从\objdata提取出的二进制流大概率是OLE复合文档
Windows平台下绝大多数RTF嵌入对象(双击可打开编辑的)都是OLE结构:一个符合Compound File Binary Format(CFB)标准的容器文件,里面至少包含ObjectPool流,有时还有Package或Native流。C++里没原生支持,得靠第三方库或自己解析CFB头。
别指望std::ifstream读完就直接当ZIP或EXE处理——CFB有固定512字节扇区、FAT、MiniFAT等结构,头16字节必须是D0 CF 11 E0 A1 B1 1A E1(即"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"),否则就是非OLE对象(比如纯EMF图)。
立即学习“C++免费学习笔记(深入)”;
- 先验证前8字节是否匹配OLE签名,不匹配就按裸二进制(如EMF/WMF)处理
- 匹配成功后,可用
libcfb(轻量C库)或手动解析Directory Entry定位ObjectPool流起始偏移 - 注意:有些RTF导出的嵌入对象会把真实数据藏在
Package流里(用于非OLE场景,如PDF嵌入),这时需读取该流并跳过前78字节包头再取有效载荷
用libcfb读ObjectPool流时容易漏掉Native子流
ObjectPool本身不是最终数据,它通常是一个嵌套OLE容器,里面再含Native流(原始应用数据,如Excel的BIFF结构)或Picture流(位图预览)。很多解析器只取ObjectPool顶层,结果拿到的是不可执行的壳。
libcfb能列目录,但不会自动递归进子存储(Storage)。你得判断ObjectPool项类型是storage还是stream:如果是storage,就得用cfb_open_storage再开一层,再找Native;如果是stream,才可直接读取。
- 调用
cfb_dir_lookup查ObjectPool后,务必用entry->type判断,别硬转成stream读 -
Native流不一定存在——某些旧版Word嵌入会用Contents或Equation Native作替代名,得做字符串模糊匹配 - 读出的
Native数据仍需按原始应用格式解析:Excel是BIFF,Word是DOC,没通用解法,只能按CLSID分流处理
CLSID决定后续怎么解析,但RTF里只存十六进制字符串
RTF的\object块里会有类似\clsid f0000000000000000000000000000000字段,这是16字节CLSID的十六进制表示(无分隔符、小端序)。你得把它转成GUID结构体,再比对已知类型:
// 示例:Excel工作表CLSID
// RTF中: \clsid 00020820000000000000000000000000
// 转为GUID: {00020820-0000-0000-C000-000000000046}
- CLSID字符串长度固定为32,不足补0,每4/2/2/2/6组按小端重排(推荐用
IIDFromString或手写转换函数) - 常见CLSID要硬编码映射:
00020820...→Excel,00020906...→Word,00020C01...→Paint,00020D01...→PowerPoint - 没匹配上的CLSID别丢弃——可能是自定义控件,此时
Native流可能是私有二进制,至少保留原始字节供用户导出
真正麻烦的不是解码或读流,而是CLSID到解析逻辑的路由:同一份Native数据,Excel和WPS生成的结构可能不同,而RTF不带版本提示。这种差异只能靠实际样本积累规则,没法靠通用库兜底。


















