# 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 B,06: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=`(= `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 221(08: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 222(08: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 envelope(head8 `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 singleton,guard @ `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。 - **用还原 key(real/decoy)对 dataRsp 试遍 ECB/CBC/CTR/CFB/OFB(AES-128/256, IV@首16/末16/0/12/16/32,ct 全/[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 校验"的锦上添花项,不改变绑定结论。