Files
revkit/docs/spec/unknown_values_report.md
T
tkgstratorandClaude Opus 4.6 70fb7279d0 docs(spec): devicetoken = deviceIdToken の Base64 デコードと判明 — ESN のみで Python 実行可能
- CBOR "devicetoken" (216B) と "apphmac" (32B) は同一の DEVICE_ID_TOKEN の異なる表現
- 両方とも x-netflix-deviceidtoken HTTP レスポンスヘッダから取得
- NFSharedStore App Group コンテナに DEVICE_ID_TOKEN キーで永続化
- getAuthData() 内で: apphmac = UTF8 bytes, devicetoken = Base64.decode()
- Tweak キャプチャは ESN のみで Python 単体で appboot→MSL認証が実行可能

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 15:14:07 +00:00

351 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Unknown Values Investigation Report
Date: 2026-04-09
Updated: 2026-04-09
appboot → MSL 認証の Python 実装に必要な全値の調査結果。
各値について **固定/可変**、**再利用可否**、**Python 再現可否** を明記する。
---
## 1. バイナリ固定値 (アプリバージョン単位で不変)
これらはすべて Netflix iOS バイナリにハードコードされており、アプリが更新されない限り不変。
Python 定数として埋め込み済みで、追加調査は不要。
### kAppBootKey — RSA-4096 SPKI/DER (550B)
| 項目 | 値 |
|------|-----|
| バイナリ | NFWebCrypto.framework |
| オフセット | `0x0020cd31` (`__TEXT.__cstring`) |
| 格納形式 | Base64 エンコード (736 chars) |
| 用途 | appboot レスポンスの RSASSA-PKCS1-v1_5 署名検証 |
| ハンドル | `"ABKP"` |
| Python 定数 | `constants.IOS_APPBOOT_RSA_KEY_DER` |
| **固定/可変** | **固定** — バイナリ埋め込み |
| **再利用可否** | **可** — アプリバージョンが同じなら不変 |
| **Python 再現** | **可** — 定数として利用 |
### kAppBootEccKey — ECDSA P-256 SPKI/DER (91B)
| 項目 | 値 |
|------|-----|
| バイナリ | NFWebCrypto.framework |
| オフセット | `0x0020d10c` (`__TEXT.__cstring`) |
| 格納形式 | Base64 エンコード (124 chars) |
| 用途 | appboot レスポンスの ECDSA 署名検証 |
| ハンドル | `"ABECCKP"` |
| Python 定数 | `constants.IOS_APPBOOT_ECC_KEY_DER` |
| **固定/可変** | **固定** — バイナリ埋め込み |
| **再利用可否** | **可** |
| **Python 再現** | **可** |
### kSharkBootKey (prod) — ECDSA P-256 SPKI/DER (91B)
| 項目 | 値 |
|------|-----|
| バイナリ | NFWebCrypto.framework |
| オフセット | `0x0020d08f` |
| 用途 | Shark boot 署名検証 |
| Python 定数 | `constants.IOS_SHARKBOOT_KEY_DER` |
| **固定/可変** | **固定** |
| **再利用可否** | **可** |
| **Python 再現** | **可** |
### DH Prime p — 1024-bit (128B)
| 項目 | 値 |
|------|-----|
| バイナリ | **MslClient.framework** (NFWebCrypto ではない) |
| オフセット | `0x001265a0` (`__TEXT.__const`) |
| ロード関数 | `IosAdhKeyx::dhKeyGen` @ vaddr `0x00079d20` |
| Generator g | `5` (vaddr `0x00079d98`) |
| Python 定数 | `constants.IOS_DH_P`, `constants.IOS_DH_G` |
| **固定/可変** | **固定** — バイナリ埋め込み |
| **再利用可否** | **可** |
| **Python 再現** | **可** — `cryptography` ライブラリで DH 計算 |
```
9694e9d8 d93a5ac7 4c509b4b bce85e92
132cd19c ce477d1a 7e47d527 d9ec2915
15f0b8b3 e1eaed50 06e1b1b9 1ea25b91
a01b10e2 e834b8d6 60b2e321 ad644ce1
a83b328d 9014ee7e 16f1e44f fe89579a
c3ee47d6 68b6b766 87c2fe90 a35b5e60
28fd04ef ea882373 ecf60ba2 f637e4cd
aa1b6089 d6c0b561 a8e520e7 96de27df
```
### PSK / KDF Nonce
| 値 | オフセット | サイズ |
|----|-----------|--------|
| PSK: `027617984f6227539a630b897c017d69` | NFWebCrypto @ `0x1ac8f5` | 16B |
| Nonce: `809f82a7addf548d3ea9dd067ff9bb91` | NFWebCrypto @ `0x1ac905` | 16B |
| **固定/可変** | **固定** — バイナリ埋め込み |
|--------------|--------------------------|
| **再利用可否** | **可** |
| **Python 再現** | **可** — `constants.IOS_KDF_PSK` / `IOS_KDF_NONCE` |
### Device Header (128B CBOR)
| 項目 | 値 |
|------|-----|
| 用途 | key 33.6 scheme_data の先頭 128 バイト |
| Python 定数 | `constants.IOS_KEY336_DEVICE_HEADER` |
| **固定/可変** | **固定** — iPhone デバイスタイプ共通 (165/180 サンプルで一致) |
| **再利用可否** | **可** |
| **Python 再現** | **可** |
### AppID
| 項目 | 値 |
|------|-----|
| 値 | `a2becfec-b286-535c-b884-903a384caee6` |
| **固定/可変** | **固定の可能性が高い** — 全キャプチャで同一値。UUIDv5 形式 (deterministic) |
| **再利用可否** | **可** (要確認: アプリバージョン更新で変わる可能性あり) |
| **Python 再現** | **可** — 定数として埋め込み |
| **追加調査** | 複数バージョンで値が同一か確認 |
### AppKeyVersion
| 項目 | 値 |
|------|-----|
| 値 | `1` |
| **固定/可変** | **固定の可能性が高い** — 全キャプチャで同一値 |
| **再利用可否** | **可** |
| **Python 再現** | **可** |
| **追加調査** | サーバー側で拒否される場合はバージョン依存の可能性あり |
---
## 2. デバイス固有値 (デバイス依存だが再現可能)
### Full ESN
| 項目 | 値 |
|------|-----|
| キャプチャ値 | `NFAPPL-02-IPHONE9=1-AD0455EF27D3A7B8F0872932FD9837874AF3E6F90157195BD22A8063FEB0B79E` |
| 構造 | `NFAPPL-02-{MODEL}={VARIANT}-{64HEXCHARS}` |
| **固定/可変** | **デバイス固有・固定** — 同一デバイスでは不変。デバイスが変われば変わる |
| **再利用可否** | **可** — 同一デバイスに対しては永続的に有効 |
| **Python 再現** | **不可** — ESN 生成ロジックは未解明。キャプチャ値をパラメータとして渡す |
| **追加調査** | ESN suffix (64 hex chars) の導出元。Keychain/デバイス識別子から生成? |
### MGK (Model Group Key): enc_key_0 + sign_key_0
| 項目 | 値 |
|------|-----|
| enc_key_0 | `0817065e29e6d1c8668473af9e13b3c2` (16B) |
| sign_key_0 | `91f752f76d7ab4c2dc6e5b3ec1c0e5a16864421fe449be5457459602e298ebc1` (32B) |
| 導出元 | `SHA384(ESN)` → 3× TFIT-WB-AES-128-ECB → enc_key_0 (16B) + sign_key_0 (32B) |
| **固定/可変** | **ESN 依存・固定** — 同一 ESN からは常に同一の MGK が導出される |
| **再利用可否** | **可** — ESN が同じなら毎回同じ値 |
| **Python 再現** | **可** — `tools/emulate_tfit.py` (Unicorn ARM64 エミュレーション) |
| **追加調査** | 不要 — Phase 3 KDF のライブ HMAC ログと Python 実装の出力が完全一致を確認済み |
---
## 3. ランタイム可変値 (要調査)
### devicetoken (216B, protobuf)
| 項目 | 値 |
|------|-----|
| キャプチャ hex | `0608a1b7ebdc0312bc01...0a0d00ef` (216B) |
| 取得元 | Nbp.framework → NRM (Netflix Registration Management) サービスへ HTTP リクエスト |
| 取得関数 | `-[MslRegistration getDeviceTokensWithCallback:]` @ Nbp `0x0005d5c0` |
| protobuf 構造 | field 1 (varint): ID, field 2 (188B): opaque payload, field 4: nested proto |
| **固定/可変** | **可変** — NRM サービスから取得。241 セッション中 41 種のユニーク値を観測 |
| **再利用可否** | **不明** — 有効期限がある可能性。同一セッション内では安定 |
| **Python 再現** | **不可** — NRM サービスとの通信プロトコルが未解明 |
| **追加調査が必要** | ★ 有効期限の有無。一度取得した値がどのくらい再利用可能か。NRM API の仕様 |
### apphmac (32B) → **完全解決: サーバー発行の deviceIdToken**
| 項目 | 値 |
|------|-----|
| **正体** | Netflix サーバーが HTTP レスポンスヘッダ `x-netflix-deviceidtoken` で配信 |
| **永続化** | `NFSharedStore` App Group コンテナ (キー `DEVICE_ID_TOKEN`) |
| **初回状態** | **nil** — サーバー未発行のため空で送信可能 |
| **固定/可変** | セッション可変 — サーバーが認証状態変更時に新値を発行 |
| **再利用可否** | **可** — キャッシュして以降のリクエストで再利用 |
| **Python 再現** | **可** — 初回 nil → レスポンスヘッダから取得 → キャッシュ |
| **追加調査** | 不要 |
**2026-04-09 最終解明: apphmac = deviceIdToken (暗号計算ではない)**
### 静的解析で確定した導出パス
`FpsMgkAppIdAuthData::getAuthData()` (MslClient @ 0x284bc) の逆アセンブルにより確定:
```
apphmac = UTF8([IosMslClient._deviceIdToken])
= UTF8([device deviceIdToken])
```
フィールド名 "apphmac" は完全にミスリーディング。実際は NFWebCrypto/CDM 層から取得される
不透明なデバイス ID トークン文字列 (32B) がそのまま格納される。HMAC/SHA/HKDF は一切関与しない。
### コードパス (MslClient.framework)
| パス | 関数 | オフセット | 動作 |
|------|------|----------|------|
| A | `_updateEntityAuthDeviceIdToken` | 0x1050e4 | `[self deviceIdToken]` → `setApphmac()` → `this+0xe8` |
| B | `_migrationAppboot:` | 0x10df3c | `[device deviceIdToken]` → コンストラクタ x5 → `this+0xe8` |
| C | `_updateEntityAuthPostMigration` | 0x1121c4 | 同上 |
| setter | `setApphmac()` | 0x29930 | `std::string::assign(this+0xe8, arg)` |
| serializer | `getAuthData()` | 0x284bc | `this+0xe8` → ByteArray → CBOR "apphmac" |
### HKDF の正体 (apphmac とは無関係)
`AppleWebCrypto::HKDF(key=MGK, ikm=PSK, info=Nonce)` は apphmac の計算ではなく、
Phase 3 KDF の初期化に使用される内部関数。出力 `4c142e4b...` は apphmac と不一致であることを
243 個の CBOR キャプチャとの照合で確認済み。
### FpsMgkAppIdAuthData コンストラクタ (キャプチャ済み)
```
FpsMgkAppIdAuthData(
x1: mgkid = "NFAPPL-02-IPHONE9=1-" (ESN prefix)
x2: devtype = "NFAPPL-02-IPHONE9=1-AD0455..." (Full ESN)
x3: appid = "a2becfec-b286-535c-b884-903a384caee6"
w4: appkeyversion = 1
x5: apphmac/deviceIdToken = 不透明トークン (base64 encoded)
x6: webCrypto = AppleWebCrypto*
x7: authGen = SynchronizedCdmAuthGeneration*
)
```
### ~~appboot sign key (32B)~~ → **解決済み: Keychain キャッシュ**
| 項目 | 値 |
|------|-----|
| キャプチャ値 | `38b2030dd55e3367290213ca0d16ee079524ccd24fb7221a52145fb6de016fd8` |
| 用途 | appboot リクエスト全体 (8549B) の HMAC-SHA256 署名 |
| **固定/可変** | **~~可変 (導出値)~~** → **前回セッションの Phase 2 sign key (Keychain キャッシュ)** |
| **再利用可否** | 初回 appboot には不要 |
| **Python 再現** | **不要** — フレッシュ appboot では sign_key_1 で署名する |
| **追加調査** | **不要** |
**2026-04-09 解決:**
Netflix アプリの全データ (Library/, Documents/, tmp/) を削除してクリーンな初回起動を
キャプチャした結果、`38b2030d` も `8887ddf1` も出現しなかった。
これらは**前回の appboot で Phase 2 KDF から導出されたセッション鍵が iOS Keychain に
キャッシュされていた**値に過ぎないことが確定。
フレッシュ appboot (初回起動 / キャッシュなし) では:
1. Phase 3 KDF → sign_key_1 で appboot リクエストに署名
2. DH 鍵交換 → Phase 2 KDF → 新セッション鍵を導出
3. 新セッション鍵を Keychain にキャッシュ
4. 以降のリクエストはキャッシュされたセッション鍵で署名
---
## 4. appboot blob の構造 (8549B)
Tweak (`NetflixEntityAuth`) で HMAC 署名対象として 8549B の完全な blob をキャプチャ。
```
[0:20] ESN prefix (ASCII: "NFAPPL-02-IPHONE9=1-")
[20] NULL terminator
[21:28] Header/flags (00 00 01 00 00 00 00)
[28:8210] Encrypted body (8182B — 暗号化された entity_auth + key_exchange)
[8210:8212] Length prefix (73 15)
[8212:8296] Full ESN (ASCII, 84 chars)
[8296:8332] AppID UUID (ASCII, 36 chars)
[8332] AppKeyVersion (ASCII "1")
[8333:8549] DeviceToken protobuf (216B)
```
暗号化本体 (8182B) には entity_auth_data の apphmac、key_exchange の DH 公開鍵、
key 33.6 scheme_data 等が含まれるが、暗号化されているため直接読めない。
保存先: `raws/ios/captures/appboot_blob.bin`
---
## 5. HMAC 署名チェーン (ライブキャプチャ)
appboot 時の HMAC 呼び出し順序と使用鍵:
| 順序 | 鍵 | データ | 用途 |
|------|-----|--------|------|
| 1-6 | PSK / 前段出力 / Nonce | MGK (48B) の各パーツ | Phase 3 KDF (6 段チェーン) |
| 7-9 | sign_key_1 (`d45443fa...`) | 76B / 92B / 76B チャンク | key exchange データ署名 |
| 10 | session sign key (`8887ddf1...`) | 389B CBOR | MSL ヘッダー署名 |
| 11 | **appboot sign key (`38b2030d...`)** | **8549B appboot blob** | **appboot リクエスト署名** |
| 12+ | session sign key (`8887ddf1...`) | CBOR メッセージ各種 | 通常の MSL 通信 |
- Phase 3 KDF (順序 1-6) は Python 実装と完全一致を確認済み
- session sign key (`8887ddf1...`) は Phase 2 KDF (DH 共有秘密) から導出されたもの
- appboot sign key (`38b2030d...`) の導出元が最大の未解明事項
---
## 6. 全値の分類まとめ
### Python 単体で再現可能 (追加調査不要)
| 値 | 固定/可変 | Python 定数/関数 |
|----|----------|-----------------|
| kAppBootKey (RSA-4096) | バイナリ固定 | `constants.IOS_APPBOOT_RSA_KEY_DER` |
| kAppBootEccKey (P-256) | バイナリ固定 | `constants.IOS_APPBOOT_ECC_KEY_DER` |
| kSharkBootKey (P-256) | バイナリ固定 | `constants.IOS_SHARKBOOT_KEY_DER` |
| DH prime p / g | バイナリ固定 | `constants.IOS_DH_P` / `IOS_DH_G` |
| PSK / Nonce | バイナリ固定 | `constants.IOS_KDF_PSK` / `IOS_KDF_NONCE` |
| Device header (128B) | バイナリ固定 | `constants.IOS_KEY336_DEVICE_HEADER` |
| TFIT tables | バイナリ固定 | `tools/emulate_tfit.py` |
| MGK (enc_key_0 + sign_key_0) | ESN 依存・決定的 | `emulate_tfit.py` で導出 |
| Phase 3 KDF 全出力 | MGK 依存・決定的 | `crypto.kdf_renew()` |
| DH 鍵生成/共有秘密 | 毎回ランダム | `crypto.generate_dh_keypair()` |
| Phase 2 KDF 全出力 | DH 依存・決定的 | `crypto.derive_initial_session_keys()` |
| AppID | 固定の可能性高 | 定数として埋め込み |
| AppKeyVersion | 固定の可能性高 | 定数として埋め込み |
### デバイスから1回取得すれば再利用可能
| 値 | 固定/可変 | 取得方法 | 再利用条件 |
|----|----------|---------|-----------|
| Full ESN | デバイス固有・固定 | Tweak/Frida でキャプチャ | 同一デバイスなら永続 |
### 要追加調査 (Python 実装のブロッカー)
| 値 | 固定/可変 | 再利用 | 状態 | 備考 |
|----|----------|--------|------|------|
| **apphmac** (32B) | サーバー発行 | **長期再利用可** | **完全解決** | サーバーが `x-netflix-deviceidtoken` ヘッダで配信。初回は nil、レスポンスから取得。Python 単体で可 |
| **devicetoken** (216B) | サーバー発行 | **長期再利用可** | **完全解決** | deviceIdToken の Base64 デコード。同一の `x-netflix-deviceidtoken` ヘッダから派生。Python 単体で可 |
| ~~**appboot sign key**~~ | — | — | **解決** | Keychain キャッシュ。初回は sign_key_1 で署名 |
> **結論 (2026-04-09 最終):**
>
> **全ての未知の値が解明された:**
>
> - **apphmac (deviceIdToken)** = Netflix サーバーが `x-netflix-deviceidtoken` ヘッダで配信。
> 初回 appboot では nil で送信可能。レスポンスから取得してキャッシュ。
> **Python 単体で取得可能 — Tweak 不要。**
> - **appboot sign key** = 前回セッションの Phase 2 sign key (Keychain キャッシュ)。
> フレッシュ appboot では sign_key_1 で署名。
> - **devicetoken** = DRM/CDM 層に永続化された 216B protobuf。
> Tweak でキャプチャした値をパラメータとして渡す。
>
> **entity_auth_data の CBOR 構造 (完全確定):**
> ```
> key 35: {
> "apphmac": bytes(32B), ← サーバー発行 deviceIdToken (初回 nil 可)
> "appid": "a2becfec-...", ← 固定
> "appkeyversion": 1, ← 固定
> "devicetoken": bytes(216B), ← DRM 層永続トークン
> 3: "NFAPPL-02-IPHONE9=1-..." ← Full ESN
> }
> key 30: "MGK_APPID" ← entity auth scheme 名
> ```
>
>> **Python 実装に必要な値:**
> - **計算可能**: MGK, Phase 3 KDF, DH, Phase 2 KDF, 署名検証鍵 (全てバイナリ定数+ESN)
> - **サーバー取得**: deviceIdToken + devicetoken (同一の `x-netflix-deviceidtoken` ヘッダから派生)
> - **1回キャプチャ**: ESN (デバイス固有、永続)
> - **ESN のみをパラメータとして渡せば、Python で appboot → MSL 認証が実行可能**