mirror of
https://github.com/qtmleap/revkit.git
synced 2026-10-31 13:11:51 +01:00
docs(spec): Phase 2 KDF 解析結果を文書化 — HMAC-SHA384(TFIT_KEY, 0x00||DH_SHARED)
DH 共有秘密から初期セッション鍵を導出するアルゴリズムを解明: - HMAC-SHA384 with 48B TFIT key, 0x00 prefix + 128B shared secret - enc_key = output[0:16], sign_key = output[16:48] - HKDF は NFWebCrypto に存在しないことを確認 - テストベクタで検証済み Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
bed56549d6
commit
dc282686e7
@@ -0,0 +1,74 @@
|
||||
# Work Plan: Phase 2 Initial Key Derivation Analysis
|
||||
|
||||
Date: 2026-04-08
|
||||
|
||||
## Goal
|
||||
|
||||
Analyze how the DH shared secret (128 bytes) is transformed into initial session keys (enc_key_0, sign_key_0) in Netflix iOS MSL protocol, and implement a pure Python simulation that eliminates the need for device-based key capture.
|
||||
|
||||
## Background
|
||||
|
||||
- Phase 3 KDF (key renewal) is solved: uses a non-standard HMAC-SHA256 chain, NOT standard HKDF
|
||||
- 100+ HKDF parameter combinations already tested with no match
|
||||
- Key 33.6 response (96 bytes) suspected structure: [IV:16B][CT:48B][HMAC:32B]
|
||||
- PSK and nonce are known hardcoded constants from NFWebCrypto.framework
|
||||
- The initial derivation likely also uses a non-standard construction
|
||||
|
||||
## Tasks
|
||||
|
||||
### Frida Engineer
|
||||
- [ ] 1. Enumerate all crypto exports in NFWebCrypto.framework (`*HMAC*`, `*SHA*`, `*KDF*`, `*derive*`, `*PRF*`, `*expand*`, `*extract*`, `*HKDF*`) and hook them all during a fresh appboot session
|
||||
- [ ] 2. Hook `AES_set_decrypt_key` / `EVP_DecryptInit` to capture the exact bytes used as decryption key for key 33.6, then trace backwards to identify which function produced those bytes
|
||||
- [ ] 3. Hook CommonCrypto (`CC_SHA256`, `CCHmac`, `CCKeyDerivationPBKDF`) in system libraries to catch KDF calls outside bundled OpenSSL
|
||||
- [ ] 4. Test MSL spec standard KDF: `HMAC-SHA384(pre_master_secret, "MASTER_SECRET" || PSK || nonce)` against live captured values
|
||||
- [ ] 5. Identify and hook C++ orchestrator method in NFWebCrypto containing `appboot`, `session`, `derive`, or `master` in symbol name
|
||||
|
||||
### Tweak Engineer
|
||||
- [ ] 6. Audit existing Tweak hook coverage — identify gaps in intermediate KDF state capture
|
||||
- [ ] 7. Hook `dhDerive` export in NFWebCrypto.framework to capture raw 128-byte DH shared secret
|
||||
- [ ] 8. Hook `HKDF` export in NFWebCrypto.framework with full parameter capture (IKM, salt, info, output length, output bytes)
|
||||
- [ ] 9. Hook `aesCbc` with full context (key, IV, plaintext/ciphertext, direction) to identify wrapping step
|
||||
- [ ] 10. Hook TFIT whitebox AES internals for MGK extraction — bridge between DH shared secret and wrapping key
|
||||
- [ ] 11. Hook `IosMslClient -_handleAppbootResponse:error:timeoutMS:` to extract enc_key_0/sign_key_0 from parsed response object
|
||||
|
||||
### Python Engineer
|
||||
- [ ] 12. Audit existing tools in `tools/` to catalogue exactly which KDF variants have been ruled out
|
||||
- [ ] 13. Build `tools/find_phase2_hmac_chain.py`: test Phase 3 HMAC chain pattern applied to DH shared secret with varying truncation/ordering
|
||||
- [ ] 14. Build `tools/find_phase2_kdf_variants.py`: test non-HKDF derivations (NIST SP 800-56C, ANSI X9.63, PBKDF2, raw SHA-256/SHA-1, PRF constructions)
|
||||
- [ ] 15. Build `tools/decrypt_key_response.py`: attempt AES-CBC decryption of 96-byte key 33.6 blob under candidate keys, verify HMAC
|
||||
- [ ] 16. Implement `derive_initial_session_keys()` in `src/netflix_msl/crypto.py` once algorithm is discovered
|
||||
- [ ] 17. Write regression test `tools/verify_phase2_kdf.py` with known test vectors
|
||||
|
||||
## Execution Order
|
||||
|
||||
1. **Parallel — Runtime Analysis** (Tasks 1-5, 6-11):
|
||||
- Frida Engineer: Deploy comprehensive crypto hook script, capture full appboot session
|
||||
- Tweak Engineer: Deploy enhanced Tweak with HKDF/aesCbc/TFIT hooks, capture parallel data
|
||||
2. **Parallel — Hypothesis Audit** (Task 12):
|
||||
- Python Engineer: Audit existing tool results while device analysis runs
|
||||
3. **Sequential — Correlation** (after Step 1 completes):
|
||||
- Correlate Frida + Tweak logs to identify exact call chain: DH_compute_key → ??? → AES_set_decrypt_key
|
||||
- Determine if HKDF is used at all, or if it's another custom HMAC chain
|
||||
4. **Sequential — Hypothesis Testing** (Tasks 13-15):
|
||||
- Python Engineer: Build and run targeted KDF search tools using parameters revealed by runtime analysis
|
||||
5. **Sequential — Implementation** (Tasks 16-17):
|
||||
- Python Engineer: Implement and verify the discovered algorithm
|
||||
|
||||
## Deliverables
|
||||
|
||||
- `packages/frida/hook_phase2_kdf.js`: Comprehensive crypto tracer for appboot session
|
||||
- `packages/tweak/<name>/Tweak.x.swift`: Enhanced hooks with HKDF/aesCbc/TFIT capture
|
||||
- `tools/find_phase2_hmac_chain.py`: Phase 3 pattern applied to Phase 2
|
||||
- `tools/find_phase2_kdf_variants.py`: Non-HKDF KDF variant tester
|
||||
- `tools/decrypt_key_response.py`: Key 33.6 response decryptor
|
||||
- `src/netflix_msl/crypto.py`: `derive_initial_session_keys()` implementation
|
||||
- `tools/verify_phase2_kdf.py`: Regression test with known test vectors
|
||||
- `docs/spec/msl_phase2_kdf_analysis.md`: Algorithm documentation
|
||||
|
||||
## Risks / Notes
|
||||
|
||||
- The derivation may involve TFIT whitebox crypto (Irdeto), making the key material opaque even to memory inspection — if so, we need to capture at the wrapping boundary, not inside the whitebox
|
||||
- CommonCrypto vs bundled OpenSSL: KDF calls may span both libraries
|
||||
- The 128-byte shared secret may be truncated or hashed before KDF input
|
||||
- If the derivation uses device-specific material beyond PSK/nonce, pure Python simulation may not be fully portable
|
||||
- Phase 3 KDF's non-standard nature strongly suggests Phase 2 is also non-standard — prioritize Netflix-custom HMAC chain hypotheses over standard KDF specs
|
||||
@@ -0,0 +1,111 @@
|
||||
# Phase 2 KDF: DH 共有秘密 → 初期セッション鍵
|
||||
|
||||
## 概要
|
||||
|
||||
Netflix iOS アプリの MSL (Message Security Layer) において、DH 鍵交換で得られた共有秘密から初期セッション鍵 (enc_key, sign_key) を導出するアルゴリズムを解明した。
|
||||
|
||||
## アルゴリズム
|
||||
|
||||
```
|
||||
HMAC-SHA384(TFIT_KEY_48B, 0x00 || DH_SHARED_SECRET_128B) → 48 bytes
|
||||
enc_key = output[0:16] (AES-128 暗号化鍵)
|
||||
sign_key = output[16:48] (HMAC-SHA256 署名鍵)
|
||||
```
|
||||
|
||||
### パラメータ
|
||||
|
||||
| パラメータ | サイズ | 説明 |
|
||||
|-----------|--------|------|
|
||||
| TFIT_KEY | 48 bytes | TFIT ホワイトボックス AES チェーンの出力。セッション毎に異なる |
|
||||
| DH_SHARED_SECRET | 128 bytes | `DH_compute_key()` の出力 (1024-bit DH) |
|
||||
| 0x00 prefix | 1 byte | 固定プレフィックスバイト |
|
||||
|
||||
### 重要な発見
|
||||
|
||||
1. **HKDF は存在しない**: NFWebCrypto.framework には `HKDF`, `HKDF_extract`, `HKDF_expand` のいずれもエクスポートされていない
|
||||
2. **HMAC-SHA384**: Phase 3 KDF (HMAC-SHA256) とは異なるハッシュアルゴリズムを使用
|
||||
3. **TFIT キーはセッション固有**: Irdeto TFIT ホワイトボックス AES チェーン (100+ iterations) によって生成される
|
||||
4. **0x00 プレフィックス**: 共有秘密の前に `0x00` が付加される (data = 129 bytes)
|
||||
|
||||
## 検証済みテストベクタ
|
||||
|
||||
### セッション (2026-04-09)
|
||||
|
||||
```
|
||||
TFIT_KEY = 268ab8d5d6cb36781f4d9b7fdaccd2d6
|
||||
92c5b6af0161e640efad7a3bd4958b42
|
||||
efc7f6ce89f84c0e37bb66794d972819
|
||||
|
||||
DH_SHARED = 6854f0b80187914f2e110cb07fb25e8c
|
||||
65c5a0f591aaf48dec8701128ceefa4f
|
||||
3aa623796400a6f97e0b6c271c2e39d3
|
||||
9432c3c5a82dd1e1301470ae418678b1
|
||||
b4df554027f35bc872c27de42edea18a
|
||||
928541dfec8c9388b788876529c87eec
|
||||
0bc71936013df12d366008005ef4e9f7
|
||||
905520da5170a6e15ae584415fdd175a
|
||||
|
||||
enc_key = d7835418df48f1d54ab54e210cf40fc6
|
||||
sign_key = 794e627118ad213532399d2ecd0c85f9
|
||||
0d6739aca767db24d98ef9360bfd956e
|
||||
```
|
||||
|
||||
## 鍵導出フロー全体
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'dark'}}%%
|
||||
graph TD
|
||||
A["TFIT Whitebox AES Chain<br/>(100+ iterations, AES-256)"] --> B["TFIT_KEY (48B)"]
|
||||
C["DH Key Exchange<br/>(1024-bit)"] --> D["DH_SHARED_SECRET (128B)"]
|
||||
B --> E["HMAC-SHA384(TFIT_KEY, 0x00 || DH_SHARED)"]
|
||||
D --> E
|
||||
E --> F["enc_key_0 (16B)"]
|
||||
E --> G["sign_key_0 (32B)"]
|
||||
F --> H["Phase 3 KDF Renewal<br/>HMAC-SHA256 Chain"]
|
||||
G --> H
|
||||
H --> I["enc_key_1 (16B)"]
|
||||
H --> J["sign_key_1 (32B)"]
|
||||
I --> K["MSL Request Encryption<br/>AES-128-CBC"]
|
||||
J --> L["MSL Request Signing<br/>HMAC-SHA256"]
|
||||
```
|
||||
|
||||
## タイムライン (ログから)
|
||||
|
||||
| 時刻 | イベント | 詳細 |
|
||||
|------|---------|------|
|
||||
| 01:08:57.661 | Phase 3 KDF | 保存済み enc_key_0/sign_key_0 から enc_key_1/sign_key_1 を導出 |
|
||||
| 01:08:57.754 | TFIT KAT | Known Answer Test 開始 (AES-256) |
|
||||
| 01:08:57.978 | appboot リクエスト | enc_key_0 でリクエスト暗号化 |
|
||||
| 01:08:58.539 | TFIT チェーン | 100+ AES-256 ホワイトボックス操作 |
|
||||
| 01:08:59.010 | **DH 鍵交換** | shared_secret(128B) 取得 |
|
||||
| 01:08:59.025 | **Phase 2 KDF** | HMAC-SHA384(TFIT_KEY, 0x00 \|\| shared) → 新 enc_key/sign_key |
|
||||
| 01:08:59.032 | Phase 3 KDF | 新鍵で即座に更新 |
|
||||
| 01:08:59.052 | MSL 通信開始 | 更新済み enc_key_1 で暗号化 |
|
||||
|
||||
## 未解明事項
|
||||
|
||||
### TFIT 48B キーの生成メカニズム
|
||||
|
||||
- TFIT キーは AES-256 ホワイトボックスチェーン (Irdeto) の出力
|
||||
- セッション毎に異なる値を生成
|
||||
- TFIT 内部の入力が何か(乱数?デバイス固有値?)は未特定
|
||||
- 48B キーは `AES_set_*_key` フックでは捕捉不可能(暗号化出力として生成される)
|
||||
- **Tweak の HMAC_Init_ex フックで毎セッション取得可能**
|
||||
|
||||
### 純粋 Python シミュレーションの制限
|
||||
|
||||
現時点では、TFIT 48B キーの取得に以下のいずれかが必要:
|
||||
|
||||
1. **Tweak フック** (推奨): AppbootKDF Tweak が HMAC_Init_ex で 48B キーをキャプチャ
|
||||
2. **Frida フック**: hook_phase2_kdf.js で同等のキャプチャが可能
|
||||
3. **TFIT リバースエンジニアリング** (未達): ホワイトボックス内部の完全な解析
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
| ファイル | 説明 |
|
||||
|---------|------|
|
||||
| `src/netflix_msl/crypto.py` | `derive_initial_session_keys()` 実装 |
|
||||
| `tools/verify_phase2_kdf.py` | 回帰テスト |
|
||||
| `packages/tweak/AppbootKDF/` | TFIT キーキャプチャ Tweak |
|
||||
| `packages/frida/hook_phase2_kdf.js` | 包括的暗号トレーサー |
|
||||
| `raws/appboot_kdf_fresh.log` | 検証用ログ |
|
||||
@@ -59,6 +59,7 @@ var fnNames = [
|
||||
"DH_generate_key", "DH_compute_key", "DH_new", "DH_free",
|
||||
"DH_get0_key", "DH_get0_pqg", "DH_set0_pqg", "DH_set0_key",
|
||||
"BN_new", "BN_free", "BN_bin2bn", "BN_bn2bin", "BN_num_bits", "BN_dup",
|
||||
"BN_mod_exp", "BN_CTX_new", "BN_CTX_free",
|
||||
"SHA256", "SHA384",
|
||||
"AES_set_decrypt_key", "AES_cbc_encrypt",
|
||||
"HMAC",
|
||||
@@ -228,6 +229,78 @@ if (fn.DH_compute_key) {
|
||||
console.log("[+] DH_compute_key hooked (observer)");
|
||||
}
|
||||
|
||||
// ===== Hook BN_mod_exp to catch manual DH computation =====
|
||||
// DH_compute_key internally does: shared_secret = server_pub ^ priv mod p
|
||||
// Netflix may call BN_mod_exp directly instead of DH_compute_key
|
||||
|
||||
var g_dhGenerateTime = 0; // timestamp when DH_generate_key fired
|
||||
|
||||
if (fn.BN_mod_exp) {
|
||||
// BN_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p_exp, const BIGNUM *m, BN_CTX *ctx)
|
||||
Interceptor.attach(fn.BN_mod_exp, {
|
||||
onEnter: function (args) {
|
||||
this.r = args[0];
|
||||
this.a = args[1]; // base
|
||||
this.p_exp = args[2]; // exponent
|
||||
this.m = args[3]; // modulus
|
||||
|
||||
// Only log if BN sizes suggest DH (1024-bit modulus)
|
||||
if (BN_num_bits) {
|
||||
var mBits = BN_num_bits(this.m);
|
||||
var aBits = BN_num_bits(this.a);
|
||||
var expBits = BN_num_bits(this.p_exp);
|
||||
|
||||
// DH: base^exp mod p where p is 1024-bit
|
||||
if (mBits >= 1020 && mBits <= 1030) {
|
||||
this.isDH = true;
|
||||
console.log("\n[BN_mod_exp] DH-sized operation detected!");
|
||||
console.log(" base: " + aBits + " bits");
|
||||
console.log(" exp: " + expBits + " bits");
|
||||
console.log(" mod: " + mBits + " bits");
|
||||
|
||||
// Dump base (first 32 bytes)
|
||||
if (BN_bn2bin) {
|
||||
var aBytes = (aBits + 7) >> 3;
|
||||
var aBuf = Memory.alloc(aBytes);
|
||||
BN_bn2bin(this.a, aBuf);
|
||||
console.log(" base hex: " + hex(aBuf, Math.min(aBytes, 32)) + (aBytes > 32 ? "..." : ""));
|
||||
|
||||
var expBytes = (expBits + 7) >> 3;
|
||||
var expBuf = Memory.alloc(expBytes);
|
||||
BN_bn2bin(this.p_exp, expBuf);
|
||||
console.log(" exp hex: " + hex(expBuf, Math.min(expBytes, 32)) + (expBytes > 32 ? "..." : ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onLeave: function (retval) {
|
||||
if (this.isDH && retval.toInt32() === 1 && BN_num_bits && BN_bn2bin) {
|
||||
var rBits = BN_num_bits(this.r);
|
||||
var rBytes = (rBits + 7) >> 3;
|
||||
var rBuf = Memory.alloc(rBytes);
|
||||
BN_bn2bin(this.r, rBuf);
|
||||
var resultHex = hex(rBuf, rBytes);
|
||||
console.log("[BN_mod_exp] result (" + rBytes + "B) = " + resultHex.substring(0, 64) + (rBytes > 32 ? "..." : ""));
|
||||
|
||||
// Store as potential DH shared secret
|
||||
g_dhSharedSecret = resultHex;
|
||||
console.log("[*] Stored as potential DH shared secret");
|
||||
|
||||
// Print backtrace to identify caller
|
||||
console.log(" backtrace:");
|
||||
var bt = Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0, 6);
|
||||
bt.forEach(function (addr) {
|
||||
var sym = DebugSymbol.fromAddress(addr);
|
||||
console.log(" " + addr + " " + sym);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("[+] BN_mod_exp hooked (DH-sized filter)");
|
||||
}
|
||||
|
||||
var g_dhSharedSecret = null; // hex string from BN_mod_exp
|
||||
|
||||
// ===== RPC functions callable from Frida console =====
|
||||
|
||||
// Set server's DH public key (from appboot response)
|
||||
@@ -308,6 +381,65 @@ function hmacSha256(keyBytes, dataBytes) {
|
||||
return new Uint8Array(outBuf.readByteArray(32));
|
||||
}
|
||||
|
||||
// Helper: compute DH shared secret manually via BN_mod_exp
|
||||
// shared_secret = server_pub ^ priv mod p
|
||||
function computeViaBnModExp() {
|
||||
if (!g_dhPrivKey || !g_serverPubKey || !g_dhP || !fn.BN_mod_exp) {
|
||||
console.log("[-] Missing data for BN_mod_exp (need priv, server_pub, p)");
|
||||
return null;
|
||||
}
|
||||
|
||||
var BN_mod_exp_fn = new NativeFunction(fn.BN_mod_exp,
|
||||
'int', ['pointer', 'pointer', 'pointer', 'pointer', 'pointer']);
|
||||
var BN_CTX_new_fn = fn.BN_CTX_new ? new NativeFunction(fn.BN_CTX_new,
|
||||
'pointer', []) : null;
|
||||
var BN_CTX_free_fn = fn.BN_CTX_free ? new NativeFunction(fn.BN_CTX_free,
|
||||
'void', ['pointer']) : null;
|
||||
var BN_new_fn = fn.BN_new ? new NativeFunction(fn.BN_new, 'pointer', []) : null;
|
||||
|
||||
// Create BIGNUMs
|
||||
var privBytes = hexToBytes(g_dhPrivKey);
|
||||
var privBuf = Memory.alloc(privBytes.length);
|
||||
privBuf.writeByteArray(privBytes.buffer);
|
||||
var privBN = BN_bin2bn(privBuf, privBytes.length, ptr(0));
|
||||
|
||||
var pubBytes = hexToBytes(g_serverPubKey);
|
||||
var pubBuf = Memory.alloc(pubBytes.length);
|
||||
pubBuf.writeByteArray(pubBytes.buffer);
|
||||
var pubBN = BN_bin2bn(pubBuf, pubBytes.length, ptr(0));
|
||||
|
||||
var pBytes = hexToBytes(g_dhP);
|
||||
var pBuf = Memory.alloc(pBytes.length);
|
||||
pBuf.writeByteArray(pBytes.buffer);
|
||||
var pBN = BN_bin2bn(pBuf, pBytes.length, ptr(0));
|
||||
|
||||
var rBN = BN_new_fn ? BN_new_fn() : BN_bin2bn(ptr(0), 0, ptr(0));
|
||||
var ctx = BN_CTX_new_fn ? BN_CTX_new_fn() : ptr(0);
|
||||
|
||||
console.log("[BN_mod_exp] Computing server_pub ^ priv mod p ...");
|
||||
var ret = BN_mod_exp_fn(rBN, pubBN, privBN, pBN, ctx);
|
||||
|
||||
if (ret !== 1) {
|
||||
console.log("[-] BN_mod_exp failed");
|
||||
BN_free(privBN); BN_free(pubBN); BN_free(pBN); BN_free(rBN);
|
||||
if (BN_CTX_free_fn && !ctx.isNull()) BN_CTX_free_fn(ctx);
|
||||
return null;
|
||||
}
|
||||
|
||||
var rBits = BN_num_bits(rBN);
|
||||
var rLen = (rBits + 7) >> 3;
|
||||
var rBuf = Memory.alloc(rLen);
|
||||
BN_bn2bin(rBN, rBuf);
|
||||
var result = new Uint8Array(rBuf.readByteArray(rLen));
|
||||
|
||||
console.log("[+] BN_mod_exp result (" + rLen + "B) = " + bytesToHex(result).substring(0, 64) + "...");
|
||||
|
||||
BN_free(privBN); BN_free(pubBN); BN_free(pBN); BN_free(rBN);
|
||||
if (BN_CTX_free_fn && !ctx.isNull()) BN_CTX_free_fn(ctx);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Main: compute shared secret and try to decrypt key 33.6
|
||||
rpc.exports.computeAndDecrypt = function () {
|
||||
return computeAndDecryptImpl();
|
||||
@@ -316,61 +448,82 @@ rpc.exports.computeAndDecrypt = function () {
|
||||
function computeAndDecryptImpl() {
|
||||
console.log("\n========== computeAndDecrypt ==========");
|
||||
|
||||
if (!g_dhHandle) {
|
||||
console.log("[-] No DH handle. Wait for DH_generate_key to fire.");
|
||||
return;
|
||||
var sharedSecret = null;
|
||||
var sharedHex = null;
|
||||
|
||||
// --- Strategy 1: Use BN_mod_exp captured shared secret ---
|
||||
if (g_dhSharedSecret) {
|
||||
console.log("[*] Using BN_mod_exp captured shared secret");
|
||||
sharedSecret = hexToBytes(g_dhSharedSecret);
|
||||
sharedHex = g_dhSharedSecret;
|
||||
console.log("[+] shared_secret (" + sharedSecret.length + "B) = " + sharedHex.substring(0, 64) + "...");
|
||||
}
|
||||
if (!g_serverPubKey) {
|
||||
console.log("[-] No server pub key. Call setServerPubKey(hex)");
|
||||
// --- Strategy 2: Compute via DH_compute_key ---
|
||||
else if (g_dhHandle && g_serverPubKey && DH_compute_key) {
|
||||
console.log("[*] Computing via DH_compute_key");
|
||||
|
||||
var serverPubBytes = hexToBytes(g_serverPubKey);
|
||||
var serverPubBuf = Memory.alloc(serverPubBytes.length);
|
||||
serverPubBuf.writeByteArray(serverPubBytes.buffer);
|
||||
|
||||
var serverPubBN = BN_bin2bn(serverPubBuf, serverPubBytes.length, ptr(0));
|
||||
if (serverPubBN.isNull()) {
|
||||
console.log("[-] BN_bin2bn failed for server pub key");
|
||||
return;
|
||||
}
|
||||
|
||||
var outBuf = Memory.alloc(256);
|
||||
var sharedLen = DH_compute_key(outBuf, serverPubBN, g_dhHandle);
|
||||
BN_free(serverPubBN);
|
||||
|
||||
if (sharedLen <= 0) {
|
||||
console.log("[-] DH_compute_key failed: " + sharedLen);
|
||||
console.log("[*] Trying BN_mod_exp fallback...");
|
||||
sharedSecret = computeViaBnModExp();
|
||||
if (!sharedSecret) return;
|
||||
sharedHex = bytesToHex(sharedSecret);
|
||||
} else {
|
||||
sharedSecret = new Uint8Array(outBuf.readByteArray(sharedLen));
|
||||
sharedHex = bytesToHex(sharedSecret);
|
||||
}
|
||||
}
|
||||
// --- Strategy 3: Manual BN_mod_exp ---
|
||||
else if (g_dhPrivKey && g_serverPubKey && g_dhP && fn.BN_mod_exp) {
|
||||
console.log("[*] Computing via manual BN_mod_exp");
|
||||
sharedSecret = computeViaBnModExp();
|
||||
if (!sharedSecret) return;
|
||||
sharedHex = bytesToHex(sharedSecret);
|
||||
} else {
|
||||
console.log("[-] Need either: BN_mod_exp captured data, or DH handle + server pub key, or priv+pub+p for manual BN_mod_exp");
|
||||
console.log(" DH handle: " + (g_dhHandle ? "yes" : "no"));
|
||||
console.log(" Server pub: " + (g_serverPubKey ? "yes" : "no"));
|
||||
console.log(" Priv key: " + (g_dhPrivKey ? "yes" : "no"));
|
||||
console.log(" DH p: " + (g_dhP ? "yes" : "no"));
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Step 1: Convert server pub key hex to BIGNUM ---
|
||||
var serverPubBytes = hexToBytes(g_serverPubKey);
|
||||
var serverPubBuf = Memory.alloc(serverPubBytes.length);
|
||||
serverPubBuf.writeByteArray(serverPubBytes.buffer);
|
||||
|
||||
var serverPubBN = BN_bin2bn(serverPubBuf, serverPubBytes.length, ptr(0));
|
||||
if (serverPubBN.isNull()) {
|
||||
console.log("[-] BN_bin2bn failed for server pub key");
|
||||
return;
|
||||
}
|
||||
console.log("[+] Server pub key BIGNUM created (" + serverPubBytes.length + " bytes)");
|
||||
|
||||
// --- Step 2: Call DH_compute_key ---
|
||||
var outBuf = Memory.alloc(256);
|
||||
var sharedLen = DH_compute_key(outBuf, serverPubBN, g_dhHandle);
|
||||
BN_free(serverPubBN);
|
||||
|
||||
if (sharedLen <= 0) {
|
||||
console.log("[-] DH_compute_key failed: " + sharedLen);
|
||||
return;
|
||||
}
|
||||
|
||||
var sharedSecret = new Uint8Array(outBuf.readByteArray(sharedLen));
|
||||
var sharedHex = bytesToHex(sharedSecret);
|
||||
console.log("[+] DH shared_secret (" + sharedLen + "B) = " + sharedHex);
|
||||
console.log("[+] DH shared_secret (" + sharedSecret.length + "B) = " + sharedHex.substring(0, 64) + "...");
|
||||
|
||||
// --- Step 3: Derive candidate keys from shared_secret ---
|
||||
console.log("\n--- Candidate key derivation ---");
|
||||
|
||||
// 3a. SHA-384(shared_secret)
|
||||
var sha384Out = Memory.alloc(48);
|
||||
var sha384In = Memory.alloc(sharedLen);
|
||||
var sha384In = Memory.alloc(sharedSecret.length);
|
||||
sha384In.writeByteArray(sharedSecret.buffer);
|
||||
SHA384(sha384In, sharedLen, sha384Out);
|
||||
SHA384(sha384In, sharedSecret.length, sha384Out);
|
||||
var sha384 = new Uint8Array(sha384Out.readByteArray(48));
|
||||
console.log("[SHA384] " + bytesToHex(sha384));
|
||||
console.log(" enc_candidate = " + bytesToHex(sha384.slice(0, 16)));
|
||||
console.log(" sign_candidate = " + bytesToHex(sha384.slice(16, 48)));
|
||||
|
||||
// 3b. SHA-384(0x00 || shared_secret) — MSL Java reference style
|
||||
var padded1 = new Uint8Array(1 + sharedLen);
|
||||
var padded1 = new Uint8Array(1 + sharedSecret.length);
|
||||
padded1[0] = 0x00;
|
||||
padded1.set(sharedSecret, 1);
|
||||
var padded1Buf = Memory.alloc(padded1.length);
|
||||
padded1Buf.writeByteArray(padded1.buffer);
|
||||
SHA384(padded1Buf, padded1.length, sha384Out);
|
||||
SHA384(padded1Buf, 1 + sharedSecret.length, sha384Out);
|
||||
var sha384null = new Uint8Array(sha384Out.readByteArray(48));
|
||||
console.log("[SHA384(0x00||ss)] " + bytesToHex(sha384null));
|
||||
console.log(" enc_candidate = " + bytesToHex(sha384null.slice(0, 16)));
|
||||
@@ -378,7 +531,7 @@ function computeAndDecryptImpl() {
|
||||
|
||||
// 3c. SHA-256(shared_secret)
|
||||
var sha256Out = Memory.alloc(32);
|
||||
SHA256(sha384In, sharedLen, sha256Out);
|
||||
SHA256(sha384In, sharedSecret.length, sha256Out);
|
||||
var sha256 = new Uint8Array(sha256Out.readByteArray(32));
|
||||
console.log("[SHA256] " + bytesToHex(sha256));
|
||||
console.log(" enc_candidate = " + bytesToHex(sha256.slice(0, 16)));
|
||||
@@ -502,6 +655,7 @@ global.getState = function () {
|
||||
console.log("Client pub: " + (g_dhPubKey ? g_dhPubKey.substring(0, 32) + "..." : "null"));
|
||||
console.log("Client priv: " + (g_dhPrivKey ? g_dhPrivKey.substring(0, 32) + "..." : "null"));
|
||||
console.log("Server pub: " + (g_serverPubKey ? g_serverPubKey.substring(0, 32) + "..." : "null"));
|
||||
console.log("BN_mod_exp captured: " + (g_dhSharedSecret ? g_dhSharedSecret.substring(0, 32) + "..." : "null"));
|
||||
console.log("key 33.6: " + (g_key336 ? g_key336.substring(0, 32) + "... (" + (g_key336.length / 2) + "B)" : "null"));
|
||||
console.log("key 33.9: " + (g_key339 || "null"));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user