libmagic: The Blathering
几年前我们发布了PolyFile:一个用于识别和映射文件语义结构的工具,包括混合文件、嵌合文件和"精神分裂"文件。它有点像file、binwalk和Kaitai Struct的结合体。PolyFile最初使用TRiD定义数据库进行文件识别。然而,这个数据库既太慢又容易误分类,所以我们决定改用libmagic——file命令背后无处不在的库。
下面是我们开发纯Python版libmagic时发现的各种奇怪现象的汇编。
魔法奥秘
libmagic库比地球上超过一半的人类年龄还要大,但它仍在积极开发中,并且是安装频率最高的Ubuntu软件包之一(位于99.9百分位)。该库的持续开发不仅限于bug修复和支持新文件格式匹配;它还经常收到破坏性变更,为其匹配引擎添加新的核心功能。
libmagic有一个自定义的领域特定语言(DSL)用于指定文件格式模式。运行man 5 magic
可以阅读其文档。该程序将其文件格式模式的DSL数据库编译成单个定义文件,通常安装到/usr/share/file/magic.mgc。libmagic是用C语言编写的,包含几个手动编写的解析器来识别各种难以用其DSL表示的文件类型(例如JSON和CSV)。不出所料,这些解析器导致了许多内存安全漏洞和大量CVE。
魔法DSL
为了理解我们在重新实现libmagic时发现的可怕事物,我们需要简要概述其深奥的DSL。每个DSL文件包含一系列测试(每行一个),用于匹配文件的子区域。这些测试可以简单到匹配魔法字节序列,也可以复杂到看似图灵完备的表达式。(证明图灵完备性留给读者作为练习。)
file命令执行DSL测试来分类输入文件。测试在DSL中组织为树状层次结构。首先,执行每个顶级测试。如果测试通过,则按顺序测试其子项。任何级别的测试都可以选择打印消息或将输入文件与MIME类型分类关联。
DSL文件中的每一行都是一个测试,包括偏移量、类型、期望值和消息,用空格分隔。例如:
|
|
这行将执行以下操作:
- 从输入文件的字节偏移量10开始
- 读取一个有符号小端长整型(4字节)
- 如果这些字节等于0x100,则打印"this is a test"
现在让我们添加一个子测试,并将其与MIME类型关联:
|
|
第二行测试前的">“表示它是先前定义的高层测试的子项。
这个新版本将执行以下操作:
- 当且仅当第一个测试匹配时,尝试第二个测试
- 如果文件偏移量20处的字节等于0xFF,则打印"test two"并将整个文件与MIME类型application/x-foo关联
到目前为止,这些示例中的所有偏移量都是绝对的,但libmagic DSL也允许相对偏移量:
|
|
以及间接偏移量:
|
|
这里的(20.s)表示:在文件的绝对字节偏移量20处读取一个小端短整型,并将该值用作要测试的有符号小端长整型(lelong)的偏移量。
未受控制的恶作剧
在开发libmagic的独立实现过程中(直到它能解析file命令的整个魔法定义集合并通过所有官方单元测试),我们发现了许多未文档化的DSL特性和明显的上游bug。
文档不全的语法
例如,匹配MSDOS文件的DSL模式中包含一个文档不全的间接偏移量括号用法:
|
|
语义是模糊的;这可能意味着"从父匹配点过去0x10字节处读取的小端长整型减4”,或者"从父匹配点过去0x10字节处读取小端长整型并加上从文件最后四个字节读取的值"。事实证明是后者。
未文档化的语法
elf模式使用了一个未文档化的${x?true:false}三元运算符语法。这种语法也可以出现在!:mime指令中!
一些规范,如CAD文件格式,使用未文档化的regex /b修饰符。从libmagic源代码中不清楚这个修饰符是被忽略还是有目的。PolyFile目前忽略它并允许正则表达式应用于ASCII和二进制数据。
Bug
libmagic DSL有一个专门用于匹配全局唯一标识符(GUID)的类型,遵循RFC 4122定义的标准结构。DSL中微软高级系统格式(ASF)多媒体容器的一个定义不符合RFC 4122——它短了两个字节。大概libmagic会静默忽略无效的GUID。我们发现了它,因为PolyFile会根据RFC 4122验证所有GUID。这个bug从2019年12月就存在于libmagic中,直到我们2022年4月向libmagic维护者报告。在此期间,PolyFile有一个针对该bug的变通方案,并且一直使用正确的GUID。
元游戏
PolyFile是libmagic的一个更安全的替代品,几乎功能兼容。
|
|
PolyFile甚至有一个交互式调试器,模仿gdb,用于在匹配过程中调试DSL模式。(见-db选项。)这对于libmagic和PolyFile的DSL开发人员都很有用。但PolyFile能做的远不止这些!例如,它可以可选地输出一个交互式HTML十六进制查看器,映射出文件的结构。它是免费和开源的。您现在可以通过运行pip3 install polyfile或克隆其GitHub仓库来安装它。