zlib è un involucro sottile intorno dati compressi con l'algoritmo DEFLATE ed è definito nella 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 |
+=====================+---+---+---+---+
Quindi aggiunge almeno due, forse sei byte prima e 4 byte con un ADLER32 checksum dopo i dati compressi grezzi di DEFLATE.
Il primo byte contiene il CMF (Metodo di compressione e bandiere), che è diviso in CM (Metodo di compressione) (primi 4 bit) e CINFO (Compression informazioni) (ultimi 4 bit) .
Da questo è abbastanza chiaro che purtroppo già i primi due byte di un flusso zlib può variare molto a seconda di cosa compressione metodo e impostazioni sono stati utilizzati.
Per fortuna, sono incappato in un post di Mark Adler, l'autore dell'algoritmo ADLER32 , dove ha lists the most common and less common combinations of those two starting bytes.
Con quella di mezzo, diamo un'occhiata a come possiamo usare Python per esaminare 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']
Quindi i dati zlib creati dal modulo di Python zlib
(utilizzando le opzioni di default) inizia con 78 9c
. Lo useremo per creare uno script che scriva un formato di file personalizzato che contenga un preambolo, alcuni dati compressi con zlib e un piè di pagina.
Abbiamo poi scrivere un secondo script che esegue la scansione di un file per quel modello di due byte, inizia la decompressione tutto ciò che segue come un flusso di zlib e capisce dove finisce stream e inizia il piè di pagina.
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)
Qui prendiamo msg
, comprimerlo con zlib, e circondano con un colpo di testa e piè di pagina prima che lo scriviamo in un file.
L'intestazione e il piè di pagina sono di lunghezza fissa in questo esempio, ma potrebbero naturalmente avere lunghezze arbitrarie e sconosciute.
Ora per lo script che tenta di trovare uno stream zlib in tale file. Perché per questo esempio sappiamo esattamente quale indicatore attendersi sto usando solo uno, ma ovviamente l'elenco ZLIB_MARKERS
potrebbe essere riempito con tutti i marcatori dal post menzionati sopra.
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."
L'idea è questa:
cominciare all'inizio del file e creare una finestra di due byte di ricerca .
Sposta la finestra di ricerca in avanti con incrementi di un byte.
Per ogni finestra controllare se corrisponde a uno dei due indicatori di byte definiti .
Se viene trovata una corrispondenza, registrare la posizione di partenza, interrompere la ricerca e provare a decomprimere tutto ciò che segue.
Ora, trovando la fine del flusso non è così banale come alla ricerca di due marcatore byte. i flussi zlib non sono né terminati da una sequenza di byte fissi, né è la lunghezza indicata in nessuno dei campi dell'intestazione. Invece è terminato da un checksum ADLER32 a quattro byte che deve corrispondere ai dati fino a questo punto.
Il modo in cui funziona è che la funzione interna C inflate()
mantiene continuamente cercando di decomprimere il flusso come lo legge, e se viene rilevato un checksum corrispondenza, segnali che al chiamante, indicando che il resto della I dati non fanno più parte del flusso zlib.
In Python questo comportamento viene esposto quando si utilizzano oggetti di decompressione anziché semplicemente chiamando zlib.decompress()
. Chiamando decompress(string)
su un Decompress
l'oggetto decomprimerà un flusso zlib in string
e restituirà i dati decompressi che facevano parte del flusso. Tutto ciò che segue lo stream verrà archiviato in unused_data
e potrà essere recuperato in seguito dallo .
Questo dovrebbe produrre il seguente output su un file creato con il primo script:
Start of zlib stream found at byte 6
Header: HEADER
Message: foo
Footer: FOOTER
L'esempio può essere facilmente modificato per scrivere il messaggio non compresso in un file invece di stamparlo. Quindi è possibile analizzare ulteriormente i dati compressi precedentemente zlib e provare a identificare i campi noti nei metadati nell'intestazione e nel piè di pagina separati.
Am I capire la vostra domanda giusta: Tu non hai la corretta specificazione del formato di file e volete sapere su tecniche di reverse engineering per identificare la struttura e il layout del formato di file? –
@LukasGraf Non sembrare troppo duro con me, ma la risposta è ... Sì! È praticamente un ultimo sforzo della nostra azienda con questo formato di file, ma qualsiasi progresso sarebbe importante per noi. – heltonbiker
Ok, volevo solo assicurarmi di aver capito bene la domanda. Non intendevo sembrare critico :) Ma probabilmente Python si applica solo come linguaggio per l'implementazione, una volta terminato il reverse engineering il formato del file (che richiederà probabilmente molto più tempo dell'implementazione stessa). –