2012-08-27 60 views
6

我的公司使用傳統文件格式用於Electromiography數據,該數據已不再生產。然而,對維持復古兼容性有興趣,所以我正在研究爲該文件格式編寫閱讀器的可能性。從python中的二進制文件中提取zlib壓縮數據

通過分析Delphi編寫的一個非常複雜的前源代碼,文件讀取器/寫入器使用ZLIB,並且在HexEditor內部,它看起來像是二進制ASCII文件頭(帶有「Player」,「Analyzer」隨時可讀),後面是一個包含原始數據的壓縮字符串。

我的疑問是:我應該如何進行,以便查明:

  • 如果是壓縮流;
  • 壓縮流在哪裏開始,它在哪裏結束;

維基百科:

zlib的壓縮數據通常寫入了gzip或ZLIB 包裝。包裝器通過添加一個 頭和尾部來封裝原始DEFLATE數據。這提供了流識別和錯誤 檢測

這與此有關嗎?

我很樂意發佈更多信息,但我不知道最相關的是什麼。

感謝您的任何提示。

編輯:我有工作的應用程序,並可以使用它來記錄任何時間長度的實際數據,如果有必要獲取文件甚至小於1KB。


一些示例文件:

一個新近創建的一個,而無需數據流:https://dl.dropbox.com/u/4849855/Mio_File/HeltonEmpty.mio

同上非常短的(即1秒?)數據流已被保存後:https://dl.dropbox.com/u/4849855/Mio_File/HeltonFilled.mio

與名爲「manco」而不是「Helton」的病人不同,它具有更短的流(適用於十六進制查看):https://dl.dropbox.com/u/4849855/Mio_File/manco_short.mio

說明:每個文件應該是病人(一個人)的文件。在這些文件中,保存一個或多個考試,每門考試包含一個或多個時間序列。提供的文件只包含一個考試,一個數據系列。

+0

我理解你的問題的正確性:你沒有適當的文件格式規範,並且想知道反向工程技術來識別文件格式的結構和佈局嗎? –

+0

@LukasGraf不要太看重我,但答案是......是的!這是我們公司採用這種文件格式的最後努力,但任何進展對我們都很重要。 – heltonbiker

+0

好的,只是想確保我正確理解問題。我的意思不是聽起來很有道理:)但是,一旦你完成逆向工程的文件格式(這可能比實現本身需要更多的時間),Python可能只適用於實現的語言。 –

回答

6

要開始了,爲什麼不掃描文件的所有有效的壓縮數據流(這是足夠好的小文件,並找出格式):

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

看起來像數據流包含小端雙精度浮點數據:

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

我很驚訝,我無話可說。它只是平原工作!當然,我將不得不在明天(現在在家)研究這些微妙之處,但我會回過頭來評論一些關鍵點,以便其他人可能會受益。非常感謝您的時間和興趣! – heltonbiker

8

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 | 
    +=====================+---+---+---+---+ 

所以它增加了至少兩個,可能六個字節之前和4個字節的 ADLER32原始DEFLATE壓縮數據後的校驗和。

第一字節包含CMF(壓縮方法和標誌),其被劃分 成CM(壓縮方法)(前4位)和CINFO(壓縮信息)(最後 4比特) 。

由此可以明顯看出,zlib流的前兩個字節 已經很不一樣了,具體取決於使用了哪些壓縮方法和設置。

幸運的是,我偶然發現了ADLER32 算法的作者Mark Adler的帖子,他在那裏他lists the most common and less common combinations of those two starting bytes

有了這樣的方式,讓我們來看看我們如何可以使用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的流計算出 一切,當流結束,頁腳開始。

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的流腳本。因爲 這個例子中,我們確切地知道標記期望我使用的只有一個,但顯然 名單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." 

的想法是這樣的:

  • 開始在文件的開頭,並創建一個雙字節搜索 窗口。

  • 向前移動搜索窗口中一個字節的增量。

  • 因爲如果它匹配任何我們 定義的兩個字節標記每個窗口的檢查。

  • 如果找到匹配項,請記錄起始位置,停止搜索並嘗試解壓後面的所有內容。現在

,找到流的末尾不如尋找兩個標記 字節微不足道。 zlib流既不是由固定字節序列終止的,也不是在任何頭部字段中指示的長度。取而代之的是,一個四字節的ADLER32校驗和終止於 ,這個校驗和必須匹配到目前爲止的數據。

它的工作方式是內部的C函數inflate()汽車無保留 嘗試,因爲它讀取它來解壓縮流,如果它遇到一個 匹配校驗信號,要它的調用者,表明的休息 數據不再是zlib流的一部分。

在Python中,當使用解壓縮對象而不是簡單的 調用zlib.decompress()時,會暴露此行爲。在Decompress對象 上調用decompress(string)將解壓縮string中的zlib流並返回作爲流一部分的解壓縮數據。該流後面的所有內容都將存儲在unused_data之後,並且可以在之後檢索到 。

這應該產生與所述第一 腳本創建的文件中的下列輸出:

Start of zlib stream found at byte 6 
Header: HEADER 
Message: foo 
Footer: FOOTER 

的例子可以很容易地被修改以寫入未壓縮消息到文件 不是打印它的。然後,您可以進一步分析以前的zlib 壓縮數據,並嘗試識別 分隔出的頁眉和頁腳中元數據中的已知字段。

+0

您提供的解釋非常有啓發性,我打算徹底閱讀。最後,cgohlke提供了一些適用於我的小文件的強力驅動程序,但是我想我必須像你所建議的那樣尋找兩個字節的頭文件。只要我得到一些好的結果,我就回來,非常感謝你的時間和興趣! – heltonbiker

+0

非常好!我認爲即使我的回答可能會提供一些關於問題的細節和背景的教育價值,@cgohlke提供的解決方案比我的優雅得多。它佔每個文件多個流,不依賴於兩個字節的標記。如果你可以讓zlib爲你做這件事,那比你自己做起來更容易,也更可靠。 –

+0

我很欣賞你的理解。其實,我已經選擇了他的答案,因爲他用一堆代碼行將正確的結果顯示在我的臉上,儘管我不打算在較大的文件上使用暴力破解,而是使用頭部字節以正確的方式尋找溪流的開始。再次感謝! – heltonbiker

相關問題