独自ファイル形式のバイナリ解読

レガシーな測定ソフトに多い、そのソフト専用のデータ形式の解読に成功したので。

はじめに

私は現在物理実験系研究室の学生として日々を送っているが、その研究室の機材はハードウェアからソフトウェアまで全てが20年物である。 まさに「レガシー」という言葉がふさわしい。

そんなレガシーなソフトウェア環境が生み出すのが『独自ファイル形式』という魔物である。

2020年代であれば、データはCSVなど"Jupyter"(Julia, Python, R)でデータフレームとして読み込める形式でフォーマットするのが普通だろう。 ところが、そういうファイル形式は文字列でデータを保存するためファイルが大きくなりがちである。

20年前のPCの性能は今よりもはるかに劣っている(1Core, メモリ1GBなど)ため、CSVは『無駄が多い』と考えられていたのか、データはバイナリとして保存される。 そして、そのバイナリは基本的に専用の解析ソフトしか読み方を知らない。 さらに、その測定ソフトのUIも20年前で止まっているため、現在は淘汰された『メインウィンドウにミニウィンドウが重なって開くシステム』である。

現代のデータエンジニアリング技術を学んできた私としては、こんな環境でデータ分析など不可能なレベルであった。 そこで、測定ソフトが吐き出した『独自ファイル形式』の生データを解析して、"Jupyter"で扱えるようにしたのであった。

用意するもの

  • 条件を1bitずつ変えた測定ソフトの生データいくつか
  • Jupyter Notebook
  • 計算機の仕組みの知識
  • 暗号学の基礎テクニック

今回はPythonで解析を行ったが、データを可視化できるパッケージがあるなら言語は何でもよい。

解析方法

ファイルサイズを比較する

条件を変えたファイルのファイルサイズを比較してみる。 条件とファイルサイズの関係性を見つけられれば一歩前進だ。

import os
filepaths = []  # 生データのファイルパス
filesizes = {filepath: os.path.getsize(filepath) for filepath in filepaths}

デコードしてみる

バイナリの中から意味の文字列を抽出できれば解読のヒントになるので、とりあえず適当な文字コードでデコードしてみる。

filepath = filepaths[0]
with open(filepath, "rb") as f:
    raw = f.read()
raw

PythonのBytes表示はバイト列をUTF-8でデコードしてくれるので、rawを眺めるだけでヒントを得られるかもしれない。

文字化けなどしてたら、cp932shift_jisでのデコードを検討する。

Hex表示する

バイト列なんて人間が読めるわけないと思うかもしれないが、模様のように眺めていると規則性が見えてくることがある。

ざっと眺めて、00の個数や頻出するバイトはないか確認しておく。

raw.hex(" ")

バイト数を512や1024で割る

フロッピーディスク時代の遺産であれば、データは512バイトごとのセクターに区切られているはずである。 その場合、末尾を00でパディングされたセクターがあれば、そのセクターがデータの区切りであるということがわかる。

バイナリのヘッダーを取り除けば、512や1024で割り切れることもあるので、いろいろ試してみる。

header_end_index = 64
header = raw[:header_end_index]
body = raw[header_end_index:]
len(body) / 512
sector_size = 512
sectors = [body[i:i+sector_size] for i in range(0. len(body). sector_size)]

セクターの特徴をつかむ

各セクターのバイトをカウントして、領域ごとに特徴がないかを探す。

import collectios
[collections.Counter(sector) for sector in sectors]

再びHex表示する

データ領域を絞り込めたら、再びHex列を眺める。 その領域が何バイトで1つの値なのかを特定する。

  • int16型: 2バイト
  • float型: 4バイト
  • double型: 8バイト

可視化する

データを直列に可視化するだけでも見えてくるものがある。 中に入っているであろうデータの特徴から、可視化を工夫する。

import plotly.express as px
px.line(data)

ひたすら仮説検証サイクルを回す

あとはただひたすら上手くいくまで仮説検証を繰り返すだけである。

解析ソフトの表示、測定器の原理、etc... あらゆる情報を元にバイナリ構造に対して仮説を立てて、加工・表示を繰り返す。

期待するデータが得られればバイナリ解読は終了である。

終わりに

正直なところ何度も心が折れかけた。 だが、今後ゴミUIに付き合う嫌悪感と、この解析は自分にしかできないという自負が原動力となった。

あるときカチッと全ての仮説が噛み合う。 解読が終わった時の達成感は、残念ながらもう忘れてしまった。 なぜならば解読は『スタートライン』であり、そのデータを使ったデータ分析が真の目的であるからだ。

比較するのも畏れ多いが、ドイツ軍のエニグマを解読した時も似たような感覚だったのだろう。 解読した直後は声を上げて喜ぶが、それは一時的なもので本当の仕事はそれからなのである。

たぶんこのスキルはマネタイズできるけど、こんなこと仕事にしてたら過労死する。 期限3日で、2日で解読したからかもしれないが。

というよりも、成功するかわからない仮説検証サイクルに期限を設けてはいけないと思う。 いや期限を設けても良いが、仮に仮説検証が早く終わってもすぐに次の仕事を与えてはいけない。 一定期間内に分析する案件数の上限を設定しておいて、早く終わった場合はその分休みを与えなければ分析者が精神崩壊してしまう。 仕事が早く終わったのは、その人が優秀なわけではなくで、莫大なリソースをつぎ込んで高速な仮説検証サイクルを回して、『たまたま』解が見つかっただけだからだ。 解読ビジネスをする場合は心に留めておいてほしい。