深入解析libmagic:文件格式识别的神秘世界与PolyFile的革新

本文探讨了libmagic库在文件格式识别中的技术细节,包括其自定义DSL语法、偏移量处理机制、未文档化特性及内存安全漏洞,并介绍了纯Python实现的PolyFile如何改进这些问题,提供更安全的替代方案。

libmagic: The Blathering - The Trail of Bits Blog

Evan Sultanik
July 01, 2022
research-practice

几年前,我们发布了PolyFile:一种用于识别和映射文件语义结构的实用工具,包括多语言文件、嵌合文件和分裂文件。它有点像将file、binwalk和Kaitai Struct全部融合在一起。PolyFile最初使用TRiD定义数据库进行文件识别。然而,该数据库速度太慢且容易误分类,因此我们决定切换到libmagic,即file命令背后的无处不在的库。

以下是我们开发libmagic的纯Python清洁室实现时发现的奇特问题的汇编。

魔法之谜

libmagic库比地球上一半以上的人口还要古老,但它仍在积极开发中,并且位于最常安装的Ubuntu包的前99.9%。该库的持续开发不仅限于错误修复和支持匹配新文件格式;库经常收到破坏性更改,为其匹配引擎添加新的核心功能。

libmagic有一个自定义领域特定语言(DSL),用于指定文件格式模式。运行man 5 magic阅读其文档。程序将其文件格式模式的DSL数据库编译成一个单一的定义文件,通常安装到/usr/share/file/magic.mgc。libmagic用C编写,并包含几个手动编写的解析器来识别各种文件类型,否则很难在其DSL中表示(例如JSON和CSV)。不出所料,这些解析器导致了许多内存安全错误和大量CVE。

PolyFile用Python编写。虽然libmagic有官方和独立的Python包装器,但我们选择创建一个清洁室实现。除了原生库的安全问题外,我们决定创建新东西还有几个额外原因:

  • PolyFile已经用纯Python编写,我们不想引入原生依赖。
  • PolyFile旨在检测libmagic可能遗漏的多语言文件和其他奇特文件格式,因此我们无论如何都必须扩展libmagic。
  • PolyFile在整个解析过程中保留词法信息(如输入字节偏移),以便将语义映射回原始文件位置。使用libmagic没有直接的方法做到这一点。

在用比C更具内存安全性的语言重新实现libmagic的想法并不新颖。在Ruby中有一个名为Arcana的努力与PolyFile的实现同时进行,但它仍然不完整。另一方面,PolyFile正确解析了libmagic的整个模式数据库,并通过了除两个之外的所有libmagic单元测试,并在Ange Albertini的900多个文件Corkami语料库上正确识别了至少与libmagic一样多的MIME类型。

魔法DSL

为了欣赏我们在重新实现libmagic时发现的诡异恐怖,我们需要简要概述其深奥的DSL。每个DSL文件包含一系列测试——每行一个——匹配文件的子区域。这些测试可以简单到匹配魔术字节序列,也可以复杂到看似图灵完备的表达式。(证明图灵完备性留给读者作为练习。)

file命令执行DSL测试以分类输入文件。测试在DSL中组织为树状层次结构。首先,执行每个顶级测试。如果测试通过,则按顺序测试其每个子项。任何级别的测试都可以选择打印消息或将输入文件与MIME类型分类关联。

DSL文件中的每一行都是一个测试,包括偏移量、类型、期望值和消息,用空格分隔。例如:

1
10    lelong    0x00000100    this is a test

这行将执行以下操作:

  • 从输入文件的字节偏移10开始
  • 读取一个有符号小端长整型(4字节)
  • 如果这些字节等于0x100,则打印“this is a test”

现在让我们添加一个子测试,并将其与MIME类型关联:

1
2
3
10          lelong    0x00000100    this is a test
>20         ubyte     0xFF          test two
!:mime      application/x-foo

第二个测试中偏移“20”前的“>”表示它是先前定义的更高级别测试的子项。

这个新版本将执行以下操作:

  • 当且仅当第一个测试匹配时,尝试第二个测试。
  • 如果文件偏移20处的字节等于0xFF,则打印“test two”,并将整个文件与MIME类型application/x-foo关联。

请注意,即使其子项不匹配,父测试的消息也会被打印。子测试仅在其父项匹配时执行。子项可以通过额外的“>”前缀任意嵌套:

1
2
3
4
5
6
10          lelong    0x00000100    this is a test
>20         ubyte     0xFF          test two
!:mime      application/x-foo
>>30        ubyte     0x01          this is a child of test 2
>20         ubyte     0x0F          this is a child of the first test that will be tested if the first test passes, regardless of whether the second child passes
!:mime      application/x-bar

如果测试通过,则测试其所有子项。

到目前为止,这些示例中的所有偏移量都是绝对的,但libmagic DSL也允许相对偏移:

1
2
10      lelong    0x00000100    this is a test
>&20    lelong    0x00000200    this will test 20 bytes after its parent match offset, equivalent to absolute offset 10 + 20 = 30

以及间接偏移:

1
(20.s)      lelong    0x00000100    indirect offset!

这里的(20.s)意思是:在文件的绝对字节偏移20处读取一个小端短整型,并使用该值作为偏移量来读取将被测试的有符号小端长整型(lelong)。间接偏移还可以包括算术修饰符:

1
2
(20.s+10)   从绝对字节偏移20处的小端短整型读取偏移量并加10
(0.L*0x20)  从绝对字节偏移零处的大端长整型读取偏移量并乘以0x20

相对和间接偏移也可以组合:

1
2
3
(&0x10.S)   从父匹配后0x10字节处的大端短整型读取偏移量
(&-4.l)     从父匹配前四字节处的小端长整型读取偏移量
&(0.S-2)    读取文件的前两个字节,将其解释为大端短整型,减二,并使用该值作为相对于父匹配的偏移量

偏移量非常复杂!

尽管存在了几十年,libmagic模式DSL仍在积极开发中。

恶作剧,未管理

在开发我们的独立libmagic实现——直到它可以解析file命令的整个魔法定义集合并通过所有官方单元测试——我们发现了许多未文档化的DSL功能和明显的上游错误。

文档不全的语法

例如,匹配MSDOS文件的DSL模式包含一个文档不全的间接偏移括号用法:

1
(&0x10.l+(-4))

语义是模糊的;这可能意味着“从父匹配后0x10字节处的小端长整型读取偏移量减四”,或者可能意味着“从父匹配后0x10字节处的小端长整型读取偏移量并加上从文件最后四个字节读取的值”。事实证明是后者。

未文档化的语法

elf模式使用未文档化的${x?true:false}三元运算符语法。这种语法也可以出现在!:mime指令中!

一些规范,如CAD文件格式,使用未文档化的regex /b修饰符。从libmagic源代码中不清楚这个修饰符是被忽略还是有目的。PolyFile目前忽略它,并允许正则表达式应用于ASCII和二进制数据。

根据文档,search关键字——从给定偏移执行字面字符串搜索——应该后跟一个整数搜索范围。但这个搜索范围显然是可选的。

一些规范,如BER,使用“search/b64”,这是未文档化的语法。PolyFile将其视为等效于合规的search/b/64。

regex关键字有一个未文档化的T修饰符。什么是T修饰符?从libmagic的代码判断,它似乎从结果匹配中修剪空白。

错误

libmagic DSL有一个专门用于匹配全局唯一标识符(GUID)的类型,遵循RFC 4122定义的标准结构。DSL中Microsoft高级系统格式(ASF)多媒体容器的一个定义不符合RFC 4122——它短了两个字节。大概libmagic静默忽略无效GUID。我们捕获它是因为PolyFile根据RFC 4122验证所有GUID。这个错误从2019年12月存在于libmagic中,直到我们在2022年4月向libmagic维护者报告。在此期间,PolyFile有一个针对该错误的变通方法,并一直使用正确的GUID。

元游戏

PolyFile是libmagic的一个更安全的替代品,几乎功能兼容。

1
2
3
4
5
6
$ polyfile -I suss.png
image/png………………………………………………………..PNG image data
application/pdf…………………………………………………..Malformed PDF
application/zip…………………………………………………..ZIP end of central directory record Java JAR archive
application/java-archive…………………………………………..ZIP end of central directory record Java JAR archive
application/x-brainfuck……………………………………………Brainf*** Program

PolyFile甚至有一个交互式调试器,模仿gdb,用于在匹配期间调试DSL模式。(参见-db选项。)这对于libmagic和PolyFile的DSL开发人员都很有用。但PolyFile可以做更多!例如,它可以可选地输出一个交互式HTML十六进制查看器,映射文件的结构。它是免费和开源的。您可以通过运行pip3 install polyfile或克隆其GitHub仓库立即安装它。

如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News

页面内容
魔法之谜
魔法DSL
恶作剧,未管理
文档不全的语法
未文档化的语法
错误
元游戏
最近帖子
构建安全消息传递很难:对Bitchat安全辩论的细致看法
用Deptective调查您的依赖项
系好安全带,Buttercup,AIxCC的评分回合正在进行中!
使您的智能合约超越私钥风险成熟
Go解析器中意外的安全陷阱
© 2025 Trail of Bits。
使用Hugo和Mainroad主题生成。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计