通过 Default App 一文,相信各位读者已经了解了 macOS 操作系统是如何将一个类型的文件与某个 App 相关联的知识。这篇文章,我们将要介绍文件类型背后的机制。
相信读者已经对文件类型很熟悉,比如 .zip 扩展名的文档为 ZIP 压缩文件,.pages 扩展名的文档为 Pages 应用程序默认的文件格式。在这里,扩展名是早期文件系统用来标志档案格式的一种机制。以 DOS (Windows 3.x) 为例,这个格式可以规范化为 8.3,也就是说,文档名至多有 8 个字符,扩展名至多为 3 个字符。当然现在的操作系统都没有这种限制了。由于扩展名的存在,操作系统可以根据扩展名来关联应用程序,也可以为不同扩展名的文件准备不同图标,方便用户分辨与使用。这种机制看起来简单易行,但是也存在着很大隐患,比如在早期 Windows 系统 (其实在早期的 macOS 上也一样) 中,由于扩展名可设定为隐藏,则某些恶意软件可以将自己伪装为看似安全的 .txt,.html 文档等诱使用户打开以便执行。所以后来的操作系统为安全计,在传统扩展名的基础上,加入通过对于文档,特别是二进制可执行文件的 “魔数” (Magic Number) 来最终确认文件类型的技术。
对于“魔数”,可能有些读者并不熟悉。其实它可以看作一串由文档格式开发者规定的任意数据组合,用于标示特定文件类型。这样,即使文件没有任何扩展名,操作系统或应用程序也可以通过读取文件根据规定找到魔数以判定文件类型。比如“一款应用程序会产生一个数据文件,该文件会在偏移量 0 处存在于一个四字节长魔数 0xFF 0xCE 0xAB 0x01”,则根据这个约定,所有可以读取该数据文件的应用程序就可以根据这个魔数来判定该文件是否是自己能够支持的文件类型。我们把魔数的概念再扩展到整个文件,其实每一种文件都有一种特定的文件结构,这个结构说起来与程序员口中的数据结构类似,这个结构可以描述文件加载到内存时的一个完整视图。通过魔数,或者说通过结构,我们就可以判定文件类型,这也是很多数据恢复软件能够恢复文件的一个基础技术,也是病毒扫描程序用于判定病毒的一个基础技术。防病毒软件会定期更新特征库,相信读者很熟悉这个名字,那么这个特征库,即可看作一个文件类型描述或者魔数。
以上说的比较远,主要是想让各位读者了解魔数以及文件结构的基本概念。下面本文将以 macOS 中的可执行文件为例,介绍如何通过技术手段判断文件类型。
在传统的 UNIX 系统中,所有拥有 execute (chmod +x) 权限设定的文件都被视为可执行文件。这只是标示上的可执行。一个文件是否真的可以被执行,还要看它的构成以及操作系统是否能够理解并执行它。一个可执行文件用于标示自身类型的信息,通常存储在文件的头部,这也叫做 "Header Signature"。为了实现简单,这些信息往往都是由一段特定的数据或常量所标示,这些特定的数据或常量,一般被称作 Magic Number。当操作系统在加载一个可执行文件后,它可以根据存储在文件头部中的 Magic 判断此文件是否真的是一个可执行文件,以及其是哪一种类型的可执行文件。常见的可执行文件类型有:
PE32/PE32+ (64 bit),称作 Portable executables 。其 Magic 为 MZ。这种可执行文件类型是 Windows 原生可执行文件类型。Intel 的 EFI 中的可执行文件,也使用这种格式 (如 OS X EFI 启动器中使用的 boot.efi)。
ELF,Executable and Linkable Format,是 Linux 和大部分的 Unix 原生可执行文件格式。其 Magic 为 x7FELF 。OS X 并不支持此类可执行文件。、
Script,常见的此类可执行文件就是 Shell Script (Shell 脚本)。它本质上是一个拥有可执行权限的出文本文件。其 Magic 为 #! (Shebang) 你可以打开 Shell 脚本,在文件最开始看到这个 Magic。
Universal Binaries (FAT),中文称作 通用二进制。它可以同时将支持多个微架构 (Power PC,PPC 32/64 位,Intel x86 32/64 位,即 i386 和 x86_64) 的代码放到一个可执行文件中。操作系统在执行时,会根据其当前的架构选择合适的代码来执行。这种方式使得一个应用程序可在多种架构上运行,用户无需下载针对特定架构的应用程序。在 2006年,Mac 全线从 Power PC 处理器转向 Intel x86 系列处理器时,Apple 就提供了这种可执行文件类型,使得应用程序开发者可以直接发布同时支持 PPC 和 x86 的程序。后来,也扩展到可以同时包含 i386 和 x86_64 可执行文件。最终,Universal Binaries 可以同时支持 4种架构代码。其 Magic 为 0xcafebabe(表明此二进制可执行文件是一个 Universal Binary) 。
Mach-O,全称为 Mach Object,它是 OS X 本地可执行文件,共享库等的默认文件类型。Magic 有两种:0xfeedface (MHMAGIC) 对应 32bit,0xfeedfacf (MHMAGIC64) 对应 64bit。
Mach-O 作为 macOS 的本地可执行文件类型,以多种形式出现。首先,在一个应用程序捆绑包中,Contents/MacOS/ 文件夹下的,与应用程序同名且没有扩展名的文件,就是应用程序的实际可执行文件,也就是一个Mach-O 文件。另外,以 .o 或 .dyld 扩展名的文件也都是 Mach-O 文件的形式。一个 Mach-O 文件的文件头含有以下基本信息:
magic,用于标示此文件对应的架构,0xfeedface 为 32bit,0xfeedfacf 为 64bit。
cputype,用于标示此文件对应的 CPU 类型,如 CPU_TYPE_I386 标示针对 Intel x86 32位处理器,CPU_TYPE_X86_64 标示针对 Intel x86 64位处理器,CPUTYPEPOWERPC64 标示针对 Power PC (PPC) 64位处理器等。
cpusubtype,用于标示此文件对应的 CPU 类型的不同实现,如 CPUSUBTYPEMULTIPLE 标示支持 cputype 中指定 CPU 类型的所有实现。CPUSUBTYPEPENTIUM4 则标示其只支持指定 CPU 类型中的 奔腾4 系列处理器。而 CPUSUBTYPEBIGENDIAN 标示支持 cputype 指定 CPU 类型中,使用大端字节序(Big Endian)的实现。有关 Big Endian 和 Little Endian 的问题,牵扯到一些字节处理的问题,所以,笔者建议你阅读 字节序。一般来说,PPC 系列处理器多为 Big Endian 实现,而 Intel x86 系列处理器为 Little Endian 实现。
filetype,用于指定文件的类型,包括 executable (可执行文件),Library (库文件),Kernel Extension (内核扩展) 等。
根据这些信息,则操作系统就可以获得是否能够执行此文件,或此文件应该如何使用等信息。在 OS X 中,我们可以使用如下命令简单的查看一个Mach-O可执行文件的文件头信息:
otool -hV /Applications/Preview.app/Contents/MacOS/Preview
/Applications/Preview.app/Contents/MacOS/Preview:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL LIB64 EXECUTE 46 5936 NOUNDEFS DYLDLINK TWOLEVEL PIE
这里我们使用 otool 命令查看 预览 可执行文件的头信息。你可以参照我们前面讲述的内容对照此结果。目前,几乎所有的 OS X 系统内置可执行文件,MAS 购买的应用程序,其都是 x8664 类型。当然,你可以使用如下命令来显示二进制可执行文件的 MAGIC:
otool -h /Applications/Preview.app/Contents/MacOS/Preview
/Applications/Preview.app/Contents/MacOS/Preview:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
Universal Binaries,我们在前文中已经介绍过它。简单来说,它是一种将不同架构的 Mach-
Universal Binaries,我们在前文中已经介绍过它。简单来说,它是一种将不同架构的 Mach-O 文件组成为一个单独的可执行文件的方式。既然它组合了多个 Mach-O ,那么它的标示信息要稍稍复杂一些:
文件头信息:magic,其值为 0xCAFEBABE,标示此文件是一个 Universal Binary 文件。nfat_arch,用于标示此文件含有几个类型的 Mach-O 文件。
文件架构 (arch,nfat_arch 的个数决定了有几个这样的文件架构) 信息:Universal Binary 中的每一个 Mach-O 文件都会含有描述其本身的文件架构信息,每一段信息称作一个 section 。cputype,与 Mach-O 中的定义相同。cpusubtype,与 Mach-O 中的定义相同。offset,偏移量,用于指定当前 Mach-O 文件的开始在整个 Universal Binary 中的开始。size,以字节(byte) 标示的此 section 获得的虚拟内存大小。align,标示的此 section 的字节对齐设定。
所以,一个 Universal Binary 架构的可执行文件的头部,就是一个 文件头外加多个文件架构信息组成。通过终端命令,可以查看这个信息。
lipo -detailed_info /Applications/Preview.app/Contents/MacOS/Preview
input file /Applications/Preview.app/Contents/MacOS/Preview is not a fat file
Non-fat file: /Applications/Preview.app/Contents/MacOS/Preview is architecture: x86_64
使用 lipo 可以查看一个可执行文件是否含是 Universal Binary 以及其文件头信息 (需要你的系统中安装 Xcode)。从以上输出可以看出,预览并非是一个 Universal Binary (not a fat file)。如果目标应用程序是一个 Universal Binary,则输入类似如下显示:
lipo -detailed_info /Applications/WaterRoof.app/Contents/MacOS/WaterRoof
Fat header in: /Applications/WaterRoof.app/Contents/MacOS/WaterRoof
fat_magic 0xcafebabe
nfat_arch 2
architecture i386
cputype CPU_TYPE_I386
cpusubtype CPU_SUBTYPE_I386_ALL
offset 4096
size 12892
align 2^12 (4096)
architecture ppc7400
cputype CPU_TYPE_POWERPC
cpusubtype CPU_SUBTYPE_POWERPC_7400
offset 20480
size 12644
align 2^12 (4096)
lipo -detailed_info /Applications/Utilities/X11.app/Contents/MacOS/X11
Fat header in: /Applications/Utilities/X11.app/Contents/MacOS/X11
fat_magic 0xcafebabe
nfat_arch 2
architecture x86_64
cputype CPU_TYPE_X86_64
cpusubtype CPU_SUBTYPE_X86_64_ALL
offset 4096
size 19808
align 2^12 (4096)
architecture i386
cputype CPU_TYPE_I386
cpusubtype CPU_SUBTYPE_I386_ALL
offset 24576
size 23456
align 2^12 (4096)
从这里,你可以看到 WaterRoof 含有两个 Mach-O,一个用于在 32位的 Intel x86 处理器上运行 (i386),另外一个用于在 32位的 PPC7400 处理器上运行。而对于 应用程序,实用工具下的 X11 应用程序,则含有针对 32位和64位 Intel CPU 的代码。如果你对于文件头的信息没有兴趣,则可以简单的通过 file 命令对一个可执行文件进行查看,如:
file /Applications/Preview.app/Contents/MacOS/Preview
/Applications/Preview.app/Contents/MacOS/Preview: Mach-O 64-bit executable x86_64
file /Applications/WaterRoof.app/Contents/MacOS/WaterRoof
/Applications/WaterRoof.app/Contents/MacOS/WaterRoof: Mach-O universal binary with 2 architectures
/Applications/WaterRoof.app/Contents/MacOS/WaterRoof (for architecture i386):Mach-O executable i386
/Applications/WaterRoof.app/Contents/MacOS/WaterRoof (for architecture ppc7400):Mach-O executable ppc
同样,从 WaterRoof 的输出可以看到,这是一个含有两种架构 Mach-O 的 Universal Binary。
通过以上的命令分析,我们即可对于文件类型判断过程有了一个基本的认识。对于 Mach-O 这类的二进制可执行文件来说,对应的文件结构如图所示:
那么对于其它类型的文件,如前面提到的 zip,pages,ipa,dmg 等等文件类型,我们其实都可以用与分析 Mach-O 文件类似的方法来分析它们。当然,可能需要使用不同的终端命令。不过,最为常用的应该就是 cat,xxd ,通过将文件数据以16进制显示,再辅以类似魔数定义和文件结构图表,即可分析文件类型,以及抽出数据。
相信到这里,各位已经对于操作系统如何使用文件扩展名和判定文件类型有了一个基本的认识。以后的文章,我们会继续文件类型的探讨,来为各位展示这些文件类型分析的手段是如何被操作系统应用到各种机制中的。
领取专属 10元无门槛券
私享最新 技术干货