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

13 KiB
Raw Blame History

新设备注册到指定账号并完成任务 — 设计与缺口文档

状态:草案 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.mdout/h5_ticket_direct_20260712_142457.logH5 Cookie 完整抓取)、core/captured_profile.pycore/device_cookie.py

2.1 三类身份材料

类别 字段 绑定对象 获取方式 当前来源
账号会话 kuaishou.api_stnative~405Bkuaishou.h5_stweb~402BuserId/ud 账号 + 登录态 登录接口签发HttpOnly .env 抓包固化
账号密码材料 client_salt32-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_stnative API 链路(广告拉取/上报、API 签到/宝箱)。
  • kuaishou.h5_stH5 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-113core/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-113main._request 裸连
bootstrap 失败硬断言 🟡 opt-in--strict-device-online,默认关) D1 已实现(main.py register_memory_device

4. 缺口清单(按优先级)

  • 位置main.py:662-663h5_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/kwwmain.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 侧已观测 200H5 侧从未测过(被 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_requestmain._request 均无 proxies 接入。
  • 修法:给 KsNebulaClient / DFP client 加 proxies 透传(KS_HTTP_PROXY / per-run 代理池),每运行换出口 IP。

G5【P1·已实现 opt-in】bootstrap 失败应硬失败

  • 位置main.py:731-742
  • 问题bootstrap 异常或无 egid 时 fallback 到本地假 egiddevice_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:291install_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_mcore/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. 验证计划

每阶段"完成"的判定标准(可复现命令):

# 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.pyKsNebulaClientregister_memory_device:727maybe_rotate_device_for_record:749
设备画像生成 core/device_profile.py
设备字段→Cookie core/device_cookie.py
设备 id 派生 / 假 egid core/device_id.py
DFP 在线注册 tools/new_device.pycore/dfp_client.pycore/dfp_forms.py
STED 持久化 core/ksse_sted.py
账号材料样本 core/captured_profile.py.env
H5 会话/ticket 分析 out/analyze_h5_ticket_flow.pyout/h5_ticket_direct_*.log
算法总览 core/README.mdout/FINDINGS.md