13 KiB
13 KiB
新设备注册到指定账号并完成任务 — 设计与缺口文档
状态:草案 v1(2026-07-23) 范围:把"每次运行生成全新设备 → 注册到指定账号 → 用该新设备跑完整任务链"这件事从现状推进到可闭环。 关联:
task_plan.md(阶段 1–12)/findings.md/progress.md/core/README.md
1. 目标与范围
1.1 目标拆解
"新设备注册到指定账号,并用这个新设备完成后续任务"实际包含三件事:
- 新设备身份生成:每次运行产出一套全新、自洽的设备画像(did/oDid/rdid/egid + 硬件 + runtime hints)。
- 新设备注册到服务端:通过 DFP bootstrap 让服务端签发并承认这套设备身份(cloud did + egid)。
- 新设备绑定到指定账号:让服务端把"这套新设备"与"指定账号"关联起来,后续任务请求同时带账号会话 + 新设备身份并被接受。
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 侧根本没发生。 - 修法:
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 逆向产物。
- 需补:
- 真机抓一次全新设备首次登录的完整 HAR(passport 域、登录接口、短信验证、token 签发)。
- 逆向登录接口签名(可能复用已还原的 sig/sig3/xfalcon,但登录体有额外字段)。
- 还原
api_st/h5_st/client_salt的签发与下发路径(getTokenClientSalt()来源)。 - 处理短信验证码 / 滑块 / 设备校验等服务端挑战。
- 依赖:账号凭据(手机号+密码 或 接码通道)。
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-110sha512,服务端未签发)后照跑 → 必触发风控。 - 修法:
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-169sync_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 后在线测 H5(G2) | 决定是否需 G3 |
| Q2 | 是否有账号凭据可供全新登录? | 用户确认 | 决定 G3 可行性 |
| Q3 | 新设备登录是否触发短信/通知风控? | 真机抓登录 HAR 观察 | G3 子问题 |
| Q4 | DFP bootstrap 在高频注册下是否被限流? | 多次注册实测 | G4、G5 |
| Q5 | 单账号挂多新设备的服务端上限? | 实测 + 观察风控响应 | 影响换设备策略 |
6. 实施计划(A 路线优先)
阶段 D1:打通 H5 新设备(P0,1–2 项改动)
- 修 G1(
_apply_device_profile同步 h5 cookie) - 修 G5(bootstrap 失败硬失败,或加显式 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. 验证计划
每阶段"完成"的判定标准(可复现命令):
# D1 单测
uv run python -m unittest tests.test_main_device_profile tests.test_device_cookie -v
# D1 dry-run(H5+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_*.jsonl:did/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 |