ksjsb/docs/capture_login_chain.md
2026-07-30 20:25:56 +08:00

145 lines
9.8 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.

# capture/ 登录链路 + APP 初始化 实测分析
> 来源:`capture/` Reqable body-only 抓包导出1738 文件)
> 时间2026-07-23 06:32 起session B全新装机 + 登录)
> 目标设备:`did=ANDROID_f05497e9cef09a7f`(与 `.env` 账号设备 `e8dfd2f16b618053` 不同 = 全新装)
## 0. 抓包格式与固有限制
- 文件命名:`{ts_ms}-{flowId}-{part}-{type}.reqable``flowId`/`ts` 决定时序。
- 类型:`req_raw-body`(请求体,常为加密 `{"data":"..."}` 或明文 form、`res-raw-body`原始响应chunked/gzip 二进制)、`res-extract-body`**已解码响应 JSON主要可读源**)、`req-extract-body`(解码请求体)。
- **无 URL/header/method 文件**。端点只能从 body 内容 + 响应推断。
- 混入少量酷狗(`com.kugou.android`flow 466/467噪声主体是快手 nebula。
- 请求体多为 kwsg 加密;关键字 grep`mobile`/`smsCode`命中的多是日志上报flow 577 几百段=批量 log非登录。
## 1. APP 初始化序列session B06:32 起,已登录前 uid=0
| flow | 时间 | 端点(推断) | 关键字段 |
|------|------|------|------|
| 13 | 06:32 | 冷启动首批请求 | — |
| 171 | 06:48 | 配置拉取 | `public_param{uid:0,did,kpn:NEBULA,kpf,app_version}` + `request_info[{config_name:config}]` |
| 189 | ~06:xx | **DFP 设备注册** | `productName=NEBULA&ts=...&deviceInfo=<urlenc>`= `gdfp_report`/`unified_fetch`,已还原于 `core/dfp_forms.py` |
| 272/274 | ~08:44 | 域名-IP 路由表 | `domains[]{domain,iplist{ipv4,ipv6}}`(含 `api.e.kuaishou.com`、`id.kuaishou.com` 等,**非登录** |
> DFP bootstrap设备 did+egid 在线注册)在登录前完成,与 `tools/new_device.py` 已还原链路一致。✓
## 2. 登录链路(核心发现)
### 登录方式 = 运营商一键登录provider-token非手机号短信
**flow 22108:18登录提交请求体明文 + 已还原签名):**
```
provider=11
&provider_token=CgZORUJVTEESGEFORFJPSURfZjA1NDk3ZTljZWYwOWE3ZhjdzY/JgMgdIKy0l/v4Mw==
&session_id=eab9a270-1d55-45a2-97dd-f4f32f9c3eb8
&cs=false&os=android&client_key=2ac2a76d
&sig=6d37a2436f910601ef23cd2b6fd212f2
&__NS_sig3=544530168b10f35b1f1c1f1ecddd17765822656c010d0315
&__NS_xfalcon=HUDR_sFnX+...==$TE_253aa3c5c5193634544b6e193634544b...
```
`provider_token` base64 解码为 protobuf
```
field1(string) = "NEBULA" # kpn
field2(string) = "ANDROID_f05497e9cef09a7f" # did
field3(varint) = 0xddcd8fc980c81d # 时间戳类
field4(varint) = 0xacb497fbf833 # nonce/凭证
```
`{kpn, did, ts, nonce}`——由**运营商一键登录 SDK**(如移动认证/极光一键登录)签发,绑定设备 did。
- `provider=11` = 认证 provider 编码(一键登录)。
- 请求签名 `sig/__NS_sig3/__NS_xfalcon` 全部用**已还原算法**可复算。✓
### 登录响应
- **flow 221 响应**`{"result":1,"bind_interval_ms":604800000}` → 登录成功,设备绑定有效期 7 天604800000 ms
- **flow 22208:19疑似 getLoginUser/refreshToken 后续)响应**
```json
{"dataRsp":"<576B base64>","result":1,"error_msg":""}
```
`result=1` = 成功。会话票据(`api_st`/`h5_st`/`user_id`/`client_salt`)封装在 `dataRsp` 加密块内。
## 3. 会话票据加密层(关键)
- `dataRsp`576B`head8 = 7a1c41a6 6f7ee464`。
- **不属于**已还原的 kwsg 10400 ZT envelope`head8``5a54eecd...`,见 `core/enc_data.py:ZT_OUTER_CONFIGS`)。
- 判定:登录会话走 **`libpfl_crypto`** 层(`Pfl.decryptBinaryNative`FINDINGS 历史"解密方向"用的是 native 桥,**纯 Python 解密未还原**)。
- 对比reward/ad 的 `encData` = kwsg 10400已还原纯 Python 加密);登录 `dataRsp` = libpfl未还原纯 Python
- 另:`api_st`/`h5_st` 也可能下发在**响应 header**body-only 抓包看不到)——子代理此前同样怀疑此点。
## 4. 与既有结论的交叉验证
- `out/FINDINGS.md:2476-2504` 已实测:直接换 H5 did/egid → `signIn/report`、`treasureBox/report` 返回 `result=50 签名验证失败`。H5 真正缺口是 `kwssectoken/kwscode/kwfv1/kww` 票据组Yoda/KsGuard/WebView 安全层)。
- 本次 capture 未见 `kwssectoken/kwscode/kwfv1/kww` 在 body 中签发 → 这组票据是 **WebView/JS 运行时产物或 header**HTTP body 抓包不包含其生成。`kww` 在真实 App 中确为请求 header`main.py:H5_HEADERS.kww`),本抓包无 header → 不可见。
## 5. "新设备纯 Python 登录"的阻塞点(结论)
| 阻塞点 | 性质 | 现状 |
|--------|------|------|
| ① 运营商 `provider_token` | 外部黑盒(运营商一键登录 SDK需 SIM+设备) | 无法纯 Python 生成;本抓包只看到它被消费 |
| ② `dataRsp` 会话解密 | libpfl_crypto envelopehead8 `7a1c41a6` | 未纯 Python 还原(历史仅 native 桥) |
| ③ `kwssectoken/kwscode/kwfv1/kww` | Yoda/KsGuard/WebView 运行时票据 | 不在 HTTP body 抓包H5 写请求换设备必 `result=50` |
| KS 侧请求签名 `sig/__NS_sig3/__NS_xfalcon` | 已还原 | ✓ 可复算登录请求 |
| DFP 设备注册 | 已还原 | ✓ 新设备可在线注册 |
### 含义
- "新设备 + 复用已有账号 token 跑任务"A 路线API 侧可行(已验证 200**H5 侧被 ③ 卡死**result=50除非补 kws* 票据(需 WebView JS 逆向,不在 HTTP 抓包内)。
- "新设备 + 全新登录拿设备绑定 token"B 路线):被 ①② 卡死。① 要么复刻运营商一键登录 SDK极难、需 SIM要么改走手机号+短信登录(**本抓包未捕获该路径**,需另抓);② 需还原 libpfl_crypto 纯 Python 解密。
## 6. 关键 flow 索引
| flow | 文件前缀 | 用途 |
|------|------|------|
| 13 | `1784817239557610-13-*` | 冷启动 |
| 171 | `1784817240305029-171-1-*` | 配置拉取 |
| 189 | `1784817240121*-189-1-*` | DFP 设备注册deviceInfo |
| 213 | `1784817244968189-213-1-*` | 登录前加密请求3798B待解 |
| 221 | `1784817247*-221-1-*` | **登录提交provider_token明文+签名)** |
| 222 | `1784817247*-222-1-*` | 登录后续响应dataRsp 会话,加密) |
| 272/274 | `1784817247*-27[24]-1-*` | 域名-IP 路由表 |
## 7. 下一步可选
1. **解密 flow 222 `dataRsp`**:还原 libpfl_crypto 纯 Python 解密(参照 `EmbeddedAesKeyHex` + `Pfl.decryptBinaryNative` 的 native 逻辑),取出 `api_st/h5_st/user_id/client_salt` 明文,确认是否设备绑定。
2. **抓手机号+短信登录路径**:作为 ① 的替代(更可纯 Python 复刻),需真机抓一次短信登录 HAR。
3. **WebView JS 逆向 kws* 票据**:解 ③,才能真正让新设备跑 H5 写请求。
## 8. pfl_crypto 静态逆向进展dataRsp 解密2026-07-23
### 已拿到(新硬证据)
- **内嵌 AES key 已静态还原**(此前 FINDINGS 认为需 runtime frida
- `EmbeddedAesKeyHex` @ `libpfl_crypto.so:0x6249c`lazy singletonguard @ `0x111f78`storage @ `0x111fa0`)。
- 构造器 `0x62550`:分配 64 字节,`key[i] = blobA[i] ^ blobB[i % 32]`i=0..63)。
- `blobA` @ `0x182c8`64B`blobB` @ `0x18308`32B循环
- 还原结果:`027e5393fd36a4ba1b6cf52094edb4aeffcc55d175492d87ed7df271eb691ef0`64 hex = AES-256 key
- 诱饵 `WrongEmbeddedAesKeyHex` @ `0x625e4`blobs `0x18328`/`0x18368`= 真 key 首字节 XOR `0x10` -> `127e...`
- **`AesDecrypt` @ `0x62fbc`1728B**hex-decode key 串(校验 len 0x40=AES-256 / 0x20=AES-128随后对密文做**自定义字节重排**memcpy @ 偏移 `0xc``end-0x10`),再调内部 cipher。
- **排除 kwsg-10400**dataRsp `head8=7a1c41a6...`,用 4 个已知 kwsg xor_key 解包后 inner magic 均 ≠ `dec0adde`;且 `7a1c41a6` 非 kwsg head8`5a54ee..`-> **dataRsp 不是 kwsg 10400 envelope**,是 pfl 自有格式。
- **设备绑定已硬证据(无需解密 dataRsp**
- 登录请求 flow 221 `provider_token` = protobuf `{kpn=NEBULA, did=ANDROID_f05497e9cef09a7f, ts, nonce}` -> 登录凭证**构造上就绑设备 did**。
- `probe3.log` 常规任务请求携带 `klinkToken`(同结构 protobuf含 .env 设备 did-> 设备绑定 token 随请求流转。
- `out/FINDINGS.md` 实测:换 H5 did -> `result=50 签名验证失败`
### 仍阻塞(需动态捕获)
- **cipher 是 standard AES硬件实现**`libpfl_crypto.so` 用 ARM 硬件
`aese/aesd/aesmc/aesimc`(全 .so 共 1769 条),故无软件 S-box/rcon/T-table。
- **用还原 keyreal/decoy对 dataRsp 试遍 ECB/CBC/CTR/CFB/OFBAES-128/256
IV@首16/末16/0/12/16/32ct 全/[16:]/[16:-16]/[32:] 等)均乱码** ->
dataRsp **不是**简单 `AesDecrypt(还原key, dataRsp)`
- 要么 dataRsp 用的 key 非 EmbeddedAesKeyHex会话派生 / RSA 包裹 / 另一静态 key
- 要么缺 `Pfl.decryptBinaryNative` 的**第二个参数 envelope**body-only 抓包看不到,
IV/envelope 可能来自该 arg 或响应 header
- 要么 dataRsp 走的不是 pfl网络层另解
- `AesDecrypt@0x62fbc` 字节重排:把密文拆 `[0:12]` / `[12:N-16]` / `[N-16:N]` 三段
std::string再调硬件 AES`0x9492c`等 + 循环)。
- 要拿 dataRsp 明文,需动态捕获真正用的 (key, envelope, IV, plaintext)。
已写探针 `out/probe_pfl_login.js`:登录时 hook `Pfl.decryptBinaryNative`
+ `pfl::crypto::AesDecrypt` + `EmbeddedAesKeyHex`,命中 `api_st/h5_st/user_id`
即打印明文。
### 结论
- 设备绑定问题**已有硬答案 = 是**(请求侧 provider_token 内嵌 did + H5 换设备 result=50
- dataRsp 明文api_st/h5_st/user_id 具体值)需动态跑 `out/probe_pfl_login.js`
抓取key 已静态还原但 dataRsp 非该 key 直解,缺 envelope/session key。属
"确认响应侧 token 是否也带 did 校验"的锦上添花项,不改变绑定结论。