Files
tkgstratorandClaude Opus 4.6 1b3e4d1fe2 feat(tools): MSLクライアント実装と解析ツールを追加
- src/netflix_msl/: Python MSLクライアント (鍵交換, 暗号化/復号, マニフェスト取得)
- tools/run.py: Fridaフックランナー (@@LOG@@パース, JSONL保存, crypto表示改善)
- tools/find_appboot.js: appboot関連シンボル検索
- tools/check_tweak.js: Tweakロード状態確認
- tools/dump_rsa_key.py, verify_key.py 等の解析ユーティリティ

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

126 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""Android Frida ログ → Chrome extension 形式変換.
Usage:
python -m tools.transformers.android raws/android/20260404/capture.jsonl
python -m tools.transformers.android raws/android/20260404/capture.jsonl -o logs/android
"""
import argparse
import sys
from pathlib import Path
from tools.transformers.base import (
BaseTransformer,
load_entries,
print_summary,
write_output,
)
class AndroidTransformer(BaseTransformer):
"""Android Frida ログ用のイベントハンドラマッピング.
Android 固有のイベント (Java MSL / Cronet / ALE BouncyCastle 等) を
Chrome extension 形式にマッピングする。
"""
def handle_esn_android(self, e: dict):
"""Android ESN は PRV / PXA を区別する."""
esn = e.get("esn", "")
if not esn:
return
if "PRV" in esn.upper():
self.esn["prv"] = esn
elif "PXA" in esn.upper():
self.esn["pxa"] = esn
else:
self.esn["prv"] = esn
self.esn["capturedAt"] = e.get("ts", "")
_handlers: dict = {
# MSL
"msl.message": BaseTransformer.handle_msl_message,
"msl.api": BaseTransformer.handle_msl_api,
"msl.api.response": BaseTransformer.handle_msl_api_response,
"appboot.response": BaseTransformer.handle_msl_api_response,
# MSL response payload (MessageInputStream / PayloadChunk)
"msl.response.payload": BaseTransformer.handle_msl_response_payload,
"msl.payload": BaseTransformer.handle_msl_response_payload,
# HTTP
"http.request": BaseTransformer.handle_http_request,
"http.response": BaseTransformer.handle_http_response,
# Crypto (Java MSL AesCbcEncryptor)
"msl.aesCbcEncrypt": BaseTransformer.handle_aes_cbc_encrypt_combined,
"msl.aesCbcDecrypt": BaseTransformer.handle_aes_cbc_decrypt,
"msl.hmacSha256": BaseTransformer.handle_hmac,
"msl.hmacSha256.sign": BaseTransformer.handle_hmac,
"msl.aesKwUnwrap": BaseTransformer.handle_aes_kw_unwrap,
"msl.dhSharedSecret": BaseTransformer.handle_dh_shared_secret,
"msl.rsaEncrypt": BaseTransformer.handle_rsa,
"msl.rsaDecrypt": BaseTransformer.handle_rsa,
# Crypto (WidevineCryptoContext / SymmetricCryptoContext)
"msl.widevine.encrypt": BaseTransformer.handle_noop,
"msl.widevine.decrypt": BaseTransformer.handle_aes_cbc_decrypt, # CBOR チャンク蓄積 (iOS と同じ)
"msl.widevine.sign": BaseTransformer.handle_noop,
"msl.widevine.verify": BaseTransformer.handle_noop,
"msl.symmetric.encrypt": BaseTransformer.handle_noop,
"msl.symmetric.decrypt": BaseTransformer.handle_noop,
"msl.symmetric.sign": BaseTransformer.handle_noop,
"msl.symmetric.verify": BaseTransformer.handle_noop,
# Crypto (JWE)
"msl.jwe.wrap": BaseTransformer.handle_noop,
"msl.jwe.unwrap": BaseTransformer.handle_noop,
# DRM
"drm.keyRequest": BaseTransformer.handle_noop,
"drm.keyResponse": BaseTransformer.handle_noop,
"drm.openSession": BaseTransformer.handle_noop,
"drm.property": BaseTransformer.handle_noop,
"drm.propertyString": BaseTransformer.handle_noop,
# ALE (BouncyCastle)
"ale.keys": BaseTransformer.handle_ale_keys,
# Keys / ESN / Manifest
"esn.detected": handle_esn_android,
"manifest": BaseTransformer.handle_manifest,
# Auth
"msl.userauthdata": BaseTransformer.handle_noop,
# Storage
"storage.sharedPreferences": BaseTransformer.handle_storage_shared_prefs,
"storage.file": BaseTransformer.handle_storage_file,
"storage.appFiles": BaseTransformer.handle_storage_sandbox,
# Skip
"manifest.kidTable": BaseTransformer.handle_noop,
"msl.encrypt.input": BaseTransformer.handle_noop,
"msl.decrypt.output": BaseTransformer.handle_noop,
"url": BaseTransformer.handle_noop,
}
def main():
p = argparse.ArgumentParser(description="Android → Chrome log transformer")
p.add_argument("input", help="Android capture.jsonl path")
p.add_argument("-o", "--output", help="Output directory (default: logs/android)")
args = p.parse_args()
input_path = Path(args.input)
if not input_path.exists():
print(f"[-] File not found: {input_path}", file=sys.stderr)
sys.exit(1)
out_dir = Path(args.output) if args.output else Path("logs") / "android"
print(f"[*] Input: {input_path}")
print(f"[*] Output: {out_dir}")
entries = load_entries(input_path)
print(f"[*] Loaded {len(entries)} entries")
t = AndroidTransformer()
t.transform(entries)
write_output(t, out_dir)
print_summary(t, "Android")
if __name__ == "__main__":
main()