文件格式的幻数File Format and Magic Number
文章目录
- 前言
- Magic Number是什么?
- 文件类型的Magic number
- 常见文件头幻数
- 幻数在zipfile.py中的应用
- zip文件格式
- 中央目录结束记录End of central directory record(EOCD)
- 中央目录结构Central directory
- 文件数据区file data
- 判断是否为zip的py代码
- is_zipfile 判断是否为zip文件
- _check_zipfile 检查zip文件格式幻数是否为正确
- _EndRecData 读取中央目录结束记录
- 中央目录结束记录的幻数
- 补充:文章中引用的位反序的分析
- 附录
- struct支持的格式
- End of central directory record
前言
最近在看python自带的zipfile的时候发现有一个函数判断文件是否为zip格式时,采用解析获取文件的魔数并以此来判断文件类型。感觉甚为神奇。
小白的学习之旅开启,下面就幻数、文件类型的幻数以及有什么应用做分享~
Magic Number是什么?
简单来说就是看似毫无意义的数值,却能在程序中完成某种功能,例如:
// 位元反序
// magic number - 0x00082082、0x01122408、255
// input: 0123456
// output: 6543210
// unsigned char 的高两位无意义unsigned char revert(unsigned char i){return ((i * 0x00082082) & 0x01122408) % 255;
}
(对幻数有了解,不想看作者瞎比比的可以直接跳到最后看看位反序的算法思路,很有趣哦~~PS||:如果你觉得幻数在算法中的应用很有趣,想了解其他数学思想的幻数实现方式可以阅读知乎的这篇文章进行探索《magic number的由来和推导》)
在编程中有以下三类使用(From Wiki:)
- 无法解释其意义的唯一值或者在程序中多次被引用但又可被命名常数所替代的值。
- 用来识别文本类型的一个常量数值或字符。
- 不易被误解为其他意义的特有值,如全局唯一标识符。
文件类型的Magic number
虽然从刚才的内容中可以看到magic number有三种用法,但在这里我仅对magic number在识别文件类型的应用——即format indicators格式指示器做学习分享。
首先我们要知道文件类型是操作系统为存储信息而使用的对信息的特殊编码,用于识别内部存储的资料。即每一类信息,可以以一种或多种文件格式保存在电脑存储中。在操作系统中,幻数是用于标识文件格式的常量;通过用常量来区分运行时文件类型。接下来了解一下常见的文件幻数。如果对magic number在文件格式应用起源很感兴趣的同学可以去看看wiki。
常见文件头幻数
JPEG (jpg),文件头:FFD8FF
PNG (png),文件头:89504E47
GIF (gif),文件头:47494638
TIFF (tif),文件头:49492A00
Windows Bitmap (bmp),文件头:424D
CAD (dwg),文件头:41433130
Adobe Photoshop (psd),文件头:38425053
Rich Text Format (rtf),文件头:7B5C727466
XML (xml),文件头:3C3F786D6C
HTML (html),文件头:68746D6C3E
Email [thorough only] (eml),文件头:44656C69766572792D646174653A
Outlook Express (dbx),文件头:CFAD12FEC5FD746F
Outlook (pst),文件头:2142444E
MS Word/Excel (xls.or.doc),文件头:D0CF11E0
MS Access (mdb),文件头:5374616E64617264204A
WordPerfect (wpd),文件头:FF575043
Adobe Acrobat (pdf),文件头:255044462D312E
Quicken (qdf),文件头:AC9EBD8F
Windows Password (pwl),文件头:E3828596
ZIP Archive (zip),文件头:504B0304
RAR Archive (rar),文件头:52617221
Wave (wav),文件头:57415645
AVI (avi),文件头:41564920
Real Audio (ram),文件头:2E7261FD
Real Media (rm),文件头:2E524D46
MPEG (mpg),文件头:000001BA
MPEG (mpg),文件头:000001B3
Quicktime (mov),文件头:6D6F6F76
Windows Media (asf),文件头:3026B2758E66CF11
MIDI (mid),文件头:4D546864
幻数在zipfile.py中的应用
python自带的zipfile.py中用幻数来判断文件是否为zip包。但是在讲解代码前,需要再科普一下zip文件格式。
zip文件格式
这里简要的对zip文件格式进行一个简介(zip详细格式内容参照官方文档)。zip文书格式由文件数据区、中央目录结构、中央目录结束节(文件尾)组成。其中中央目录结束节中有一个字段保存了中央目录开始处的偏移。
文件数据区 |
中央目录结构 |
中央目录结束记录 |
中央目录结束记录End of central directory record(EOCD)
- 中央目录结束记录:被用于标识中央目录结束,可以通过读取它来找到中央目录并解析整个文件结构。结构EndLocator的目录结束标记固定值为0x06054b50。
- 结构包含:
signature 目标结束标记:0x06054b50 4bytes elDiskNumber 当前磁盘编号 2bytes elStartDiskNumber 中央目录开始位置的磁盘编号 2bytes elEntriesOnDisk 该磁盘上所记录的核心目录数量 2bytes elEntriesInDirectory 中央目录结构总数 2bytes elDirectorySize 中央目录的大小 4bytes elStartDicOffetInDisk 相对启动磁盘编号的中央目录启动偏移量 4bytes elCommentLen 注释长度 2bytes elComment 注释内容 大小不定
中央目录结构Central directory
中央目录结构:位于文件数据区后,用于保存所有文件的路径信息和对应文件数据结构区在文件中的偏移。其中中央文件头标识固定为0x02014b50。
- 结构包括:
signature 中央目录文件header标识:0x02014b50 deVersionMadeBy 压缩所用的pkware版本 deVersionMadeBy 压缩所用的pkware版本 deVersionToExtract 解压所需pkware的最低版本 deFlags 通用位标记 deCompression 压缩方法 deFileTime 文件最后修改时间 deFileDate 文件最后修改日期 deCrc CRC-32校验码 deCompressedSize 压缩后的大小 deUncompressedSize 未压缩的大小 deFileNameLength 文件名长度 deExtraFieldLength 扩展域长度 deFileCommentLength 文件注释长度 deDiskNumberStart 文件开始位置的磁盘编号 deInternalAttributes 内部文件属性 deExternalAttributes 外部文件属性 deHeaderOffset 本地文件头的相对位移 deFileName 目录文件名 deExtraField 扩展域 deFileComment 文件注释内容 - 中央目录结构区由中央目录结构的数组组成(包含目录文件名、扩展域、不定长度的文件注释内容)。
- 可以通过计算中央目录结构偏移量来遍历所有的文件(next_offset 下个中央目录结构偏移,current_offset当前中央目录结构偏移):next_offset = current_offset + sizeof(DirEntry) - 3sizeof(char) + deFileNameLength + deExtraFieldLength + deFileCommentLength;
- CRC32校验码与对应文件的数据压缩数据的CRC32校验码可做比对,若不同则数据损坏。<>
文件数据区file data
- 文件数据区:保存所有压缩文件数据的区,位于文件头,由压缩数据结构的数组构成。其中文件头标识固定为0x04034b50
- 结构如下:
signature 文件头标识:0x04034b50 frVersion 解压文件所需 pkware最低版本 frFlags 通用比特标志位(置比特0位=加密) frCompression 压缩方式 frFileTime 文件最后修改时间 frFileDate 文件最后修改日期 frCrc CRC-32校验码 frCompressedSize 压缩后的大小 frUncompressedSize 未压缩的大小 frFileNameLength 文件名长度 frExtraFieldLength 扩展区长度 frFileName 文件名 frExtraField 扩展区 frData 压缩数据
其中frCompression的取值有0~12,代表的含义各不相同。有兴趣可以wiki看看。
判断是否为zip的py代码
以下是截取的zipfile.py文件中的部分代码:
is_zipfile 判断是否为zip文件
def is_zipfile(filename):result = Falsetry:if hasattr(filename, "read"): #解读:判断文件是否有"read"属性result = _check_zipfile(fp=filename) #解读:通过_check_zipfile判断文件是否为zipelse:with open(filename, "rb") as fp: #解读:若文件没有“read"属性,则以二进制格式打开文件result = _check_zipfile(fp) #解读:通过_check_zipfile判断文件是否为zipexcept OSError:passreturn result
zipfile.py中以is_zipfile方法快速判断文件是否为zip格式。首先判断文件是否有属性“read”,若有则通过_check_zipfile快速判断文件是否为zip;若文件无“read”属性,则以二进制格式打开该文件(文件指针位于文件的开头)再通过_check_zipfile判断文件是否为zip。
那么神奇的_check_zipfile是怎样的呢?
_check_zipfile 检查zip文件格式幻数是否为正确
def _check_zipfile(fp):try:if _EndRecData(fp): #解读:若文件有正确的文件格式幻数,则返回真return True except OSError:passreturn False
_check_zipfile通过_EndRecDate方法读取文件的中央目录结束记录,以此检查zip文件格式。至此,追溯到读取文件标志数据的方法_EndRecData,那我们继续往下分析。
_EndRecData 读取中央目录结束记录
def _EndRecData(fpin):#解读:确定文件大小fpin.seek(0, 2) #解读:移动文件读取指针到文件末尾filesize = fpin.tell() #解读:返回文件指针当前的位置,即返回文件大小#解读:检查是否为无压缩注释内容的zip文件,若无压缩注释内容则按照以下方式处理(此时,中央目录结束记录是文件的最后一个数据项)try:fpin.seek(-sizeEndCentDir, 2) #解读:移动件读取指针到中央目录结束记录前,sizeEndCentDir为22位except OSError:return Nonedata = fpin.read() #解读:读取中央目录结束记录EOCD传给dataif (len(data) == sizeEndCentDir anddata[0:4] == stringEndArchive anddata[-2:] == b"\000\000"):# 解读:如果中央目录结束记录没有注释内容且目录结束标记signature准确,则对data进行解包# 判断条件:# 1. 读取的data的大小与zip文件格式既定的中央目录结束记录大小一致(这里的size大小计算在下一个小节进行讲解)# 2. data前4位是signature,是一个固定值。signature等于stringEndArchive(定义的值为b"PK\x05\x06")# 3. 由于没有注释内容,故注释长度为0,注释长度有2位,故data最后两位为b"\000\000"endrec = struct.unpack(structEndArchive, data) #解读:以strucEndArchive模式串进行解包(模式串在下一个小节进行讲解),解包返回一个元组endrec=list(endrec) #解读: 将解包出来的元组数据转换为列表#解读:对列表附加一个空白的注释和开始偏移量endrec.append(b"")endrec.append(filesize - sizeEndCentDir) #解读: 由于是空白注释,故开始偏移量为文件大小-中央目录文件结束记录(22bytes)大小#解读: 读取"Zip64 end of central directory" 结构(这个结构在本文没有进行介绍,可阅读zip官方文档进行了解)return _EndRecData64(fpin, -sizeEndCentDir, endrec)#解读: 若这是一个带压缩注释内容的zip文件或非zip文件,则需要搜索文件末尾以获取中央目录结束记录的标识(此时注释内容是文件的最后一个数据项,并且长度最长为64k)。假定中央目录结束记录的幻数不会出现在注释内容中。maxCommentStart = max(filesize - (1 << 16) - sizeEndCentDir, 0) #解读:计算最长注释内容的文件指针位置,文件大小-2^16-中央目录文件结束记录大小(22bytes)fpin.seek(maxCommentStart, 0) #解读:移动文件读取指针到中央目录结束记录前data = fpin.read()start = data.rfind(stringEndArchive) #解读:返回stringEndArchive(b"PK\005\006")最后出现的位置,若无匹配则返回-1if start >= 0:#解读:读取中央目录结束记录recData = data[start:start+sizeEndCentDir]if len(recData) != sizeEndCentDir:# 解读: 若读取的中央目录结束记录大小与zip文件定义的大小不符,则该文件有损坏,返回nonereturn Noneendrec = list(struct.unpack(structEndArchive, recData)) #解读::以strucEndArchive模式串进行解包,并将元组结果转为列表commentSize = endrec[_ECD_COMMENT_SIZE] #解读: zip官方文件声明的注释内容大小,为7comment = data[start+sizeEndCentDir:start+sizeEndCentDir+commentSize] #解读:读取注释内容endrec.append(comment) #解读:对列表添加注释内容和开始偏移量endrec.append(maxCommentStart + start)#解读: 读取"Zip64 end of central directory"return _EndRecData64(fpin, maxCommentStart + start - filesize,endrec)# 无法找到有效的中央目录结束记录,返回nonereturn None
此函数返回文件的中央目录结束记录值或返回none。
已知的是end of central directory record值存储在zip的“End of central dir”结构(这个结构是个列表)中的第九项,相对启动磁盘编号的中央目录启动偏移量保存在列表的第十项。最终的值由方法_EndRecData64对Zip64 end of central directory record进行读取并返回中央目录结束值。
_EndRecData64在此不进行具体阐述,有兴趣的同学可以阅读zipfile.py中的def _EndRecData64(fpin, offset, endrec)及zip官方文档中的Zip64 end of central directory record进行学习。
中央目录结束记录的幻数
structEndArchive = b"<4s4H2LH"
stringEndArchive = b"PK\005\006"
sizeEndCentDir = struct.calcsize(structEndArchive)
- structEndArchive 的值b"<4s4H2LH" 是二进制数,代表的意思是:
- “<” - 小端模式
- “4s” - 长度为4的字符串
- “4H” - 长度为4的无符号短整型
- “2L” - 长度为2的无符号长整型
- “H” - 长度为1的无符号整型
- b"<4s4H2LH"正好是中央目录结束记录ECOD的小端结构
(备注:struct支持的格式可见附录。对大端小端有兴趣的同学请wiki。)
- stringEndArchive是中央目录结束记录的前四位值,在zip官方文档中定义的是 b’PK\x05\x06’。
- sizeEndCentDir是中央目录结束记录的大小,zip文件官方定义的是22bytes。
- struct.calcsize 用于计算给定的格式b"<4s4H2LH"占用多少字节内存。
(备注:中央目录结束记录ECOD可见附录。)
补充:文章中引用的位反序的分析
unsigned char revert(unsigned char i) {return ((i * 0x00082082) & 0x01122408) % 255;
}
位反序的算法通过三个运算就完成了反转。这里记录下其的分析过程(假设输入为“abcdef”)。
i = abcdefabcdef
* 0x00082082 10000010000010000010
------------------------------------abcdefabcdef abcdefabcdef
------------------------------------abcdefabcdefabcdefabcdef0
这里的乘项0x00082082将6位位元向左扩展了3次,并在右侧补一个0。
abcdefabcdefabcdefabcdef0
and 0x01122408 1000100100010010000001000
------------------------------------------a000e00b000f00c000000d000^ ^ ^ ^ ^ ^| | | | | |25 21 18 14 11 4
上一步运算的结果abcdefabcdefabcdefabcdef0和 0x01122408 进行与运算后,剩下在 4 / 11 / 14 / 18 / 21 / 25 位分别保持输入 6 位二进制的各个位。
a000e00b000f00c000000d000 的 256 进制表示为 axyz
其中 x = e00b0 y = f00c00 z = d000
因 a + x + y + z < 255
则 axyz % 255 = a + x + y + ze00b0+ f00c00+ d000+ a
---------------------------------------------fedcba
最后可以看到位元反序算法完成~
附录
struct支持的格式
Format | C Type | Python | 字节数 |
---|---|---|---|
x | pad byte | no value | 1 |
c | char | string of length 1 | 1 |
b | signed char | integer | 1 |
B | unsigned char | integer | 1 |
? | _Bool | bool | 1 |
h | short | integer | 2 |
H | unsigned short | integer | 2 |
i | int | integer | 4 |
I | unsigned int | integer or long | 4 |
l | long | integer | 4 |
L | unsigned long | long | 4 |
q | long long | long | 8 |
Q | unsigned long long | long | 8 |
f | float | float | 4 |
d | double | float | 8 |
s | char[] | string | 1 |
p | char[] | string | 1 |
P | void * | long |
注:
- q和Q只在机器支持64位操作时有意思
- 每个格式前可以有一个数字,表示个数
- s格式表示一定长度的字符串,p表示的是pascal字符串
- P用来转换一个指针,其长度和机器字长相关
- 最后一个可以用来表示指针类型的,占4个字节
End of central directory record
数据 | 字节数 |
---|---|
end of central dir signature | 4 bytes (0x06054b50) |
number of this disk | 2 bytes |
number of the disk with the start of the central directory | 2 bytes |
total number of entries in the central directory on this disk | 2 bytes |
total number of entries in the central directory | 2 bytes |
size of the central directory | 4 bytes |
offset of start of central directory with respect to the starting disk number | 4 bytes |
.ZIP file comment length | 2 bytes |
.ZIP file comment | (variable size) |
文件格式的幻数File Format and Magic Number相关推荐
- LLVM Bitcode File Format - LLVM 比特流文件格式
LLVM Bitcode File Format - LLVM 15.0.0git documentationhttps://llvm.org/docs/BitCodeFormat.html 目录 A ...
- 根据Magic Number(幻数)判断文件类型
通常我们根据文件的后缀名来判断文件的类型,一般情况下,这样做是没有问题的,但是如果手动的把文件的后缀名进行了修改(比如,test.txt,修改后的文件为test.doc),此时我们根据后缀名获取文件类 ...
- PCD文件格式(The PCD (Point Cloud Data) file format)
本文档描述PCD(点云数据)文件格式,以及它在点云库(PCL)中的使用方式. PCD文件格式图标 PCD文件格式图标 #为什么新的文件格式? PCD文件格式并不意味着重新发明轮子,而是补充现有文件格式 ...
- BMP文件格式详解(BMP file format) (转)
自:http://www.cnblogs.com/Jason_Yao/archive/2009/12/02/1615295.html BMP文件格式,又称为Bitmap(位图)或是DIB(Device ...
- C++magic number幻数的判断算法(附完整源码)
C++magic number幻数的判断算法 C++magic number幻数的判断算法完整源码(定义,实现,main函数测试) C++magic number幻数的判断算法完整源码(定义,实现,m ...
- 幻数浅析(Magic Number)
在源代码编写中,有这么一种情况:编码者在写源代码的时候,使用了一个数字,比如0x2123,0.021f等,他当时是明白这个数字的意思的,但是别的程序员看他的代码,可能很难理解,甚至,过了一段时间,代码 ...
- R语言用load(xxx.Rdata)报错 bad restore file magic number (file may be corrupted) -- no data loaded
Error in load(file) : bad restore file magic number (file may be corrupted) -- no data loaded In ...
- off文件格式(Object File Format)
off文件格式(Object File Format) 本文译自Princeton Shape Benchmark,原文地址http://shape.cs.princeton.edu/benchmar ...
- resize2fs: 超级块中的幻数有错(Bad magic number in super-block )
resize2fs: 超级块中的幻数有错(Bad magic number in super-block ) 问题: lvm创建的逻辑卷,在使用lvextend扩容之后,df看并不会有变化,通常我们需 ...
- BMP文件格式详解(BMP file format)[图文解说]
BMP文件格式,又称为Bitmap(位图)或是DIB(Device-Independent Device,设备无关位图),是Windows系统中广泛使用的图像文件格式.由于它可以不作任何变换地保存图像 ...
最新文章
- 自然语言处理之机器处理流程
- Struts2(批量类型转换器struts2.3.4)
- linux查看端口访问用户,如何查找连接到HTTP或HTTPS端口的所有客户端
- java并发编程(二十一)----(JUC集合)CopyOnWriteArraySet和ConcurrentSkipListSet介绍
- 【渝粤题库】广东开放大学 形成性考核 - 副本 (17)
- matlab如何测两点的角度_根据2点经纬度,计算方位角,以及计算2条线的夹角
- 天然气压缩因子计算软件_徐秀芬等:天然气加气站压缩机组效率的计算方法
- 推荐系统技术演进趋势:排序篇
- kotlin 泛型约束
- 最好浏览器_Windows最好的浏览器!只有你想不到,没有它做不到
- Mac 安装 valet
- 星尘小组第六周学习笔记—如何在各类控件中输入/输出数据
- C语言编程齿轮轮廓线坐标,C语言程序实现齿轮基本参数几何尺寸计算
- 大数据可以考哪些证书?
- 参考文献类型标识码--中英文对照
- tesseract-ocr识别英文和中文图片文字以及扫描图片实例讲解
- Error installing to Instantiated: name=AttachmentStore state=Described
- 2022年劳务员-通用基础(劳务员)考试题库及答案
- 入门图形学:雪地特效(一)
- 网页设计之标题栏显示当前系统日期