1. 如何知道一个文件是什么类型的文件

文件有各种各样的类型, 比如有普通的文本文件(*.txt), doc文件, ppt文件, exe文件, dll文件, so文件, iso文件, mp3文件, mp4文件, jpg文件, pdf文件等等.

但是, 你有没有想过这个问题:

任何一个文件, 它实质上就是一片0和1组成的数据. 工具(比如linux中的file工具)是怎么判断出文件是什么类型的文件呢?

例如, 有一个1foo.o的ELF文件, 用file命令可以得出它是ELF Relocatable文件.

[mg@vm201226 elf]$ gcc -g -O0 -c 1foo.c -o 1foo.o
[mg@vm201226 elf]$ file 1foo.o
1foo.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped

命令file 1foo.o中, file工具怎么知道1foo.o是ELF文件呢? 它为什么不判定1foo.o是txt文件, 或者是doc文件, 或者是mp4文件呢?

这里, 就提供了一个1foo.o文件, 其他什么也没有了, 这说明了什么呢?
这说明文件类型信息肯定是存在于文件1foo.o本身之中. 也就是说, 一个文件, 它自己就携带了描述它自身是什么文件类型的信息.

2. 文件的魔数决定了文件的类型

在文件的规范/约定/协议中, 规定文件开头的几个字节, 可以用来作为Format Indicator(格式指示), 并将这几个用来指示文件类型的字节数据称为Magic Number(魔数).

不同类型的文件, 魔数不同. 每种类型的文件, 魔数都唯一的.

当给定一个文件, 读取文件最开始的一些字节, 判断这些字节是什么内容, 从而可以得知文件时什么类型的文件.

常见的文件的魔数, 见List of file signatures.
这里列举一些常见的文件魔数.

魔数文件扩展名描述
ED AB EE DBrpmRedHat Package Manager (RPM) package
53 50 30 31binAmazon Kindle Update Package
42 5A 68bz2Compressed file using Bzip2 algorithm
CA FE BA BEclassJava class file, Mach-O Fat Binary
25 50 44 46 2DpdfPDF document
43 44 30 30 31isoISO9660 CD/DVD image file
6B 6F 6C 79dmgApple Disk Image file
66 74 79 70 69 73 6F 6Dmp4ISO Base Media file (MPEG-4)
7F 45 4C 46Executable and Linkable Format
89 50 4E 47 0D 0A 1A 0ApngImage encoded in the Portable Network Graphics format

所以, 给我们一个文件, 我们只需读取文件最开始的很小一部分, 然后将读出的内容跟协议规范中文件魔数一一做对比, 当匹配上某个魔数时, 就说明该文件就是那个类型.

下面以解析一个ELF文件为例

3. 解析ELF文件

首先, 用工具倒出文件的内容, 不需要倒出太多.

# xxd工具, 可以用来倒出二进制文件的内容, -g 4是表示4个octet(8bit为一个octet)为一个组
[mg@vm201226 elf]$ xxd -g 4 1foo.o
00000000: 7f454c46 02010100 00000000 00000000  .ELF............
00000010: 01003e00 01000000 00000000 00000000  ..>.............
00000020: 00000000 00000000 98130000 00000000  ................
00000030: 00000000 40000000 00004000 15001400  ....@.....@.....
00000040: 554889e5 be000000 00bf0000 0000b800  UH..............
00000050: 000000e8 00000000 b8000000 005dc355  .............].U
00000060: 4889e58b 15000000 008b0500 00000001  H...............
00000070: c28b0500 00000001 d089c6bf 00000000  ................
00000080: b8000000 00e80000 0000b800 0000005d  ...............]
...
...

可以看到, 开始的4个字节是 7f 45 4c 46 这既是ELF文件的魔数, 因此可以断定1foo.o是一个ELF文件.

当然ELF文件可以进一步细分为很多种, 比如可执行文件, core文件, ko文件, .o文件, .so文件. 进一步确认出是哪一种ELF文件, 就不是本篇文章所讨论的范围了.

4. 总结 (TLV模型)

一个文件本身就携带了描述它自身是什么文件类型的信息. 这个信息叫魔数, 存在于文件最开始处的几个字节中. 不同类型的文件, 魔数也不相同. 因此读取文件开头部分的一些字节, 就可以鉴别出文件是什么类型.

为什么文件开头要存放标识文件类型的魔数信息呢? 这是因为 "解析"本身就是一种 "TLV"模型的不断运用.

"解析"本身就是一种 "TLV"模型的不断运用. 解析实际上就是将一片数据怎么切割成许多小的数据块, 然后分析出每个数据块是什么含义.


TLV模型, 即 “类型(Type) – 长度(Length) – 值(Value)” 模型.


T是指Type(类型), L是指Length(长度), V是指Value(值). Type信息一定是放在最前面的, 因为读取了Type, 就知道把这片数据强转成什么类型的结构体来接收. 知道了Length, 就知道一片的数据如何切割, 如何下刀. 从首地址开始, 到多长的范围是属于当前数据块的, 从多少开始是属于下一个数据块的. 然后V, 即数据块中存的内容. 只有知道了T和L, 才可能读出V, 否则没法解析出V.


前面数据的V记录着后面数据的T和L.
因此读取第一个数据的V, 可以知道第二个数据是什么数据, 它是什么类型, 占据多长的范围. 进而可以用什么类型的数据结构来强转和接收, 因此可以读出第二个数据的V. 第二个数据的V记录的是后面第三个数据的T和L, 进而解析出第三个数据, … 以次类推, 可以解析出所有数据.


不管是解析文件, 还是解析报文, 还是解析什么东西, 统统都是运用TLV模型.

Logo

更多推荐