zlib 是使用
DEFLATE算法压缩的数据的瘦包装器,并在RFC1950中定义:
A zlib stream has the following structure:
0 1
+---+---+
|CMF|FLG| (more-->)
+---+---+
(if FLG.FDICT set)
0 1 2 3
+---+---+---+---+
| DICTID | (more-->)
+---+---+---+---+
+=====================+---+---+---+---+
|...compressed data...| ADLER32 |
+=====================+---+---+---+---+
因此,它在原始 DEFLATE 压缩数据之前添加至少两个(可能是六个)字节和带有
ADLER32校验和的 4 个字节。
第一个字节包含CMF(压缩方法和标志),分为CM(压缩方法)(前 4 位)和CINFO(压缩信息)(后 4 位)。
从这里可以清楚地看出,不幸的是,zlib 流的前两个字节可能会根据使用的压缩方法和设置而有很大差异。
幸运的是,我偶然发现了 ADLER32 算法的作者 Mark Adler 的一篇文章,他在其中列出了这两个起始字节的最常见和不太常见的组合。
顺便说一句,让我们看看如何使用 Python 来检查 zlib:
>>> import zlib
>>> msg = 'foo'
>>> [hex(ord(b)) for b in zlib.compress(msg)]
['0x78', '0x9c', '0x4b', '0xcb', '0xcf', '0x7', '0x0', '0x2', '0x82', '0x1', '0x45']
因此 Python 的zlib
模块(使用默认选项)创建的 zlib 数据以
78 9c
. 我们将使用它来创建一个脚本,该脚本编写一个自定义文件格式,包含一个序言、一些 zlib 压缩数据和一个页脚。
然后,我们编写第二个脚本,扫描文件以查找该双字节模式,开始解压缩后面的所有内容作为 zlib 流,并确定流的结束位置和页脚的开始位置。
创建.py
import zlib
msg = 'foo'
filename = 'foo.compressed'
compressed_msg = zlib.compress(msg)
data = 'HEADER' + compressed_msg + 'FOOTER'
with open(filename, 'wb') as outfile:
outfile.write(data)
在这里,我们msg
使用 zlib 对其进行压缩,并在将其写入文件之前用页眉和页脚将其包围。
在此示例中,页眉和页脚的长度是固定的,但它们当然可以具有任意的未知长度。
现在对于尝试在此类文件中查找 zlib 流的脚本。因为对于这个例子,我们确切地知道我只使用了一个标记,但显然列表ZLIB_MARKERS
可以填充上面提到的帖子中的所有标记。
身份文件
import zlib
ZLIB_MARKERS = ['\x78\x9c']
filename = 'foo.compressed'
infile = open(filename, 'r')
data = infile.read()
pos = 0
found = False
while not found:
window = data[pos:pos+2]
for marker in ZLIB_MARKERS:
if window == marker:
found = True
start = pos
print "Start of zlib stream found at byte %s" % pos
break
if pos == len(data):
break
pos += 1
if found:
header = data[:start]
rest_of_data = data[start:]
decomp_obj = zlib.decompressobj()
uncompressed_msg = decomp_obj.decompress(rest_of_data)
footer = decomp_obj.unused_data
print "Header: %s" % header
print "Message: %s" % uncompressed_msg
print "Footer: %s" % footer
if not found:
print "Sorry, no zlib streams starting with any of the markers found."
这个想法是这样的:
现在,找到流的结尾并不像寻找两个标记字节那么简单。zlib 流既不以固定的字节序列终止,也不在任何头字段中指示它们的长度。相反,它由一个四字节的 ADLER32 校验和终止,该校验和必须与此时的数据匹配。
它的工作方式是内部 C 函数inflate()
在读取流时不断尝试解压缩流,如果遇到匹配的校验和,则向其调用者发出信号,表明其余数据不属于zlib 流了。
在 Python 中,当使用解压缩对象而不是简单地调用zlib.decompress()
. 调用decompress(string)
一个Decompress
对象将解压缩一个 zlib 流string
并返回作为流的一部分的解压缩数据。流之后的所有内容都将被存储unused_data
并可以在之后检索。
这应该在使用第一个脚本创建的文件上产生以下输出:
Start of zlib stream found at byte 6
Header: HEADER
Message: foo
Footer: FOOTER
该示例可以很容易地修改为将未压缩的消息写入文件而不是打印它。然后你可以进一步分析以前zlib压缩的数据,并尝试在你分离出来的页眉和页脚中识别元数据中的已知字段。