mirror of
https://github.com/qtmleap/revkit.git
synced 2026-10-03 16:31:51 +02:00
chore: prepare for commit message generation without code changes
This commit is contained in:
+196
@@ -0,0 +1,196 @@
|
||||
# PXA ESN (Proxy ESN) — 取得フロー
|
||||
|
||||
> **対象:** Netflix Android v9.57.0 (build 63928)
|
||||
> **取得方法:** Frida フック (`hook_msl.js` → `ProxyEsnMslRequest` / `ProxyEsn`)
|
||||
> **検証日:** 2026-03-14
|
||||
|
||||
---
|
||||
|
||||
## 概要
|
||||
|
||||
PXA ESN はサーバーが発行するデバイス識別子。ローカル生成の Base ESN (PRV) と異なり、サーバー側で fingerprint を付与して返す。MSL 通信や GraphQL API で `X-Netflix.esn` / `X-Netflix-ProxyEsn` ヘッダーとして使用される。
|
||||
|
||||
---
|
||||
|
||||
## 取得フロー
|
||||
|
||||
### 1. トリガー
|
||||
|
||||
`WidevineEntityAuthEsnProviderImpl.c(Long serialNumber)` が呼ばれたとき、以下の条件で `getProxyEsn` が発行される:
|
||||
|
||||
```java
|
||||
// WidevineEntityAuthEsnProviderImpl.c()
|
||||
if (serialNumber == null) return true; // serialNumber なし → 再取得
|
||||
if (proxyEsn.c != serialNumber) return true; // serialNumber 不一致 → 再取得
|
||||
return proxyEsn.d; // expired フラグ → true なら再取得
|
||||
```
|
||||
|
||||
### 2. キャッシュ有効期限
|
||||
|
||||
`ProxyEsn` コンストラクタで有効期限を判定:
|
||||
|
||||
```java
|
||||
// ProxyEsn.$init()
|
||||
long ttl = this.f; // EsnHendrixConfig.refreshProxyEsnTimeInMs
|
||||
if (ttl < 1) {
|
||||
this.d = false; // expired = false → 無期限キャッシュ
|
||||
return;
|
||||
}
|
||||
boolean expired = C20986jOb.c(ttl, this.g); // TTL ベースの期限判定
|
||||
this.d = expired;
|
||||
```
|
||||
|
||||
**実測値: `refreshProxyEsnTimeInMs = 0`**
|
||||
|
||||
TTL が 0 のため、**PXA ESN は一度取得したら無期限にキャッシュされる**。`getProxyEsn` が再発行されるのは以下のケースのみ:
|
||||
|
||||
- 初回インストール時
|
||||
- アプリデータクリア時
|
||||
- SharedPreferences が消えたとき
|
||||
- `masterTokenSerialNumber` が変わったとき
|
||||
|
||||
### 3. MSL リクエスト
|
||||
|
||||
`ProxyEsnMslRequest` が MSL 経由で `/getProxyEsn` にリクエストを送信する。
|
||||
|
||||
**リクエストボディ:**
|
||||
|
||||
```json
|
||||
{"url": "/getProxyEsn"}
|
||||
```
|
||||
|
||||
**リクエストヘッダー:**
|
||||
|
||||
MSL 層で暗号化されるため、HTTP レベルでのヘッダーは空。MSL VolleyRequest のヘッダーとして `router: getProxyEsn` が設定される。
|
||||
|
||||
```java
|
||||
// ProxyEsnMslRequest.getHeaders()
|
||||
C20979jNv.e(d, "router", "getProxyEsn", true, true);
|
||||
d.remove("x-netflix.nq-shadow.id");
|
||||
d.remove("x-netflix.nq-shadow");
|
||||
```
|
||||
|
||||
### 4. MSL レスポンス
|
||||
|
||||
サーバー (`playapi`) が PXA ESN を JSON で返す。
|
||||
|
||||
**レスポンス:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"version": 2,
|
||||
"serverTime": 1773478742949,
|
||||
"result": {
|
||||
"esn": "NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-02028KVLM5OU1MSBUS4RUV6011TPPJVL5CS65GVMMH6PD5I6TSR8TMAC49OTSG3I4BJ3P78GD7ECQ6CVIIFHPN52CC20RJ2CPEE0A3FF"
|
||||
},
|
||||
"common": {},
|
||||
"from": "playapi"
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 説明 |
|
||||
|---|---|
|
||||
| `id` | リクエスト ID |
|
||||
| `version` | プロトコルバージョン |
|
||||
| `serverTime` | サーバー時刻 (epoch ms) |
|
||||
| `result.esn` | PXA ESN 本体 |
|
||||
| `from` | 発行元サーバー (`playapi`) |
|
||||
|
||||
- Cookie ではない。MSL レスポンスの JSON ボディで返される。
|
||||
- fingerprint 部分はサーバー側で生成されるため、ローカルでは再現不可。
|
||||
- **起動ごとに異なる fingerprint が返される** (同一デバイスでも毎回異なる)。
|
||||
|
||||
### 5. 保存
|
||||
|
||||
`ProxyEsn.onKnown(Long serialNumber, String esn)` で SharedPreferences に永続化。
|
||||
|
||||
```java
|
||||
// ProxyEsn.onKnown()
|
||||
this.b = esn;
|
||||
jNK.b(this.e, "nf_drm_esn", esn); // PXA ESN 文字列
|
||||
this.g = System.currentTimeMillis();
|
||||
this.c = serialNumber;
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("esn", this.b);
|
||||
json.put("ts", this.g); // 取得時刻
|
||||
json.put("sn", this.c); // masterTokenSerialNumber
|
||||
jNK.b(this.e, "nf_drm_proxy_esn", json.toString());
|
||||
```
|
||||
|
||||
**SharedPreferences キー:**
|
||||
|
||||
| キー | 値 | 例 |
|
||||
|---|---|---|
|
||||
| `nf_drm_esn` | PXA ESN 文字列 | `NFANDROID1-PXA-P-L3-GOOGLPIXEL=...` |
|
||||
| `nf_drm_proxy_esn` | JSON メタデータ | `{"esn":"...","ts":1773478742949,"sn":8572399748193023}` |
|
||||
|
||||
### 6. 使用
|
||||
|
||||
取得後、以下のヘッダーで API リクエストに付与される:
|
||||
|
||||
| ヘッダー | 用途 |
|
||||
|---|---|
|
||||
| `X-Netflix.esn` | GraphQL / Cronet リクエスト |
|
||||
| `X-Netflix-ProxyEsn` | WebSocket / MSL リクエスト |
|
||||
|
||||
---
|
||||
|
||||
## PXA ESN 構造
|
||||
|
||||
```
|
||||
NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-02028KVLM5OU1MSB...
|
||||
│ │ │ │ │ │ └─ サーバー発行 fingerprint (毎回異なる)
|
||||
│ │ │ │ │ └─ Widevine systemId
|
||||
│ │ │ │ └─ sanitized model (Base ESN と同じ)
|
||||
│ │ │ └─ Security Level (L3)
|
||||
│ │ └─ Device Category (P=Phone)
|
||||
│ └─ Type: PXA = Proxy (サーバー発行)
|
||||
└─ Platform prefix
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## タイムライン (実測)
|
||||
|
||||
```
|
||||
08:41:58.240 ProxyEsn.$init() expired=true (キャッシュなし or 期限切れ)
|
||||
08:41:58.240 getHeaders() router=getProxyEsn
|
||||
08:41:58.248 getBodyForNq() {"url":"/getProxyEsn"}
|
||||
── MSL リクエスト送信 ──
|
||||
08:41:59.567 onSuccess() レスポンス受信 (約1.3秒後)
|
||||
08:41:59.569 ProxyEsn.onKnown() SharedPreferences に保存 (2ms後)
|
||||
── 以後のAPIリクエストで PXA ESN を使用 ──
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 関連クラス
|
||||
|
||||
| クラス | 役割 |
|
||||
|---|---|
|
||||
| `ProxyEsnMslRequest` | `/getProxyEsn` MSL リクエスト発行 |
|
||||
| `ProxyEsn` | PXA ESN のキャッシュ管理・永続化 |
|
||||
| `WidevineEntityAuthEsnProviderImpl` | ESN プロバイダー (PRV/PXA 統合管理) |
|
||||
| `EsnHendrixConfig` (`o.fkQ`) | `refreshProxyEsnTimeInMs` 設定値 |
|
||||
| `InterfaceC13214fkS` | `onKnown` コールバック |
|
||||
|
||||
---
|
||||
|
||||
## キャプチャ方法
|
||||
|
||||
`hook_msl.js` で以下のフックが有効:
|
||||
|
||||
| フック | イベント名 | 内容 |
|
||||
|---|---|---|
|
||||
| `ProxyEsn.$init` | `proxyEsn.forceExpired` | expired を強制 true にして再取得を発火 |
|
||||
| `ProxyEsnMslRequest.getBodyForNq` | `proxyEsn.request` | リクエストボディ |
|
||||
| `ProxyEsnMslRequest.getHeaders` | `proxyEsn.requestHeaders` | リクエストヘッダー |
|
||||
| `ProxyEsnMslRequest.onSuccess` | `proxyEsn.response` | レスポンス JSON |
|
||||
| `ProxyEsnMslRequest.e` | `proxyEsn.error` | エラー |
|
||||
| `ProxyEsn.onKnown` | `proxyEsn.onKnown` | 保存される ESN と serialNumber |
|
||||
|
||||
```bash
|
||||
./run_android.sh hook_msl.js
|
||||
```
|
||||
Reference in New Issue
Block a user