2012-08-27 9 views
6

Meine Firma verwendet ein Legacy-Dateiformat für Electromiography-Daten, das nicht mehr in Produktion ist. Es gibt jedoch ein gewisses Interesse an der Aufrechterhaltung der Retro-Kompatibilität, daher studiere ich die Möglichkeit, einen Leser für dieses Dateiformat zu schreiben.Extrahieren komprimierte zlib-Daten aus Binärdatei in Python

Durch die Analyse eines sehr verschachtelten früheren Quellcodes, der in Delphi geschrieben wurde, verwendet der Dateileser/Schreiber ZLIB, und innerhalb eines HexEditor sieht es aus wie ein Dateikopf im binären ASCII (mit Feldern wie "Player", "Analyzer") sofort lesbar), gefolgt von einer komprimierten Zeichenkette mit Rohdaten.

Mein Zweifel ist: Wie soll ich vorgehen, um zu identifizieren:

  • Wenn es ein komprimierter Strom ist;
  • Wo beginnt der komprimierte Stream und wo endet er?

Aus Wikipedia:

zlib komprimierten Daten werden typischerweise mit einem gzip geschrieben oder eine zlib -Wrapper. Der Wrapper kapselt die rohen DEFLATE-Daten ein, indem er einen Header und Trailer hinzufügt. Dies liefert Strom Erkennung und Fehlererkennung

Ist das relevant?

Ich werde gerne weitere Informationen posten, aber ich weiß nicht, was am relevantesten wäre.

Danke für jeden Hinweis.

EDIT: Ich habe die funktionierende Anwendung, und kann es verwenden, um tatsächliche Daten beliebiger Länge Länge aufzuzeichnen, Dateien sogar kleiner als 1kB, wenn nötig.


Einige Beispieldateien:

Ein frisch ein erstellt, ohne Datenstrom: https://dl.dropbox.com/u/4849855/Mio_File/HeltonEmpty.mio

Das gleiche wie oben bereits nach sehr kurzer (1 Sekunde?) Datenstrom gespeichert wurde: https://dl.dropbox.com/u/4849855/Mio_File/HeltonFilled.mio

Eine andere, von einem Patienten namens "Manco" anstelle von "Helton", mit einem noch kürzeren Strom (ideal für Hex-Betrachtung): https://dl.dropbox.com/u/4849855/Mio_File/manco_short.mio

Anleitung: Jede Datei sollte die Datei eines Patienten (einer Person) sein. Innerhalb dieser Dateien werden eine oder mehrere Prüfungen gespeichert, wobei jede Prüfung aus einer oder mehreren Zeitreihen besteht. Die bereitgestellten Dateien enthalten nur eine Prüfung mit einer Datenreihe.

+0

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? –

+0

@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

+0

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). –

Antwort

6

zu starten, warum die Dateien für alle gültigen Zip-Streams nicht scannen (es ist gut genug für kleine Dateien und das Format, um herauszufinden):

import zlib 
from glob import glob 

def zipstreams(filename): 
    """Return all zip streams and their positions in file.""" 
    with open(filename, 'rb') as fh: 
     data = fh.read() 
    i = 0 
    while i < len(data): 
     try: 
      zo = zlib.decompressobj() 
      yield i, zo.decompress(data[i:]) 
      i += len(data[i:]) - len(zo.unused_data) 
     except zlib.error: 
      i += 1 

for filename in glob('*.mio'): 
    print(filename) 
    for i, data in zipstreams(filename): 
     print (i, len(data)) 

Sieht aus wie die Datenströme Little-Endian doppelter Genauigkeit enthalten Gleitkommadaten:

import numpy 
from matplotlib import pyplot 

for filename in glob('*.mio'): 
    for i, data in zipstreams(filename): 
     if data: 
      a = numpy.fromstring(data, '<f8') 
      pyplot.plot(a[1:]) 
      pyplot.title(filename + ' - %i' % i) 
      pyplot.show() 
+1

Ich bin erstaunt, ich habe keine Worte. Es war einfach nur gut! Natürlich muss ich morgen die Feinheiten studieren (zu Hause), aber ich komme zurück, um einige wichtige Punkte zu kommentieren, damit andere davon profitieren. Vielen Dank für Ihre Zeit und Interesse! – heltonbiker

8

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.

+0

Die Erklärung, die Sie zur Verfügung gestellt haben, ist sehr aufschlussreich, und ich habe vor, sie gründlich zu lesen. Am Ende lieferte cgohlke eine Art Bruteforce, die für meine kleinen Dateien funktionierte, aber ich denke, ich werde nach dem Zwei-Byte-Header suchen müssen, wie Sie es vorgeschlagen haben.Sobald ich gute Ergebnisse erziele, komme ich zurück, vielen Dank für Ihre Zeit und Ihr Interesse! – heltonbiker

+0

Ausgezeichnet! Ich denke, obwohl meine Antwort einen pädagogischen Wert für die Details und den Hintergrund des Problems bieten kann, ist die Lösung, die von @cgohlke angeboten wird, viel eleganter als meine. Es berücksichtigt mehrere Streams pro Datei und ist nicht auf einen Zwei-Byte-Marker angewiesen. Wenn du es zlib so machen kannst, dass es für dich funktioniert, ist das einfacher und wahrscheinlich zuverlässiger, als es selbst zu tun. –

+0

Ich freue mich über Ihr Verständnis. Tatsächlich habe ich seine Antwort gewählt, weil er die richtigen Ergebnisse mit einer Reihe von Codezeilen direkt auf mein Gesicht geworfen hat, obwohl ich nicht vorhabe, Brute-Force auf die größeren Dateien anzuwenden, sondern stattdessen Ihre Header-Bytes zu verwenden nach dem Anfang der Ströme den richtigen Weg zu suchen. Danke noch einmal! – heltonbiker

Verwandte Themen