发生场景

在不同的平台,给多个文件打包成一个 zip 压缩文件,常常会出现文件压缩损坏,损坏的形式很多

  • 比如 Central Directory 损坏
  • 或者由于压缩文件里面各个文件相对独立,可能其中一个文件损坏
  • 或者文件在持久化的时候在内存中被清理了一段内容,最终文件某一部分全是数据 0
  • 还有就是不是被清理而是内存中随机的一段数据
  • 甚至是整个文件都是 0
  • 或者 0kb 文件
  • 前面两种都能比较容易修复,后面三种情况不可能修复,第三种情况如果幸运,也可以修复

常见的 zip 结构

[文件头+文件数据+数据描述符]{此处可重复n次}+核心目录+目录结束标识

当压缩包中包含多个文件,就会有多个文件头+文件数据+数据描述符

使用 xxd 可以获得压缩文件的 hexdump 内容,如下:

00000000: 504b 0304 0a00 0000 0000 615e 6d4e 3f41  PK........a^mN?A
          Signature Ver  flag thod moti moda CRC-
00000010: 2435 0300 0000 0300 0000 0500 1c00 612e  $5............a.
          32   Comprsize UnComSize flen elen filename-n
00000020: 7478 7455 5409 0003 267e 3f5c 277e 3f5c  txtUT...&~?\'~?\
 5 Bytes End name|Extral field 28 Bytes
00000030: 7578 0b00 0104 3f01 0000 0414 0000 0061  ux....?........a
                                 EndExtralField|dataArea
00000040: 6263 504b 0304 0a00 0000 0000 715e 6d4e  bcPK........q^mN
               Signature Ver  flag ...
00000050: 033f 2c51 0300 0000 0300 0000 0700 1c00  .?,Q............
00000060: 6566 672e 7478 7455 5409 0003 457e 3f5c  efg.txtUT...E~?\
00000070: 467e 3f5c 7578 0b00 0104 3f01 0000 0414  F~?\ux....?.....
00000080: 0000 0065 6667 504b 0102 1e03 0a00 0000  ...efgPK........
                         CentraDir Ver  VMin flag
00000090: 0000 615e 6d4e 3f41 2435 0300 0000 0300  ..a^mN?A$5......
          thod moti moda CRC-32    ComprSize Unco
000000a0: 0000 0500 1800 0000 0000 0100 0000 3f3f  ..............??
         mSize flen elen clen dnum Iatt ExterAttr
000000b0: 0000 0000 612e 7478 7455 5405 0003 267e  ....a.txtUT...&~
       offsetHeader |FileName   |46 Bytes
000000c0: 3f5c 7578 0b00 0104 3f01 0000 0414 0000  ?\ux....?.......
000000d0: 0050 4b01 021e 030a 0000 0000 0071 5e6d  .PK..........q^m
000000e0: 4e03 3f2c 5103 0000 0003 0000 0007 0018  N.?,Q...........
                           |ExtraField 24 Bytes
000000f0: 0000 0000 0001 0000 003f 3f42 0000 0065  .........??B...e
                                            End|File comment, there is zero
00000100: 6667 2e74 7874 5554 0500 0345 7e3f 5c75  fg.txtUT...E~?\u
00000110: 780b 0001 043f 0100 0004 1400 0000 504b  x....?........PK
00000120: 0506 0000 0000 0200 0200 3f00 0000 3f00  ..........?...?.
0x06054b50 End
00000130: 0000 0000 0a                             .....

如果需要每个 flag 的意思,可以去 ZIP 1 或者下面的参考链接中查看

在不了解 zip 结构的情况下,可以使用专业的修复工具处理

常见的可以使用 zip 自带的修复功能:

zip -FF --out fixed.zip ./corrupt.zip

其中 如果一个 -F 是在 Central Directory 存在的情况下,能修复部分功能,两个F -FF 是能直接扫描所有文件头,并提取出来。

这样会出现一种现象就是解压厚的文件会出现替换的提示,这是因为里面有一个文档在之前的 Central Directory 中标记为删除,现在被提取出来了。可惜的是, zip 自带的方法还是有问题,对于只有一部分正常的数据,是无法正常修复对应的文件的。并且一旦文件头损坏, zip 也不能自主修复。

Windows 上的工具比较专业,大部分可能修复的文件都能修复:

  • 使用 ZIP Repair 修复,目前感觉最优秀的修复工具,下载地址是 https://www.diskinternals.com/download/zip_repair.exe
  • 常见的压缩工具也有一定的修复能力,其中试过最好的应该是 haozip

尝试修复压缩包中一个文件 fileA.json

  1. 使用 xxd filename.zip > filename.hex 生成一个 hexdump 文件(用 vim 的 !xxd ,在恢复的时候数据会丢失,有可能要用 vim -b filename 用二进制打开文件)
  2. 自己建立的一个 zip 文件,里面包含了一个同名 fileA.json ,并且内容要大于 37 Bytes,否则会 zip 会使用 store 的压缩方式
echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa > fileA.json
zip filename_correct.zip fileA.json
xxd filename_correct.zip > filename_correct.hex
  1. 在 filename.hex 找到 0x04034b50 以 ascii 为 PK 开头的位置,拷贝 [文件头+文件数据+数据描述符] 到 filename_correct.hex 中对应的位置
  2. 把左边的地址和右边的 ascii 全部删除,并且去掉空格和换行,然后使用 xxd -r -p filename_correct.hex > filename_correct_fixed.zip 获得新的 zip 包
  3. 尝试用 unzip filename_correct_fixed.zip 解压缩文件,如果文件正常,是能正常释出文件
  4. 如果不正常,比如 unzip 报告说:warning: filename too long--truncating. 那就是文件名长度附近的位置值不正确,可以看看周围的数据是否能人为的推断
  5. 实在不行那暂时没有方法了,因为数据有可能是离散无序的

附录

  • 这里没有讲 Deflate 算法的实现,不过如果上面的方式还不能修复,那希望基本很渺茫了,以后有机会再说。
  • ZIP 最开始是 PKZIP 工具支持的一种压缩文件,最开始由 Phil Katz 开发,由 PKWARE 公司维护,所以文件二进制开头是 PK,不过 Phil Katz 没赚到钱。
  • 当时以 Floppy Disk 为主,转换磁头很慢,多个文件压缩成一个文件,其中的每个文件都是独立的,所以可以为 zip 添加新内容。通过操作 Central Directory 来控制,这样减少了磁头频繁大角度移动。

参考链接

  • https://en.wikipedia.org/wiki/Zip_(file_format)
  • https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.2.0.txt
  • https://blog.csdn.net/a200710716/article/details/51644421
  • https://superuser.com/questions/125376/how-do-i-compare-binary-files-in-linux

  1. ZIP: https://en.wikipedia.org/wiki/Zip_(file_format)