zlib ist eine dünne Hülle um mit dem DEFLATE Algorithmus komprimierten Daten und ist in RFC1950 definiert:
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 |
+=====================+---+---+---+---+
So fügt sie mindestens zwei, möglicherweise sechs Bytes vor und 4 Bytes mit einem ADLER32 Prüfsumme nach den rohen DEFLATE komprimierten Daten.
Das erste Byte enthält die CMF (Compression Method and flags), die in CM aufgespalten wird (Komprimierungsverfahren) (erste 4 Bits) und CINFO (Kompression info) (letzte 4 Bits) .
Von diesem ist ziemlich klar, dass leider bereits die ersten zwei Bytes eines zlib-Streams stark variieren können, abhängig davon, welche Komprimierungsmethode und Einstellungen verwendet wurden.
Zum Glück stolperte ich über einen Beitrag von Mark Adler, dem Autor des ADLER32 Algorithmus, wo er lists the most common and less common combinations of those two starting bytes.
, die mit der Art und Weise aus, schauen wir uns an, wie wir Python verwenden können zlib zu untersuchen:
>>> import zlib
>>> msg = 'foo'
>>> [hex(ord(b)) for b in zlib.compress(msg)]
['0x78', '0x9c', '0x4b', '0xcb', '0xcf', '0x7', '0x0', '0x2', '0x82', '0x1', '0x45']
So die zlib Daten von zlib
Moduls Python erstellt (Standardoptionen) beginnt mit 78 9c
. Wir verwenden das, um ein Skript zu erstellen, das ein benutzerdefiniertes Dateiformat schreibt, das eine Präambel, einige zlib-komprimierte Daten und eine Fußzeile enthält.
Wir haben dann ein zweites Skript schreiben, das für das Zwei-Byte-Muster eine Datei durchsucht, beginnt alles Dekomprimieren, die als zlib Strom folgt und herausfindet, wo der Strom endet und die Fußzeile beginnt.
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)
Hier nehmen wir msg
, komprimieren sie mit zlib, und umgeben sie mit einem Kopf- und Fußzeile , bevor wir sie in eine Datei schreiben.
Header und Fußzeile haben in diesem Beispiel eine feste Länge, aber sie könnten natürlich beliebige, unbekannte Längen haben.
Jetzt für das Skript, das versucht, einen ZLIB-Stream in einer solchen Datei zu finden. Denn für dieses Beispiel wissen wir genau, welche Marker zu erwarten, dass ich nur eine verwenden, aber offensichtlich die Liste ZLIB_MARKERS
könnte mit allen Markierungen aus dem oben genannten Beitrag gefüllt werden.
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."
Die Idee ist folgende:
Beginnen Sie am Anfang der Datei und erstellen Fenster eine Zwei-Byte-Suche.
Verschieben Sie das Suchfenster in Ein-Byte-Schritten vorwärts.
Überprüfen Sie für jedes Fenster, ob es mit einem der zwei Byte-Marker übereinstimmt, die wir definiert haben.
Wenn eine Übereinstimmung gefunden wird, notieren Sie die Startposition, stoppen Sie die Suche und versuchen Sie, alles, was folgt, zu dekomprimieren. nicht so trivial, wie die Suche nach zwei Marker Bytes
Nun ist das Ende des Stroms zu finden. zlib-Streams werden weder durch eine feste Bytefolge terminiert noch ist ihre Länge in einem der Headerfelder angegeben. Stattdessen wird es durch eine vier Byte ADLER32 Prüfsumme beendet, die die Daten bis zu diesem Punkt übereinstimmen müssen.
Das System funktioniert so, dass die interne C-Funktion inflate()
kontinuierlich hält versucht, den Strom zu dekomprimieren, wie sie es liest, und wenn es über eine passende Prüfsumme kommt, Signale, die an den Aufrufer, was darauf hinweist, dass der Rest der Daten sind nicht mehr Teil des zlib-Streams.
In Python ist dieses Verhalten bei der Verwendung von Dekompressionsobjekte statt einfach Aufruf zlib.decompress()
ausgesetzt. Ein Aufruf von decompress(string)
auf einem Decompress
Objekt dekomprimiert einen ZLIB-Stream in string
und gibt die dekomprimierten Daten zurück, die Teil des Streams waren. Alles, was dem Stream folgt, wird in unused_data
gespeichert und kann danach abgerufen werden.
Start of zlib stream found at byte 6
Header: HEADER
Message: foo
Footer: FOOTER
Das Beispiel lässt sich leicht die unkomprimierte Nachricht in einer Datei modifiziert werden, um zu schreiben, anstatt sie zu drucken:
Dies sollte die folgende Ausgabe auf eine Datei mit dem ersten Skript erstellt produzieren. Dann können Sie die zuvor komprimierten Daten von zlib weiter analysieren und versuchen, bekannte Felder in den von Ihnen getrennten Kopf- und Fußzeilen zu identifizieren.
Ich verstehe Ihre Frage richtig: Sie haben keine genaue Spezifikation des Dateiformats und möchten etwas über Reverse-Engineering-Techniken wissen, um die Struktur und das Layout des Dateiformats zu identifizieren? –
@LukasGraf Schau nicht zu hart auf mich, aber die Antwort ist ... Ja! Es ist so ziemlich eine letzte Anstrengung unseres Unternehmens mit diesem Dateiformat, aber jeder Fortschritt wäre uns wichtig. – heltonbiker
Ok, wollte nur so stellen Sie sicher, dass ich die Frage richtig verstehe. Ich wollte nicht verurteilend klingen :) Aber Python gilt dann wahrscheinlich nur als die Sprache für die Implementierung, sobald Sie fertig sind mit dem Reverse Engineering des Dateiformats (was wahrscheinlich viel mehr Zeit in Anspruch nehmen wird als die Implementierung selbst). –