前言:
这几天周围的伙伴都在讨论关于zip压缩包的十六进制格式的一些认识,乘着记忆还算热乎,赶紧记录下来,也算是对这方面的一个小归纳。
参考文献:
https://www.cnblogs.com/esingchan/p/3958962.html
https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.2.0.txt
正文:
还要啰嗦一句:压缩可以分为有损压缩和无损压缩。从名字中就可以看出,有损压缩就是无法将原始的信息完整的保存下来,这就造成解压的时候有一部分信息会被损坏,但它的压缩率高啊,jpg文件就是一种有损压缩,所以一般jpg文件会比png图片模糊,但文件大小会小一点;反之,无损压缩可以完整的保存原始信息,zip就是其中之一,此外还有GZIP,RAR,PNG,GIF等格式的文件也是无损压缩。这里重点记录zip文件格式解析。
这里先展示一波从网上)抓来的表格:
文件头实体(0x504B0304):
偏移 | 字节数 | 描述 |
---|---|---|
0 | 4 | 固定值0x04034b50 |
4 | 2 | 解压缩所需pkware最低版本 |
6 | 2 | 通用比特标志 |
8 | 2 | 压缩方式 |
10 | 2 | 文件最后修改时间 |
12 | 2 | 文件最后修改日期 |
14 | 4 | CRC-32校验 |
18 | 4 | 压缩后大小 |
22 | 4 | 压缩前大小 |
26 | 2 | 文件名称长度 |
28 | 2 | 文件注释长度 |
压缩数据描述符(0x504B0708):
偏移 | 字节数 | 描述 |
---|---|---|
0 | 0/4 | 固定值0x08074b50 |
0/4 | 4 | CRC-32校验 |
4/8 | 4 | 压缩后大小 |
8/12 | 4 | 压缩前大小 |
压缩内容信息块(0x504B0102):
偏移 | 字节数 | 描述 |
---|---|---|
0 | 4 | 固定值0x02014b50 |
4 | 2 | 压缩版本 |
6 | 2 | 解压缩所需pkware最低版本 |
8 | 2 | 标志 |
10 | 2 | 压缩方式 |
12 | 2 | 文件最后修改时间 |
14 | 2 | 文件最后修改日期 |
16 | 4 | CRC-32校验 |
20 | 4 | 压缩后大小 |
24 | 4 | 压缩前大小 |
28 | 2 | 文件名称长度(n) |
30 | 2 | 扩展字段长度(m) |
32 | 2 | 文件注释长度(k) |
34 | 2 | 文件开始的分卷号 |
36 | 2 | 文件内部属性 |
38 | 4 | 文件外部属性 |
42 | 4 | 对应文件实体在文件中的偏移 |
46 | n | 文件名称 |
46+n | m | 扩展字段 |
46+n+m | k | 文件注释 |
主文件结束描述数据块(0x504B0506):
偏移 | 字节数 | 描述 |
---|---|---|
0 | 4 | 固定值0x06054b50 |
4 | 2 | 当前分卷号 |
6 | 2 | Central Directory的开始分卷号 |
8 | 2 | 当前分卷Central Directory的记录数量 |
10 | 2 | Central Directory的总记录数量 |
12 | 4 | Central Directory的大小 (bytes) |
16 | 4 | Central Directory的开始位置偏移 |
20 | 2 | Zip文件注释长度(n) |
22 | n | Zip文件注释 |
正文->解释:
有了上面的表格,这里的解释就可以更形象一点,从上面的表格可以看出,具体的zip文件格式模块可以分成4大块:
- 文件头实体(0x504B0304):必选模块,记录被压缩的的每一个文件的文件头信息,文件数据信息,以及数据描述信息,主要部分便是上述表格内容。与核心压缩内容块对应,压缩包中包含几个文件,就有几个
0x504B0304
模块。 - 压缩数据描述符(0x504B0708):可选模块,用于在不能对输出的 ZIP 文件进行检索(如磁带机)时使用。只有在相应的local file header中通用标记字段的第3bit设为1时才会出现,跟在每一个文件头模块的后面。
- 核心压缩数据目录(0x504B0102):必选模块,记录被压缩文件的具体数据,一部分与文件头模块对应,一部分为文件内容的偏移模块,压缩内容寻址方式就是在这里记录的。该模块的一些标识信息对应文件头模块的信息,下面会做具体解释。
- 目录结束标识符(0x504B0506):必选模块,用于标记压缩的目录数据的结束,每一个压缩文件只有一个目录结束标识符,记录压缩包的简介信息以及文件读取等。
这里用一张图来描述:
local header指的就是文件头,统称为file header,由central directory(也就是核心目录)控制着偏移位置。而且计算机也是通过读核心目录的个数来确定文件实体的个数和文件内容的具体信息,这就注定了在一个压缩文件中,有一个0x504B0102就有一个0x504B0304。而核心目录的偏移地址和大小由目录结束标识符来确定。此外,这里强调一下,在hex编辑器里面,排在后面的字节是高位字节,排在前面的字节是低位字节。
实例分析:
先看一下样本压缩包的总体可视化框架的格式:
OK,懂了这些的话,对于其中具体的分析就能有一个大架构的认识。这里再对每一个大模块里面的具体内容做一个大致的实例分析,第一大块部分:
这是一张zip压缩包最前面部分的截图,可以看出这个是文件头实体,解释如下:
16进制码 | 十进制码 | 解释 |
---|---|---|
0x04034B50 | 67324752 | 十进制码没有特殊意义,这里表示PK文件模块,是每一个文件头实体的标识符开始,后面标注部分的一连串字节描述文件部分简介信息。 |
0x0014 | 20 | 表示解压该文件的pkware(该公司的数据压缩库)的最低版本是2.0。 |
0x0000 | 0 | 表示该文件只能在支持输出zip的设备机上使用,如果是0x1000时才是代表该文件可以在无法检索zip文件信息的设备上读入。 |
0x0008 | 8 | 这两位位是加密方式,有无加密,哪种加密方式都是在此记录。 |
0xAB6D | 43885 | 文件最后修改时间(这里是标准的MS-DOS时间日,因为该程序是在MS-DOS上发明的)。 |
0x4E74 | 20084 | 文件最后修改日期(这里是标准的MS-DOS时间日,因为该程序是在MS-DOS上发明的)。 |
0x4FB0B314 | 1336980244 | crc32校验码(也就是循环冗余校验码),用于判断传输的文件是否完整,对于两个文件来说,crc32校验码出现相同的概率实在是太小了,所以一个crc32基本只能标识一个文件。产生crc32的算法是David Schwadeter的CRC-32算法。 |
0x00002556 | 9558 | 十进制码代表该文件在压缩包中被压缩后的大小(单位是比特)。 |
0x0000261B | 9755 | 十进制码代表该文件在压缩包中被压缩前的大小(单位是比特)。 |
0x0019 | 25 | 十进制码代表文件名称的长度是25比特长,也就是16进制的25个字节,16进制两个字符算一个字节。 |
0x0000 | 0 | 十进制码表示文件注释内容的长度是0比特字节,也就是16进制的0个字节长度,16进制两个字符算一个字节。 |
后面跟着的就是一些不定字节的数据信息,比如水蜜桃红部分的就是该文件头信息所解释的文件的文件名信息,以及文件内部的实体内容信息。
上面这张图就是压缩包的部分可视化简介内容,水蜜桃红部分的信息0xD0C2BDA8CEC4BCFEBCD02F62 6C696E67626C696E672E7A6970
就是文件名称blingbling.zip
的意思;同样的道理,大小和存储大小分别指该文件在被压缩前和被压缩后文件大小(单位是比特);创建时间就是上面的文件最后修改时间和修改日期组合且格式化输出的显示,跟在后面的内容也就是该文件的实体内容信息了。
如果压缩文件可以被不能检索zip压缩包的设备上读入的话,再每一个文件头实体后面都会再跟着一个压缩数据描述模块,包含固定描述符,crc32校验码,文件压缩前大小和文件压缩后大小四个部分。通俗来看,就是将实体文件头的部分信息重新提取并整理成一个单独的模块:
由蓝色背景显示的部分就是一个例子,
十六进制码 | 十进制码 | 解释 |
---|---|---|
0x08074B50 | 134695760 | 十进制码没有意义,这里表示PK文件模块——数据描述模块的开始,用于描述该文件的最主要解释数据,即后面跟着的数据信息。 |
0x4FB0B314 | 1336980244 | crc32校验码(也就是循环冗余校验码),用于判断传输的文件是否完整,对于两个文件来说,crc32校验码出现相同的概率实在是太小了,所以一个crc32基本只能标识一个文件。产生crc32的算法是David Schwadeter的CRC-32算法。 |
0x00002556 | 9558 | 十进制码代表该文件在压缩包中被压缩后的大小(单位是比特)。 |
0x0000261B | 9755 | 十进制码代表该文件在压缩包中被压缩前的大小(单位是比特)。 |
模块后面可能还会跟着其他冗余信息,这里是没有。接下来跟着的模块就是下一个文件头实体的信息和文件描述信息,通俗说就是下一个压缩文件的信息,模块各数据的含义与上面举例的模块信息相同,这里不一一举例。所有的文件头实体模块和数据描述模块读完之后,就是文件目录模块。
文件目录模块,顾名思义,就是一个与目录作用相仿的模块信息,主要是记录文件完整简介的模块,以及该文件实体内容的读取方式等,类似于记录这张截图:
可以说这张可视化的图在文件目录板块都可以找到对应的信息:
该文件实体的中心核心目录块的具体解释如下:
十六进制码 | 十进制码 | 解释 |
---|---|---|
0x02014B05 | 33639173 | 十进制码没有特殊意义,这里表示PK文件模块——中心文件目录模块的开始,主要记录相对应目录的文件的具体简介信息,以及文件实体内容的读取方式。 |
0x003F | 63 | 表示解压该文件的pkware(该公司的数据压缩库)的版本是6.3。 |
0x0014 | 20 | 表示解压该文件的pkware(该公司的数据压缩库)的最低版本是2.0。 |
0x0000 | 0 | 表示该文件只能在支持输出zip的设备机上使用,如果是0x1000时才是代表该文件可以在无法检索zip文件信息的设备上读入。 |
0x0008 | 8 | 这两位位是加密方式,有无加密,哪种加密方式都是在此记录。 |
0xAB6D | 43885 | 文件最后修改时间(这里是标准的MS-DOS时间日,因为该程序是在MS-DOS上发明的)。 |
0x4E74 | 20084 | 文件最后修改日期(这里是标准的MS-DOS时间日,因为该程序是在MS-DOS上发明的)。 |
0x4FB0B314 | 1336980244 | crc32校验码(也就是循环冗余校验码),用于判断传输的文件是否完整,对于两个文件来说,crc32校验码出现相同的概率实在是太小了,所以一个crc32基本只能标识一个文件。产生crc32的算法是David Schwadeter的CRC-32算法。 |
0x00002556 | 9558 | 十进制码代表该文件在压缩包中被压缩后的大小(单位是比特)。 |
0x0000261B | 9755 | 十进制码代表该文件在压缩包中被压缩前的大小(单位是比特)。 |
0x0019 | 25 | 十进制码代表文件名称的长度是25比特长,也就是16进制的25个字节,16进制两个字符算一个字节。 |
0x0024 | 36 | 十进制码表示文件扩展字段的长度是36比特长,也就是16进制的36个字节,16进制两个字符算一个字节。 |
0x0000 | 0 | 十进制码表示文件注释内容的长度是0比特字节,也就是16进制的0个字节长度,16进制两个字符算一个字节。 |
0x0000 | 0 | 表示文件在磁盘中起始位置的号码,也是磁盘在读入该文件的时候开始解析的位置。 |
0x0000 | 0 | 用来表示该文本的数据类型,如果是0x0001,则代表该文本是ASC文本数据类型,否则是二进制数据类型。这里只用到最低位,其他做为补齐字节长度存在,为0。 |
0x00000020 | 32 | 用来表示文件外部属性,既该文件可以依赖的主机操作系统。 |
0x00000000 | 0 | 用来表示与该中心目录模块相对应的文件头实体在压缩包文件中的偏移地址。这里是0,即对应的0x504B0102是最前面的。 |
0x+n | 略 | 表示文件名称,这里的n具体的值为25,从上面可以算出来。 |
0x+n+m | 略 | 表示文件扩展字段,这里的n具体的值为25,m具体的值为36,从上面可以算出来。 |
0x+m+n+k | 略 | 表示文件注释内容,这里的n具体的值为25,m具体的值为36,k具体的值为0,从上面可以算出来。 |
中央核心目录模块可以说是索引并区分实体文件内容的钥匙,文件头实体部分的简介内容在核心目录模块中都有对应。同时,有一个0x504B0102
就会有一个0x504B0304
,而且中央核心目录模块的内容直接影响到对应文件的属性,比如该模块的压缩方式可以直接决定文件是否加密,以何种方式加密等等。这里还有句题外话:这些信息都是zip压缩包必定自带的数据,也就是说压缩后的数据大小比压缩前的数据大小大,算法会选择不对数据进行压缩。
解释这么多的模块,还有一个关键问题:多个文件数据混淆在一个压缩文件中,压缩包又是怎么区分并读取这些文件的?这就牵扯到zip压缩包解析格式的最后一个模块——核心目录结束标志模块:
核心目录结束标志模块像是一个大的总结,一个压缩文件中只会有一个:
十六进制码 | 十进制码 | 解释 |
---|---|---|
0x06054B50 | 101010256 | 十进制值没有意义,这里用作pk文件目录结束说明,zip文件也是从此处开始读取各文件大致描述的。 |
0x0000 | 0 | 处理文件的磁盘号,主要的是做核心目录结束记录。 |
0x0000 | 0 | 核心目录在磁盘中的起始号,记录第一个核心目录起始位置。 |
0x0003 | 3 | 记录压缩文件中核心目录的总个数(这里是在磁盘中的)。 |
0x0003 | 3 | 记录压缩文件中核心目录的总个数(这里是指zip文件中的)。 |
0x0000012D | 301 | 十进制码表示所有核心目录模块在zip文件中所占的字节格式,也就是301个字节,对应16进制的602个字符,十六进制下两个字符算一个字节。 |
0x0000260E | 9742 | 十进制码表示第一个核心目录模块的初始位置在文件中的偏移位置。这里就是指第一个核心目录模块的从文件的第9743个字节开始。 |
0x0000 | 0 | 十进制码表示zip文件的注释字段长度。 |
0x+k | 略 | 这里的k即为上一行的十进制码的值,内容就是zip文件的注释部分在压缩后的内容。 |
这个核心目录结束模块数据不多,确是zip文件读取的入口。一般都是先找到核心目录结束符,通过里面的核心目录个数和首个核心目录偏移位置找到这个核心目录模块,在根据里面的对应实体文件所在偏移位置读取实体文件,便可以还原一个压缩文件。而这其中一些相同的数据模块也不是冗余数据,是用来判断文件所属,关键的地方对应不上便会造成压缩文件损坏而不可读取。
总结:
上面描述的zip文件是以pkzip作为模版的,一些属性问题都与pkware公司当初在开发pkzip时的环境和规定有关,详情可以参见pkware公司出留下的开发手册。总的来说,zip文件格式分为不定量个核心文件目录模块和等量的文件头实体模块(条件成立下还可能存在等量的数据描述符模块),以及一个核心目录结束标志模块,通过Roger Schlafiy提供的加密算法进行加密,以树形结构解析文件,将数据压缩到同一个文件下。
上述内容来自我个人在查阅资料后的个人理解,如果有什么不正确的地方还请及时骚扰批评我: