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 圧縮データの前に少なくとも 2 バイト、場合によっては 6 バイトを追加し、
ADLER32チェックサムを使用して 4 バイトを追加します。
最初のバイトにはCMF (圧縮方法とフラグ) が含まれ、 CM (圧縮方法) (最初の 4 ビット) とCINFO (圧縮情報) (最後の 4 ビット)に分割されます。
このことから、残念ながらすでに zlib ストリームの最初の 2 バイトが、使用されている圧縮方法と設定によって大きく異なる可能性があることは明らかです。
幸いなことに、私は ADLER32 アルゴリズムの作成者である Mark Adler による投稿を見つけました。そこでは、これら 2 つの開始バイトの最も一般的な組み合わせと一般的ではない組み合わせがリストされています。
それはさておき、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']
zlib
そのため、Python のモジュールによって (デフォルト オプションを使用して)
作成された zlib データは78 9c
. これを使用して、プリアンブル、いくつかの zlib 圧縮データ、およびフッターを含むカスタム ファイル形式を書き込むスクリプトを作成します。
次に、その 2 バイト パターンのファイルをスキャンし、zlib ストリームとして続くすべての圧縮解除を開始し、ストリームの終了位置とフッターの開始位置を特定する 2 つ目のスクリプトを作成します。
create.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 ストリームを見つけようとするスクリプトについて説明します。この例では、予想されるマーカーが正確にわかっているため、使用しているのは 1 つだけですが、ZLIB_MARKERS
上記の投稿のすべてのマーカーでリストを埋めることができることは明らかです。
ident.py
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."
アイデアは次のとおりです。
ファイルの先頭から始めて、2 バイトの検索ウィンドウを作成します。
検索ウィンドウを 1 バイト単位で前方に移動します。
すべてのウィンドウについて、定義した 2 バイト マーカーのいずれかと一致するかどうかを確認します。
一致が見つかった場合は、開始位置を記録し、検索を停止して、後続のすべてを解凍してみてください。
ここで、ストリームの終わりを見つけることは、2 つのマーカー バイトを探すことほど簡単ではありません。zlib ストリームは、固定バイト シーケンスで終了することも、その長さがヘッダー フィールドに示されることもありません。代わりに、この時点までのデータと一致する必要がある 4 バイトの ADLER32 チェックサムで終了します。
そのしくみは、内部の C 関数がinflate()
、ストリームを読み取るときに継続的に圧縮解除を試行し続け、一致するチェックサムに遭遇した場合、そのことを呼び出し元に通知し、残りのデータがストリームの一部ではないことを示します。 zlib ストリームはもうありません。
Python では、単純に を呼び出すのではなく、解凍オブジェクトを使用すると、この動作が明らかになりますzlib.decompress()
。オブジェクトを呼び出すdecompress(string)
と、zlib ストリームが解凍され、ストリームの一部であった解凍されたデータが返されます。ストリームに続くものはすべて に保存され、後で取得できます。Decompress
string
unused_data
これにより、最初のスクリプトで作成されたファイルに次の出力が生成されます。
Start of zlib stream found at byte 6
Header: HEADER
Message: foo
Footer: FOOTER
この例は、圧縮されていないメッセージを印刷する代わりにファイルに書き込むように簡単に変更できます。次に、以前に zlib で圧縮されたデータをさらに分析し、分離したヘッダーとフッターのメタデータで既知のフィールドを特定しようとします。