为何一个文件两次压缩的MD5不一样?这里究竟发生了什么,我为什么总是给自己挖坑?(~ ̄▽ ̄)~
问题背景
事情是这样的,某天需要对一批陈旧的文件对比校验,这批文件放在2个位置,如果对比结果一致就可以清掉一处,把磁盘空出来。这里用A、B主机区分好了。
Z文件:两层嵌套的ZIP压缩包。
A主机:系统Windows 10,存储的是Z文件经过一层解压后的文件夹,里面有N个文件。之所以解压过一次,是因为解压后子文件夹中的压缩包要拷贝出去使用,而磁盘空间又不够,就删了原始的。
B主机:Linux系统,存储的是原始的压缩包Z文件。
这也是后面问题产生的根源。
当时的认知是同一个文件,文件内容没有任何变化,那么我将A主机的文件夹再压缩回去,MD5应该和B主机一样,结果并非我想的这样。开始以为是压缩软件的问题,使用了Bandizip、7Zip、Zip for Windows对A主机文件夹进行压缩,得到了三个不同的MD5,均和B主机不一致,甚至同一个压缩软件连续压缩两次产生的MD5都不一样,就这让我怀疑人生,当时猜测可能和时间戳、压缩算法等有关系,但是没有证据,就很难受。
排查原因
自己挖坑自己填,发现问题就得查,回到家后开始模拟。首先创建一个test.txt,里面写入了一段文本内容,再次通过不同的压缩软件压缩、相同软件不同时间段压缩,得到了和上述相同的情况。首先想到的是通过HEX分析文件哪里不同。
以相同软件不同时间段压缩举例,通过对比HEX,可以发现在压缩文件的中部有几个字节明显不一致:
而010 Editor中并没有将此处的值解析出来,告诉我们这里代表什么。
进行到这,想着通过调试压缩软件源代码的方式继续找答案了。这里选的是7zip,因为用的人比较多,找相关的资料也会方便一些。Zip for Windows虽然也有源代码,但看起来资料很少,只能先放放。按照网上的文章,通过Visual Studio 2017编译7z1900-src
中的Format7z确实成功了,不过编译出来的是dll文件,这难度就高一些,进行不下去。
调试不行,就只能分析源码,好在7zip的代码结构非常非常的清晰,同时也看了7zip源码分析相关的文章。
Archive 目录,包含各种 archive 算法的代码。因为7z不光支持7z文件,还支持zip, rar, chm等其他的打包文件格式。进入到ZIP目录,ZipHeader.h
中清晰的描述了Signature、CompressionMethod、ExtraID、HostOS等标识,这里结合HEX值、代码在分析十分有用。
首先查看Signature:
1 | namespace NSignature |
在文件HEX开头的054B0304
对应kLocalFileHeader
、中间的504B0102
对应kCentralFileHeader
。因为是生成压缩文件,那么可直接在ZipOut.cpp
进行搜索定位到WriteCentralHeader
函数:
1 | void COutArchive::WriteCentralHeader(const CItemOut &item) |
可以看出到整个CentralHeader
的结构十分清晰,手工计算部分变量值、偏移能够对应上。不计算也可以更快速的定位:
1 | WriteBytes((const char *)item.Name, item.Name.Len()); |
很明显写了文件名test.txt
,一眼就看到。再往下就是NtfsTimeIsDefined
的判断,看到这里太高兴了。
1 | 0001b0 45 b7 f1 79 3a bb d8 cb 51 ef 50 4b 01 02 3f 00 E..y:...Q.PK..?. |
kNTFS在ExtraID的值就是0x0A
哇。
1 | namespace NExtraID |
逐个计算下来,问题应该在Ntfs_MTime
、Ntfs_ATime
、Ntfs_CTime
的部分上。相关的定义:
1 | class CItemOut: public CItem |
那么问题来了,这三个时间又是啥?
NTFS Timestamp
关于这三个时间在检索的时候没发现微软有相关的文档进行介绍(可能是方式不对…),资料也不多。其中有一篇日文资料写的比较清楚,放在参考里。
Windows NTFS使用MACB时间:
- Modified
- Accessed
- Changed ($MFT Modified)
- Birth (file creation time)
那么该怎么从文件中读取到这些值呢?可以通过比较常用的取证工具Autospy来获取,安装和使用可以网上搜索一下,非常简单的。
先看一下两个文件MD5吧:
1 | 文件名称: E:\Win10-ZIPTest\test-0212-01.zip |
将测试目录导入Autospy后,就可以查看File Metadata了:
1 | Name |
其中6月23日是创建test.txt
的时间,可看出两个压缩包的Accessed时间明显不一致,也就说在Windows 10上7zip对同一个文件连续压缩两次产生不同MD5的原因和FILETIME有关。
软件对比
知道Windows 10的FILETIME变化会导致MD5不同后,我想看看其他软件是不是也是这个原因造成的。
Bandizip
Bandizip也是非常好用的压缩软件,连续两次压缩产生的MD5不一样。对比步骤:
1、对比Bandizip和7zip的HEX,可以看出压缩结构是类似的。
2、对比两次Bandizip压缩文件HEX,同样是在压缩文件的中部有几个字节明显不一致。
3、通过Autospy查看Accessed时间,的确不一样。
1 | Name |
可以确定Bandizip对同一文件连续压缩两次MD5不同也是由于Accessed变化导致的。
Zip for Windows
Zip for Windows的对比步骤与上面一样,但在第一步就有明显的区别:
1、对比Zip for Windows和7zip的HEX,可以看出压缩结构并不相似,有明显的调整:
2、对比两次Zip for Windows压缩文件HEX,有几个字节明显不一致:
3、通过Autospy查看Accessed时间,会发现Accessed、Created时间都是0000-00-00 00:00:00
:
1 | Name |
这就很让人头大,和7zip的分析结果完全不一样了,需要找出那几个字节代表的啥。
打开Zip for Windows的源代码,在zip.h
中找到了结构体zlist
,可以看一下怎么定义的,就不具体贴出来了:
1 | /* Structures for in-memory file information */ |
看一下头部的数据
1 | 000000 50 4b 03 04 14 00 00 00 08 00 c9 b1 d7 52 7f 46 PK...........R.F |
再通过Zip for Windows压缩一个不同的文件,比如EXE等都行,对比发现53 44
、55 54
几处附近是没有变化的。查找相关的宏定义:
1 |
从上面的图片中可以看到不一致的字节位置在EF_TIME
后面,所以分析这些字节代表什么就可以了。
可以检索到函数,这里举例:
1 | local int ef_scan_ut_time(ef_buf, ef_len, ef_is_cent, z_utim) |
而get_ef_ut_ztime
又调用了ef_scan_ut_time
:
1 | int get_ef_ut_ztime(z, z_utim) |
接下来可以在zip.c
文件中找到几处调用get_ef_ut_ztime
的地方:
1 |
|
但是他们都和时间有关系,可以跟踪到zip.h
:
1 | /* Structure carrying extended timestamp information */ |
这样就可以确定Zip for Windows在Windows 10上压缩文件时与时间的变化有关系。但是为什么Autospy没有解析出来,显示的都是0000-00-00 00:00:00
,我想可能和unix2dostime
的实现或者Autospy解析有关系,这里就不去深究了。
另外插一句,Zip for Windows可以通过选项打印出压缩过程的整个阶段,可以辅助分析使用:
1 | zip -r zip-test-09.zip test.txt -sd |
Windows 7对比
7zip
前面之所以说系统是Windows 10,先交代系统版本。是因为我在Windows 7虚拟机中观察到的测试结果和Windows 10完全不一样,在Windows 7中,使用相同版本7zip对同一个文件压缩两次,产生的文件MD5是相同的。
1 | 文件名称: C:\Users\VM\Desktop\ZIPTest\mmm-1242-01.zip |
在Autospy中观察:
1 | Name |
在Windows 7中Accessed时间不会产生变化。调整系统时钟的方式也进行了测试,压缩文件MD5同样不产生变化。
Zip for Windows
同一个文件连续压缩MD5相同(包括时钟调整),Autospy查看信息与Windows 10上一样,Accessed、Created时间都是0000-00-00 00:00:00
,就不贴出来了。
结语
到这里我的疑问就解答完毕啦😃,其实不看源代码的话,可以直接调整操作系统时间,每次调整的时候再进行压缩,通过HEX值对比的方式找答案。
参考
1、https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
2、https://cyberforensicator.com/2018/03/25/windows-10-time-rules/
3、https://forensixchange.com/posts/19_04_22_win10_ntfs_time_rules/
4、https://n-archives.net/articles/windows/ntfs-mft-timestamps/
5、https://superuser.com/questions/973547/how-can-i-display-all-8-ntfs-timestamps
6、http://gnuwin32.sourceforge.net/packages/zip.htm
7、https://www.cnblogs.com/shuidao/p/3262304.html
8、https://www.cnblogs.com/shuidao/p/3266092.html
9、https://github.com/sleuthkit/autopsy
10、https://unix.stackexchange.com/questions/91197/how-to-find-creation-date-of-file