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

222 lines
13 KiB
Markdown
Raw Permalink 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.

# 新设备注册到指定账号并完成任务 — 设计与缺口文档
> 状态:草案 v12026-07-23
> 范围:把"每次运行生成全新设备 → 注册到指定账号 → 用该新设备跑完整任务链"这件事从**现状**推进到**可闭环**。
> 关联:`task_plan.md`(阶段 112/ `findings.md` / `progress.md` / `core/README.md`
---
## 1. 目标与范围
### 1.1 目标拆解
"新设备注册到指定账号,并用这个新设备完成后续任务"实际包含三件事:
1. **新设备身份生成**每次运行产出一套全新、自洽的设备画像did/oDid/rdid/egid + 硬件 + runtime hints
2. **新设备注册到服务端**:通过 DFP bootstrap 让服务端签发并承认这套设备身份cloud did + egid
3. **新设备绑定到指定账号**:让服务端把"这套新设备"与"指定账号"关联起来,后续任务请求同时带账号会话 + 新设备身份并被接受。
### 1.2 关键解释分叉(需明确,影响工作量量级)
| 解释 | 含义 | 是否需要登录流 |
|------|------|----------------|
| **A. 账号已登录态绑定** | 账号已通过 `.env` 持有有效 `api_st/h5_st/client_salt`"注册到账号"= 让新设备成为该已登录账号的活跃设备 | 否(若 token 不绑设备)/ 待验证 |
| **B. 全新登录绑定** | 仅有账号凭据(手机号+密码/短信),需在新设备上走完整登录流程,拿设备绑定的 token | 是passport SDK 逆向,量大) |
**推荐路线**:先按 A 推进并用真机/在线测试验证 token 是否跨设备存活(成本低);仅当 A 不可行token 强绑设备)才投入 B。本文档以 A 为主线B 作为兜底章节。
---
## 2. 账号–设备绑定模型(基于证据)
证据来源:`out/FINDINGS.md`、`out/h5_ticket_direct_20260712_142457.log`H5 Cookie 完整抓取)、`core/captured_profile.py`、`core/device_cookie.py`。
### 2.1 三类身份材料
| 类别 | 字段 | 绑定对象 | 获取方式 | 当前来源 |
|------|------|----------|----------|----------|
| 账号会话 | `kuaishou.api_st`native~405B、`kuaishou.h5_st`web~402B、`userId`/`ud` | 账号 + 登录态 | 登录接口签发HttpOnly | `.env` 抓包固化 |
| 账号密码材料 | `client_salt`32-hex | 账号 | `QCurrentUser.getTokenClientSalt()` | `.env` / `captured_profile.py` |
| 设备身份 | `did`/`oDid`/`rdid`/`egid` + 硬件字段 | 设备 | DFP bootstrap 在线签发 / 本地派生 | 内存设备生成 |
| 应用常量 | `client_key=2ac2a76d` | 应用 | 静态 | 硬编码 |
### 2.2 绑定机制 = 隐式共载
- **未发现**独立的"bind device to account"接口HAR 内 grep `passport/login/account/grant` 无命中,仅 `refresh=false` 噪声)。
- 账号 token 与设备 did/egid **同处一个 Cookie** 共载上报;服务端在"看到有效账号 token 携带某设备 did/egid"时建立关联。
- `__NStokensig = SHA256(sig + client_salt)``client_salt` 账号绑定、**非设备绑定**,理论上可在任意设备复用(`out/FINDINGS.md` 已验证)。
### 2.3 两套会话 token关键
- `kuaishou.api_st`native API 链路(广告拉取/上报、API 签到/宝箱)。
- `kuaishou.h5_st`H5 WebView 链路余额、task_list、签到、宝箱信息
- 两者**各自独立**、均账号绑定、均 HttpOnly。新设备能否跑通需分别验证。
---
## 3. 现状评估
| 环节 | 状态 | 证据 / 位置 |
|------|------|-------------|
| 新设备画像生成(全套随机) | ✅ 完成 | `core/device_profile.py:289-489` `DeviceProfileGenerator.new_profile` |
| 进程内即用即弃(不落盘) | ✅ 完成 | `main.py:727-747` `register_memory_device` |
| DFP 在线注册设备cloud did+egid | ✅ 完成 | `tools/new_device.py:54-113`、`core/dfp_client.py` |
| API 链路使用新设备 | ✅ 已验证 200 OK | `progress.md`(换设备后广告拉取 result=1 |
| 账号 token 复用于新设备API | 🟡 实测可用 | 同上:换设备后 API 仍 200 |
| **H5 链路使用新设备** | 🟡 opt-in`--rotate-h5-device`,默认关) | D1 已实现G2 待在线验证 |
| 账号 token 复用于新设备H5 | ❓ 未测试 | 因 §4.1 阻塞h5_st 从未在新设备上跑过 |
| 登录流(获取设备绑定 token | 🟡 已抓到+定性capture/ | 见 `docs/capture_login_chain.md`;运营商一键登录,会话走 libpfl 加密未还原 |
| 新设备登录风控(短信/通知/审核) | ❌ 未处理 | — |
| 出口 IP 轮换 | ❌ 缺失 | `core/dfp_client.py:74-113`、`main._request` 裸连 |
| bootstrap 失败硬断言 | 🟡 opt-in`--strict-device-online`,默认关) | D1 已实现(`main.py` `register_memory_device` |
---
## 4. 缺口清单(按优先级)
### G1【P0·已实现 opt-in】H5 cookie 未随设备轮换
- **位置**`main.py:662-663``h5_cookie_dict/h5_full_cookie` 仅 `__init__` 拷贝一次);`main.py:723-725``_apply_device_profile` 只更 `cookie_dict/full_cookie`)。
- **后果**`query_balance/final_balance/task_list/sign_in_resource/sign_in/treasure_box_info/open_treasure_box` 的 Cookie 头与 `__NS_sig3`/`kww``main.py:1036,1062,1075`)仍用旧账号设备 → **一次运行双设备**H5 旧 / API 新)→ "新设备注册到账号"在 H5 侧根本没发生。
- **修法**
```python
def _apply_device_profile(self, profile: DeviceProfile) -> None:
self.cookie_dict = apply_device_profile_to_cookie(self.cookie_dict, profile)
self.full_cookie = cookie_to_string(self.cookie_dict)
self.h5_cookie_dict = apply_device_profile_to_cookie(self.h5_cookie_dict, profile)
self.h5_full_cookie = cookie_to_string(self.h5_cookie_dict)
```
- **验证**`--memory-device` 在线跑,对比两次 `out/request_replay_*.jsonl` 中 H5 接口 Cookie 的 `did/egid` 是否换新且互异。
### G2【P0·验证未知】h5_st / api_st 是否跨设备存活
- **问题**:账号 token 是在旧设备上签发的新设备上携带是否被服务端接受API 侧已观测 200**H5 侧从未测过**(被 G1 阻塞)。
- **动作**:先修 G1再在线用 `--memory-device` 跑 H5 链路,观察 `sign_in_resource`/`treasure_box_info` 是否 `result=1`
- **分支**
- 若 H5 也 200 → 解释 A 成立,无需登录流,跳到 G4+。
- 若 H5 返回会话失效/设备校验类错误 → token 强绑设备,需走 G3登录流
### G3【P1·大块逆向】登录流仅 G2 失败时启用)
- **现状**HAR 未捕获登录;无 passport/login SDK 逆向产物。
- **需补**
1. 真机抓一次**全新设备首次登录**的完整 HARpassport 域、登录接口、短信验证、token 签发)。
2. 逆向登录接口签名(可能复用已还原的 sig/sig3/xfalcon但登录体有额外字段
3. 还原 `api_st/h5_st/client_salt` 的签发与下发路径(`getTokenClientSalt()` 来源)。
4. 处理短信验证码 / 滑块 / 设备校验等服务端挑战。
- **依赖**:账号凭据(手机号+密码 或 接码通道)。
### G4【P1·风控前置】出口 IP 轮换
- **问题**:同 IP 反复注册新设备 + 跑任务 = 经典风控信号。`dfp_client.post_request`、`main._request` 均无 `proxies` 接入。
- **修法**:给 `KsNebulaClient` / DFP client 加 `proxies` 透传(`KS_HTTP_PROXY` / per-run 代理池),每运行换出口 IP。
### G5【P1·已实现 opt-in】bootstrap 失败应硬失败
- **位置**`main.py:731-742`。
- **问题**bootstrap 异常或无 egid 时 fallback 到本地假 egid`device_id.py:98-110` sha512服务端未签发后照跑 → 必触发风控。
- **修法**`device_online` 模式下 bootstrap 失败 → `raise SystemExit`,不 fallback或加 `--allow-fallback-egid` 显式开关。
### G6【P2·用法】设备 seed 不可固定
- **位置**`main.py:1368``--device-seed`)、`device_profile.py:287`。
- **问题**:固定 seed → 每次生成**完全相同**设备。文档/示例里 `--device-seed 20260711` 仅供 dry-run 复现。
- **修法**:默认不传 seed如需可复现用运行时间派生唯一 seed 并记录到日志。
### G7【P2·质量】设备字段合理性 / 去聚类
- **位置**`device_profile.py:291``install_time_ms = now - randint(10s,600s)` → 每个新设备都"10 分钟内刚装",弱聚类特征)。
- **修法**install_time 分布到几小时~几天3 个硬件模板可扩充;`keeper_seed/du/manus/ipv6_map` 已随机(确认其确实进入 DFP deviceInfo 且语义合法)。
### G8【P2·一致性】STED / native 持久化态随 egid 同步
- **位置**`core/device_profile.py:154-169` `sync_egid_cache` / `refresh_persisted_cache_m`、`core/ksse_sted.py`。
- **现状**:换 egid 后 `sted_cache_json`/`persisted_cache_m` 已重算并进 DFP deviceInfo。
- **需确认**:这些 native 态在"纯 Python 无真机"下是否仅作为 deviceInfo 字段上报(服务端不读回本地文件)——若是则已足够;若服务端有读回校验则需补持久化模拟。**初判:仅上报字段,已足够。**
---
## 5. 关键未知与决策点
| 编号 | 未知 / 决策 | 解决方式 | 阻塞 |
|------|------------|----------|------|
| Q1 | h5_st/api_st 是否跨设备存活? | 修 G1 后在线测 H5G2 | 决定是否需 G3 |
| Q2 | 是否有账号凭据可供全新登录? | 用户确认 | 决定 G3 可行性 |
| Q3 | 新设备登录是否触发短信/通知风控? | 真机抓登录 HAR 观察 | G3 子问题 |
| Q4 | DFP bootstrap 在高频注册下是否被限流? | 多次注册实测 | G4、G5 |
| Q5 | 单账号挂多新设备的服务端上限? | 实测 + 观察风控响应 | 影响换设备策略 |
---
## 6. 实施计划A 路线优先)
### 阶段 D1打通 H5 新设备P012 项改动)
- [ ] 修 G1`_apply_device_profile` 同步 h5 cookie
- [ ] 修 G5bootstrap 失败硬失败,或加显式 fallback 开关)
- [ ] 单测:`tests/test_main_device_profile.py` 增"H5 cookie 随 memory device 更新"用例
- [ ] dry-run 验证 H5 与 API 一致用新设备
### 阶段 D2在线验证 token 跨设备P0解 Q1
- [ ] `--memory-device` 在线跑完整链,记录 H5 各接口 result
- [ ] 重复跑 2 次,确认设备每次不同且任务成功
- [ ] 结论A 可行 → 进 D3不可行 → 转 B 路线(阶段 D5+
### 阶段 D3风控前置P1
- [ ] G4 接入代理池 / `KS_HTTP_PROXY`
- [ ] G6 默认随机 seed
- [ ] G7 install_time 去聚类、扩硬件模板
### 阶段 D4闭环与回归P1
- [ ] 全链在线跑 N 次,统计成功率与风控响应
- [ ] 回归既有单测 + DFP parity 测试
- [ ] 更新 `task_plan.md` 新增阶段
### 阶段 D5仅 B 路线):登录流逆向
- [ ] 真机抓首次登录 HAR
- [ ] 定位 passport/login 接口与签名
- [ ] 还原 api_st/h5_st/client_salt 签发
- [ ] 处理短信/滑块挑战
---
## 7. 验证计划
每阶段"完成"的判定标准(可复现命令):
```bash
# D1 单测
uv run python -m unittest tests.test_main_device_profile tests.test_device_cookie -v
# D1 dry-runH5+API 一致用新设备)
uv run python main.py --dry-run --memory-device --no-device-online --out-dir out\d1_dryrun
# D2 在线验证(真·每次新设备)
uv run python main.py --memory-device --out-dir out\d2_online_run1
uv run python main.py --memory-device --out-dir out\d2_online_run2
# 比对 run1/run2 的 request_replay_*.jsonldid/egid 互异且 H5 接口 result=1
```
验收清单:
- [ ] 两次运行 did/egid 互不相同(无固定 seed 泄漏)
- [ ] H5 与 API 的 Cookie 中 did/egid **同次运行一致**
- [ ] H5 接口在线返回 `result=1`(解 Q1
- [ ] bootstrap 失败时进程退出而非 fallback 假 egid
- [ ] 出口 IP 每运行可切换
---
## 8. 风险
| 风险 | 等级 | 缓解 |
|------|------|------|
| h5_st 强绑设备 → A 路线失败 | 高 | D2 早验证;失败转 B |
| 单账号多设备触发风控/封号 | 高 | 限频、IP 轮换、控制换设备次数(已有 `--device-max-switches` |
| DFP bootstrap 高频被限流 | 中 | 实测限流阈值;代理 + 退避 |
| 登录流逆向工作量超预期 | 中 | 仅在 A 失败时启动;先抓 HAR 评估 |
| 假 egid 静默 fallback 致封号 | 中 | G5 硬失败 |
| 设备字段聚类特征 | 低 | G7 去聚类 |
---
## 9. 附录:关键文件索引
| 关注点 | 文件 |
|--------|------|
| 任务编排 / 设备轮换 | `main.py``KsNebulaClient`、`register_memory_device:727`、`maybe_rotate_device_for_record:749` |
| 设备画像生成 | `core/device_profile.py` |
| 设备字段→Cookie | `core/device_cookie.py` |
| 设备 id 派生 / 假 egid | `core/device_id.py` |
| DFP 在线注册 | `tools/new_device.py`、`core/dfp_client.py`、`core/dfp_forms.py` |
| STED 持久化 | `core/ksse_sted.py` |
| 账号材料样本 | `core/captured_profile.py`、`.env` |
| H5 会话/ticket 分析 | `out/analyze_h5_ticket_flow.py`、`out/h5_ticket_direct_*.log` |
| 算法总览 | `core/README.md`、`out/FINDINGS.md` |