Binary Analysis 13 · 秘密情報の捜索 — 鍵・証明書・資格情報が残っていないか

Chapter 13

秘密情報の捜索 — 鍵・証明書・資格情報が残っていないか

この章のゴール.

自分の製品のダンプから、平文で残ってはいけない秘密(鍵・証明書・パスワード・トークン)を体系的に探せるようになること。 「見つかった=脆弱性」であることを理解し、何を保護すべきだったかを判断できること。

この章で使う既出の用語(定義は各リンク先). エントロピー(01 章 3 節)、binwalk(02 章 3 節)、SWD(04 章 2 節)、文字列(07 章 6 節)、署名(08 章 4 節)、NVM3(10 章 1 節)、ATE(11 章 3 節)、NVS(11 章 1 節)、バイナリ(12 章 3 節)

1. これは防御のための作業である

この章は「他人の製品から鍵を盗む」ためのものではない。 自分の製品を出荷前に自己監査し、「攻撃者が同じことをしたら何が漏れるか」を先に知るための作業である。 攻撃者がフラッシュを吸い出せる(04 章)以上、そこに平文で秘密があれば、それは漏れる。だから探して、保護する。

PSA 編・IoT セキュリティ編が「どう守るか」を扱うのに対し、本章は「守れているかをバイナリで確かめる」。

2. 探すべき秘密の一覧

種類何に使う漏れると
秘密鍵(ECC / RSA / 対称鍵)署名・暗号・認証なりすまし、通信の復号、偽ファーム
証明書と秘密鍵の対TLS クライアント認証機器のなりすまし
Wi-Fi / ネットワーク資格情報接続ネットワーク侵入
クラウドのトークン / API キーサーバ接続アカウント乗っ取り、課金
パスワード / PINローカル認証不正操作
共通のマスター鍵全機器共通の鍵(最悪)1 台の解析で全機器が破れる
デバッグ用の裏口開発時のバックドア認証回避

3. 高エントロピー領域を探す — 鍵の在りか

鍵はランダムなバイト列なので、周囲よりエントロピーが高い(14 章)。 コード(.text)は中程度、文字列(.rodata)は低め、鍵や圧縮/暗号データは高い。

binwalk -E dump.bin              # エントロピーのグラフ。急に高くなる区間が候補
ent dump.bin                     # 全体の統計

エントロピーが高い区間のうち、16 / 24 / 32 バイト(対称鍵)や 32 バイト(P-256 の秘密鍵)、04 で始まる 65 バイト(非圧縮の公開鍵)にきれいに区切れるものは鍵の候補である。 ただし、圧縮・暗号化されたデータも高エントロピーなので、区別は文脈による(14 章)。

高エントロピー領域を探す — 鍵の在りかオフセットHコード ~6文字列 ~4空き ~0鍵/圧縮/暗号 ~8鍵はランダム = 周囲より高エントロピー。16/24/32(対称)・32/66(ECC)に区切れる区間が候補binwalk -E / ent で測る。圧縮・暗号も高いので区別は文脈による(14 章)
鍵は高エントロピー。binwalk -E の急に高い区間で、鍵長に区切れるものが候補

4. 既知の形式で探す — 証明書と鍵

証明書や鍵には決まった形式があり、目印を検索できる。

PEM(テキスト形式)

strings -n 16 dump.bin | grep -E "BEGIN (CERTIFICATE|.*PRIVATE KEY|PUBLIC KEY)"
# -----BEGIN CERTIFICATE-----
# -----BEGIN EC PRIVATE KEY-----   ← これが出たら秘密鍵が平文

-----BEGIN ... PRIVATE KEY----- が見つかれば、秘密鍵が平文で埋まっている。切り出して openssl で確認する。

# 見つけた範囲を切り出して
openssl ec -in found.pem -text -noout       # EC 秘密鍵の内容
openssl x509 -in found.pem -text -noout     # 証明書の内容(発行先、有効期限)

DER(バイナリ形式)

DER の証明書・鍵は 30 82(SEQUENCE、2 バイト長)で始まることが多い。

# DER 証明書らしき先頭を探す(0x30 0x82 の後に妥当な長さ)
python3 -c "
d=open('dump.bin','rb').read(); i=-1
while (i:=d.find(b'\x30\x82',i+1))>=0:
    ln=int.from_bytes(d[i+2:i+4],'big')
    if 200<ln<4000: print(hex(i),'len',ln)"
# 候補を切り出して
# openssl x509 -inform DER -in cand.der -text -noout
# openssl asn1parse -inform DER -in cand.der            ← 構造を見る

openssl asn1parse は ASN.1(DER の中身の文法)を木構造で表示し、証明書か鍵か、中に何があるかを教えてくれる。

5. パターンで探す — トークンと資格情報

多くの資格情報は、見た目に特徴がある。

strings -n 12 dump.bin | grep -iE "password|passwd|secret|token|api[_-]?key|bearer|ssid|psk"
# JWT(eyJ で始まる Base64)
strings dump.bin | grep -oE "eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}"
# AWS アクセスキー
strings dump.bin | grep -oE "AKIA[0-9A-Z]{16}"
# 一般的な 32/64 桁 hex(鍵やハッシュ)
strings dump.bin | grep -oE "\b[0-9a-fA-F]{32,64}\b"

gitleaks / trufflehog のような資格情報スキャナは、多数のパターン(クラウド各社の鍵形式)を内蔵している。バイナリにも --no-git 系のモードで当てられる。

既知の形式・パターンで探すPEM(テキスト)BEGIN ... PRIVATE KEY = 平文の秘密鍵DER(バイナリ)30 82 で始まる証明書・鍵。openssl asn1parse証明書BEGIN CERTIFICATE。openssl x509 で中身JWT トークンeyJ で始まる Base64.Base64.Base64クラウド鍵AKIA…(AWS)などの決まった形汎用password/token/psk 文字列、32/64 桁 hexgitleaks / trufflehog は多数のパターンを内蔵。CI に組み込んで自動化(19 章)
PEM の BEGIN PRIVATE KEY、DER の 30 82、JWT の eyJ、クラウド鍵の形で検索

6. 設定ストレージの中を見る

10 章〜11 章のストレージには、しばしば秘密が入る。そして追記型なので、消したはずの古い秘密が残る。

7. 見つけたらどう判断するか

秘密が平文で見つかったとき、それが問題かどうかは保護の前提による。

状況判断
フラッシュ読み出し保護(04 章)が有効で、正規手段では読めない平文でも一定の防御。ただし保護回避(フォールト注入など)には無力。多層防御が望ましい
読み出し保護が無効 / 未設定で、SWD や SPI 直読みで読めた脆弱性。秘密は暗号化するか、セキュアエレメント / TF-M 側に置くべき(PSA 編 12 章・13 章)
全機器共通のマスター鍵が入っている重大。1 台の解析で全機器が破れる。機器ごとの鍵に(PSA 編 17 章のパターン F)
公開鍵・証明書だけ(秘密鍵はない)問題なし。公開鍵は秘密ではない

「見つかったこと」より、「読み出せる状態だったこと」が問題である。対策は本サイトの PSA 編・IoT セキュリティ編にある。

8. 監査を仕組みにする

出荷ごとに手で探すのは漏れる。自動化(19 章)する。

# CI に組み込む「秘密が平文で残っていないか」チェックの例
strings -n 12 firmware.bin | grep -qiE "BEGIN .*PRIVATE KEY" && echo "NG: 平文の秘密鍵" && exit 1
binwalk -E firmware.bin > entropy.txt        # エントロピーの回帰比較

9. 手を動かす

ダンプから秘密を捜索する


この章のポイント