Chapter 14
圧縮と暗号化の見分け — エントロピーで領域を分類する
この章のゴール.
エントロピーを使って、ダンプの各領域をコード・データ・空き・圧縮・暗号化に分類できるようになること。 「高エントロピーだが圧縮か暗号か」を、ヘッダとヒストグラムで見分けられること。
この章で使う既出の用語(定義は各リンク先). メモリマップ(01 章 3 節)、binwalk(02 章 3 節)、unblob(02 章 3 節)、オフセット(03 章 1 節)、文字列(07 章 6 節)、MCUboot(08 章 1 節)、TLV(08 章 4 節)、メタデータ(08 章 1 節)、squashfs(09 章 2 節)、ファイルシステム(09 章 1 節)
1. エントロピーとは何か
エントロピー(情報量。ここではシャノンエントロピー)は、「バイト列がどれだけランダムに見えるか」を 0〜8 の数値で表したものである。 ある区間の各バイト値 0〜255 の出現確率を $p_i$ として、
- すべて同じバイト(0xFF の空き領域): H ≈ 0
- 英語のテキスト: H ≈ 4〜4.5
- 機械語コード: H ≈ 5.5〜6.5
- 圧縮・暗号化データ: H ≈ 7.9〜8.0(ほぼ完全にランダム)
つまり、区間ごとに H を計算すれば、中身の種類の当たりがつく。
2. 区間ごとに測る
binwalk -E dump.bin # 一定窓ごとの H をグラフに(matplotlib 必須)
ent dump.bin # 全体の H・カイ二乗・圧縮可能性自分で計算するのも簡単で、境界を細かく調整できる。
import math
def entropy(b):
if not b: return 0.0
from collections import Counter
c = Counter(b); n = len(b)
return -sum((v/n)*math.log2(v/n) for v in c.values())
d = open("dump.bin","rb").read()
W = 4096
for off in range(0, len(d), W):
h = entropy(d[off:off+W])
bar = "#" * int(h*6)
print(f"{off:#08x} {h:4.2f} {bar}")出力の「H が段になって変わる位置」が、領域の境界である。これを 01 章のメモリマップに反映する。
0x000000 5.9 ################### ← コード
0x008000 6.1 ####################
0x010000 4.2 ############# ← 文字列・データ
0x018000 0.0 ← 空き(0xFF)
0x020000 7.99 ################################ ← 圧縮 or 暗号3. 高エントロピーを「圧縮」と「暗号」に分ける
H ≈ 8 の区間は、圧縮データ・暗号化データ・鍵のどれか。ここからが見分けである。
手がかり 1: ヘッダ / マジック
圧縮には形式があり、先頭にマジックがある(03 章)。暗号化データには通常マジックがない(あってもラッパのヘッダだけ)。
| 先頭バイト | 形式 |
|---|---|
1F 8B | gzip |
28 B5 2F FD | zstd |
FD 37 7A 58 5A | xz |
42 5A 68 | bzip2 |
5D 00 00 | lzma(生) |
04 22 4D 18 | lz4 |
| ヘッダなしで H≈8 | 暗号化の可能性が高い |
binwalk dump.bin # 圧縮形式を検出してオフセットを示す
# 見つけた圧縮を試しに展開
dd if=dump.bin bs=1 skip=$((0x20000)) | gzip -dc 2>/dev/null | head -c 64 | xxd手がかり 2: ヒストグラムとカイ二乗
暗号化データは全バイト値がほぼ均一に出る(カイ二乗が理想値に近い)。 圧縮データもランダムに近いが、形式によっては微妙な偏り(ブロックヘッダ、辞書の痕跡)がある。ent のカイ二乗値と「ランダムを超える確率」を見る。
ent dump_region.bin
# Chi square distribution ... would exceed this value 50.00% ... ← 均一に近い=暗号らしい手がかり 3: 展開できるか
圧縮なら展開すれば意味のあるデータが出る。暗号なら鍵がなければゴミのまま。 候補のオフセットから各展開ツールを試す(binwalk -e / unblob が自動でやってくれる)。展開に成功して中に文字列やコードが現れれば圧縮、どうやっても出なければ暗号(または未知の圧縮)。
4. なぜ見分けが大事か
- 圧縮なら展開して中身を解析できる(ファーム本体が gzip/lzma で圧縮されて OTA 配布される、Linux の squashfs、15 章)
- 暗号化なら、鍵なしでは中身に踏み込めない。時間を無駄にしない判断ができる。また「ファームが暗号化されている=一定の保護がある」という設計の読み取りにもなる(13 章の逆)
- 鍵(短い高エントロピー)は 13 章の対象
5. 部分的に圧縮・暗号された構造
実際のイメージは、低エントロピーのヘッダ + 高エントロピーの本体という組み合わせが多い。
- MCUboot(08 章): ヘッダ(低)+ アプリ(中〜高)+ TLV(低)。アプリが暗号化されていれば本体だけ H≈8
- OTA パッケージ: マニフェスト(低・テキスト)+ 圧縮/暗号本体(高)
- ファイルシステム: メタデータ(低)+ 圧縮ファイル(高)が混在
だから「区間を細かく測る」ことが効く。ヘッダの低エントロピー部分に形式の手がかりが残る。
6. 差分でも使える
同じ機器の 2 つのダンプ(更新前後、正常と異常)を比べるとき、エントロピーのプロファイルを並べるとどの領域が変わったかが一目で分かる(18 章)。
7. 手を動かす
エントロピーで領域を塗り分ける
この章のポイント
- エントロピー H(0〜8)は「ランダムさ」。空き ≈ 0、テキスト ≈ 4、コード ≈ 6、圧縮/暗号 ≈ 8
- 区間ごとに H を測ると、領域の境界と種類が見える。メモリマップに反映する
- H ≈ 8 は圧縮か暗号。マジックがあれば圧縮(gzip
1F 8Bなど)、なければ暗号を疑う - 展開できれば圧縮。カイ二乗の均一さも手がかり。
binwalk -e/unblobが自動判定 - ヘッダは低エントロピーで手がかりが残る。差分にもエントロピープロファイルが使える