Files
yml2213 e2485c5ea6 docs(账本): 记录 H3 单变量实验结论(captcha 域 cookie 与 errorCode=12 无关)
做了什么:
- ANALYSIS.md §7.1 新增 E8: captcha 域"带/不带"腾讯云登录 cookie, 单变量 A/B 均为 12
- ANALYSIS.md §7.4 假设表更新: H3 标注为已排除, 只剩 H1/H2
- ANALYSIS.md §4.3 更正: extract_eks 改通用匹配后实测 248 字符, 与真实浏览器一致
- PROGRESS.md §三 已排除表新增 H3 一行

为什么:
- 彻底关闭"换 cookie/cookie 同源"这条猜测路线, 避免重复投入

怎么验证:
- 单变量 A/B: scripts/experiment_h3_cookie_ab.py --both  -> A=12, B=12
  (同一次运行, 题目 2404 求解 posterior=1.000 全对, collect_len=1856)
2026-10-11 21:47:27 +08:00

25 KiB
Raw Permalink Blame History

毫秒 1ms.run 腾讯天御「文字点选」协议分析

分析样本:/Users/yml/Desktop/抓包/毫秒/点选.har(20 条记录,WebInspector 导出) 分析时间:2026-10-11 目标题型:subcapclass = 2404(腾讯天御 文字点选,提示语为「请依次点击:甲 乙 丙」)


一、请求链

抓包中与验证码相关的记录共 12 条,可还原为 1 个业务侧引导请求 + 11 个腾讯验证码请求:

① POST https://1ms.run/api/v1/captcha/params            ← 业务侧:取验证码配置
   body: {"scene":"checkin","identity_context":"emctx1.<b64>.<b64>"}
   resp: {"code":0,"data":{"enabled":true,"provider":"tencent",
                           "params":{"captchaAppId":195672344},
                           "sessionUuid":"01a12a04-e5e7-726b-b00c-2f54b2e1c167"}}

② GET  https://turing.captcha.qcloud.com/cap_union_prehandle     ← 建立会话 + 首题
       ?aid=195672344&protocol=https&accver=1&showtype=popup&ua=<base64 UA>
       &noheader=1&fb=1&aged=0&enableAged=0&enableDarkMode=0&grayscale=1
       &clientype=2&cap_cd=&uid=&lang=zh-cn
       &entry_url=https%3A%2F%2F1ms.run%2Fconsole%2Fpersonal%2Fbenefits%2Fcheck-in
       &elder_captcha=0&js=%2Ftcaptcha-frame.ba387dd1.js&login_appid=
       &wb=1&tkid=407017912&subsid=1&callback=_aq_260580&sess=
   resp: _aq_260580({... "capclass":"1","subcapclass":"2404","sess":"s0Rr8i01...",
                     "sid":"1793111442254233600",
                     "data":{"comm_captcha_cfg":{"tdc_path":"/tdc.js?app_data=<sid>&t=<rand>",
                                                 "pow_cfg":{"prefix":"a79f541e7c1ca571#",
                                                            "md5":"0264f7a4329e2b6f9f9076c53294c277"}},
                             "dyn_show_info":{"instruction":"请依次点击:虫 呈 般 ",
                                              "bg_elem_cfg":{"size_2d":[672,480],
                                                             "click_cfg":{"mark_style":"inc_number",
                                                                          "data_type":["DynAnswerType_POS"]},
                                                             "img_url":"/cap_union_new_getcapbysig?img_index=1&image=...&sess=..."}}}})

③ GET  .../tdc.js?app_data=1793111442254233600&t=1309148276      ← 会话专属混淆脚本
④ GET  .../cap_union_new_getcapbysig?img_index=1&image=...&sess=...  ← 672x480 背景图

   ── 第 1 次作答 ──
⑤ POST .../cap_union_new_verify   (ans + collect + eks + sess + pow_*)  → errorCode 50
⑥ POST .../cap_union_new_getsig   (sess + tkid)  → 新 sess + 新题
⑦ GET  .../cap_union_new_getcapbysig?...  第 2 张图
   ── 第 2 次作答 ── ⑧ verify → errorCode 9
⑨ getsig ⑩ getcapbysig            第 3 张图
   ── 第 3 次作答 ── ⑪ verify → errorCode 50
⑫ getsig ⑬ getcapbysig            第 4 张图(抓包结束时未提交)

抓包中的 y.clarity.ms/collect(微软 Clarity 埋点)、collector.entmesh.mliev.com 与验证码无关,属于站点自身的监控上报,本文不纳入实现。


二、会话状态机(关键!错一个就永远失败)

sess 在三处出现,且互不相同,必须严格区分:

位置 说明
prehandle 响应体的 sess 会话主 sess,首次 verify 必须用它
dyn_show_info.bg_elem_cfg.img_url 里的 sess= 只作用于取图,与 verify 用的 sess 不是同一个
verify 响应的 sess 上一轮失败后的新会话 sess
getsig 请求体 sess 用的是上一步 verify 响应里的新 sess(不是会话主 sess)
getsig 响应体的 sess 下一轮的会话 sess,下一轮 verify 用它

实测(点选.har):

prehandle.sess          = s0Rr8i01...(482)   ← verify#1 用它
verify#1  resp.sess     = s0TMSpmIpMCH3...(482) ← getsig#1 用它
getsig#1  resp.sess     = s0VSknLJwQHTwRC...(494) ← verify#2 用它,同时也是该轮 img_url 的 sess
verify#2  resp.sess     = s05b9OQJg2z6nx...(494) ← getsig#2 用它
getsig#2  resp.sess     = s0nrIxkexDWO0Rgd...(482) ← verify#3 用它

题型在会话内固定:对同一会话连续 getsig,返回的 subcapclass 不变(实测滑块会话 连发 8 次 getsig 全是滑块)。想换题型只能重新 prehandle 开新会话。


三、参数分类

3.1 固定/常量参数

参数 值 说明
protocol https 固定
accver 1 协议版本
showtype popup 弹窗模式
noheader / fb 1 / 1 固定
aged / enableAged / enableDarkMode 0 固定
grayscale 1 灰度模式开启
clientype 2 PC 端
lang zh-cn 语言
js /tcaptcha-frame.ba387dd1.js 验证码 frame 脚本名
wb 1 1ms.run 侧取值(雨云为 2)
subsid 1 抓包中为 1(雨云实现里随机 1~5,实测不影响)

3.2 每会话动态参数

参数 生成规律
aid 业务侧下发:1ms.run/api/v1/captcha/params → captchaAppId = 195672344
tkid 0~9 数字串,9 位;同一会话内所有请求复用同一个值(抓包:407017912)
callback _aq_ + 6 位随机数;仅 prehandle 使用(JSONP)
entry_url 触发页 URL(1ms 为签到页),URL-encoded
ua 请求 UA 的 base64(抓包解码 = Chrome/155.0.0.0 on macOS)
sid / app_data prehandle 响应返回,仅用于拼 tdc_path
sess prehandle / getsig / verify 三级传递,见第二节
image 服务端下发在 img_url 中的图片令牌,逐题变化

3.3 动态参数变化规律(多题对比)

题号 image instruction ans pow_answer pow_calc_time
1 026409000000001300000015aa3f011427ce 请依次点击:虫 呈 般 162,249 / 104,37 / 561,156 a79f541e7c1ca571#16447 46
2 026409000090e146000000159f1391142763 请依次点击:佰 衬 稗 426,47 / 332,156 / 517,332 同题 1 46
3 0264090000857147000000159f13911427e7 请依次点击:簿 蚕 沧 498,136 / 45,118 / 252,146 同题 1 46

结论:

  • pow_answer / pow_calc_time 在整个会话内恒定 —— 它来自 prehandle 的 pow_cfg, 是一次性的工作量证明,而不是每轮刷新。
  • collect / eks 在整个会话内恒定(三轮 verify 的 collect 长度都是 1784 且字节一致) —— 它们由 tdc.js 在页面初始化时一次性生成,之后每次 verify 复用,不随后续点击坐标变化。
  • ans 逐题变化,且坐标随图片令牌变化,不存在可复用规律。

四、签名 / 加密参数分析

4.1 pow_answer —— MD5 工作量证明(已完全还原)

服务端给 pow_cfg = {"prefix": "a79f541e7c1ca571#", "md5": "0264f7a4329e2b6f9f9076c53294c277"}。

反推验证:

md5("a79f541e7c1ca571#16447") = 0264f7a4329e2b6f9f9076c53294c277   ✅ 命中

即 pow_answer = prefix + str(i),其中 i 从 0 递增直到 MD5 命中;pow_calc_time 是浏览器自己测量的耗时(抓包 46ms,本机 Python 约 120~190ms)。

4.2 collect —— 自定义 XTEA 分组加密 + 行为轨迹(结构与编码已还原)

collect 是 URL-encoded 的 Base64,不是 4 段分别 base64 后拼接的产物:

来源 Base64 长度 解码字节数 内部 =
点选.har 真实浏览器 1784 1336 = 48 + 768 + 504 + 16 仅末位 ==
本仓库 Node 沙箱执行真实 tdc.js 1654 1168 = 48 + 600 + 504 + 16 仅末位 ==
本仓库纯 Python 还原 1712~1822 随轨迹长度浮动 块内出现 = ❌

明文结构(按 8 字节分组、32 轮自定义 XTEA 加密,密钥 4 个 32bit word):

[0    .. 48)    cd 前缀   {"cd":[[],"1920-1080-1055-24-*-*-|-*",0,0,"",   (空格补齐)
[48   .. 816)   鼠标轨迹  [[x0,y0,2],[dx1,dy1,dt1],...]  长度必须是 24 的倍数
[816  .. 1320)  cd 后缀   画布指纹/UA/平台/分辨率/时区/语言/…
[1320 .. 1336)  sd 结尾   "sd":{"od":"C"}}

已修掉的实现缺陷:原 generate_trajectory_payload() 把轨迹补齐到 8 的倍数, 导致分块 base64 后出现块内 = 填充(真实样本只有末尾一个 ==)。已改为补齐到 24(= lcm(8,3))的倍数,与真实样本结构一致。

已定论(2026-10-11 实测修正):DEFAULT_KEY = [1448563793, 1847730015, 1733184827, 1364613714] 以及两个轮加盐 2829068 / 328986,是当时逆向的那一份 tdc.js 构建里的密码学参数。 实测三份脚本(每份都先跑真实 tdc.js 拿到 collect,再尝试解密):

tdc.js 来源 轮加盐(脚本内联值) 用 DEFAULT_KEY + 该加盐解密
tcaptcha/tdc_sample.js(雨云样本) 2829068 / 328986 ✅ 明文 {"cd":[[],"1920-1080..."
雨云 aid=2039519451 当天实时 394753 / 657930 ❌ 随机字节
1ms aid=195672344 当天实时 197386 / 1513230 ❌ 随机字节

结论:XTEA 的 4 个密钥字与 2 个轮加盐都是"每次构建随机"的,且不是可 grep 的字面量 (它们藏在 JSVMP 的操作数流里,DELTA 2654435769 与 DELTA*32 = 84941944608 是唯一可见的常量)。 所以「纯 Python 硬编码密钥」本质上不是算法还原,而是单份脚本的参数固化。

但关键反证:服务端并不按 tdc.js 的会话密钥去解密 collect。实测雨云 aid 用 纯 Python(硬编码密钥)的 collect 提交正确答案 → errorCode=0 + 真实 ticket。 因此参数随机化不构成阻塞,纯 Python 交付形态依然成立(默认 telemetry_mode="python")。 node / browser 后端保留为「目标 aid 风控更严格」时的备选。

4.3 eks —— 会话密钥信封

eks 由 tdc.js 内部 TDC.getInfo().info 产出,形如 Base64 的 236248 字符 (= 176184 字节),推测是用服务端公钥包裹的会话密钥,服务端用它解密 collect。

  • 早期用写死变量名的正则(window.KQnlRHY...)提取,得到 236 字符,与真实浏览器的 248 不一致。 现已改为通用匹配(任取最长的 window.<随机名>='<base64>'),在 2026-10-11 的实时构建上 稳定取到 248 字符,与真实浏览器观测一致(见 E8 那次运行 eks_len=248)。 eks 的来源与 collect 必须同源(同一份 tdc.js、同一次执行),不可混用。

4.4 ans —— 点击坐标(明文,无需签名)

[{"elem_id":1,"type":"DynAnswerType_POS","data":"162,249"},
 {"elem_id":2,"type":"DynAnswerType_POS","data":"104,37"},
 {"elem_id":3,"type":"DynAnswerType_POS","data":"561,156"}]

坐标位于 672x480 原图坐标系,顺序即提示语顺序(mark_style: inc_number)。

4.5 无法确定的内容(明确声明,不虚构)

  1. XTEA 会话密钥的派生过程:tdc.js 每次加载都会重新混淆,密钥常量未以字面量出现 (1448563793 等 4 个常量在 tdc.js 中检索不到),需要源码级插桩才能完全还原。 本实现改为执行真实 tdc.js,绕过该问题。
  2. eks 的加密算法与字段布局:未逆向,直接复用 tdc.js 产出。
  3. click_cfg 之外的题型(2408 图标点选、2402/2401 滑块)尚未实现求解器。

五、文字点选题型的视觉求解(2404)

与雨云那套「图标点选」完全不同,本题型:

  • 没有提示图:img_url 换成 img_index=0 返回 0 字节(实测多次)
  • 提示是纯文本:instruction = "请依次点击:虫 呈 般 "
  • 背景图里只有这 3 个汉字(无干扰字),位置随机,部分题目带斜切变形
  • 汉字颜色恒定:BGR≈(58,176,228) → HSV H≈20 S≈185 V≈228

因此求解 = 颜色分割定位 → 汉字识别 → 按提示顺序做最优一对一指派,实现见 tcaptcha/glyph_solver.py:

  1. HSV 阈值分割(H∈[8,34] S>85 V>145)+ 形态学闭运算 + 13px 膨胀把偏旁并成整字
  2. 连通域得到包围盒,按面积/长宽比过滤,得到候选字形
  3. 掩膜 → 白底黑字 → 3 倍放大,交给 OCR 集成打分
  4. 得分 = 每个引擎在 CTC 各时间步上目标字概率的最大值,取多预处理变体最大值后加权求和
  5. 剔除全部目标字得分都 < 0.02 的伪字形(照片暖色区域会误检)
  6. 3! 全排列求最优指派,并用归一化后验概率作为置信度

OCR 集成为什么必要

字形 ddddocr max P(正确字) PP-OCRv4
筹(生僻,带斜切) 0.0046 ❌ 正确 ✅
雹 正确 ✅ 误识为「电」❌
笆 误识为「芭」❌ 正确(or 集成后仍正确指派)✅

单引擎都不够;概率级集成后 6/6 真实样本全部正确且后验置信度 = 1.000。

6/6 回归样本

样本 提示 求解结果
har_round1 虫 呈 般 (149,252) (48,55) (549,351) ✅
har_round2 佰 衬 稗 (449,53) (549,353) (349,153) ✅
har_round3 簿 蚕 沧 (549,153) (50,255) (48,154) ✅
har_round4 沉 驰 齿 (150,154) (550,153) (49,53) ✅
live_probe 笆 辩 裁 (549,153) (50,54) (351,152) ✅
live_rare 筹 炽 雹 (449,253) (49,353) (250,255) ✅

用户抓包里失败的 3 次,回看坐标可确认是人工点错,不是协议问题:

  • 第 1 题「虫 呈 般」:虫(162,249) ✅、呈(104,37) ✅、般 点在 (561,156), 而 般 实际在 (549,351) —— 第 3 击差 195px → 50
  • 第 2 题「佰 衬 稗」:佰 ✅,但第 2、3 击把 稗 和 衬 点反了 → 50
  • 第 3 题「簿 蚕 沧」:第 2 击点在 (45,118) 是 沧 而非 蚕,第 3 击 (252,146) 落空 → 9

六、线上验证记录(errorCode 语义)—— 以及一个必须纠正的初判

6.1 1ms aid=195672344 的返回规律

提交内容 errorCode 复现次数
三击全部落在图外的乱坐标 (10,10)(20,20)(30,30) 50 4 次
正确坐标(求解器命中 3/3) 12 8 次
正确坐标但故意把第 1、2 击顺序点反 50 1 次
[REAL ] 请依次点击:安 胺 猜  post=1.000 -> 12
[BOGUS] 请依次点击:睬 持 层            -> 50
[REAL ] 请依次点击:半 陈 补  坐标正确但 1<->2 点反 -> 50

errorCode 12 只在答案完全正确时出现 → 答题链路(坐标空间、顺序、elem_id、 pow_answer、sess 状态机)100% 正确,服务端是在答案通过后进入风控评估才拒绝的。

6.2 三个对照实验(决定了最终结论)

对照 A —— 同一套代码换 aid,直接拿到 ticket:

雨云 aid=2039519451, 图标点选(2408), 纯 Python 硬编码密钥 collect
→ errorCode=0, ticket=tr03jvqlp225MjNBxjDj_-mnM1VTS8O9, randstr=@wBc  ✅

这一条同时证明了两件事:

  1. 纯 Python 交付形态完全有效,服务端不校验 collect 与 eks 的同源性;
  2. 1ms 的 12 不是遥测密钥/算法的问题。

对照 B —— 三种遥测后端在 1ms 上结果一致:

后端 说明 1ms 结果
python 硬编码密钥纯算法还原 12
node Node VM 伪造环境执行真实 tdc.js 12
browser 真实 Chrome + CDP 可信鼠标事件执行真实 tdc.js 12

对照 C —— 换出口 IP(住宅代理),彻底排除 IP 滚动风控:

出口 IP 归属 结果
118.248.52.65 本机(与抓包 IP 118.248.47.92 同 /16) 12
123.181.235.161 河北秦皇岛(住宅代理) 12
114.222.19.181 江苏南京(住宅代理) 12
123.181.235.170 住宅代理 12

每个新 IP 上都是「后验置信度 1.000 的正确答案 → 12」,其中还夹杂一次 「后验 0.789 的题 → 50」(说明这道题确实点错了,反向印证了 12/50 的语义)。 另外还做过「静默 9 分钟 + 单发一次」,同样是 12。

6.3 结论(诚实版)

  • ✅ 文字点选求解器:16 道实时题全部命中,后验置信度均 ≈1.000 (尝辨长 / 佰簿班 / 半陈补 / 安胺猜 / 侈持钡 / 稗伴瓣 / 乘匙层 / 辰本虫 / 秤遍稗 / 背岸拔 / 奔薄芭 / 倍玻畅 / 趁骋薄 / 筹辩彻 …)
  • ✅ 协议与答题提交:正确(服务端只在答案正确时才进入风控评估)
  • ✅ 纯 Python 遥测:在雨云 aid 上端到端产出真实 ticket,交付形态成立
  • ❌ 1ms aid 的票据发放:被该 aid 的风控拦在 errorCode=12

已经排除的原因:算法、XTEA 参数随机化、遥测引擎(python/node/browser)、 请求参数与 sess 状态机、短时频控、出口 IP 滚动风控。

剩余唯一可能:该 aid 配置了更严格的风控,要求会话具备"真实验证码页面上下文" (tdc.js 运行在真实验证码 iframe 内:真实 iframe location、真实 Canvas/WebGL 指纹、 isTrusted 鼠标事件、真实 document.referrer),而我们的 node/browser 后端 都是把 tdc.js 注入到"空白页"执行,缺少这层上下文。

6.4 下一步(能真正拿到 ticket 的路)

完整浏览器内求解:用 CDP/Playwright 打开真实页面触发验证码,让原生 SDK 自己跑 tdc.js;复用本仓库已完成的视觉求解器算坐标,用 Input.dispatchMouseEvent 在 iframe 内真实点击,从 SDK 回调取 ticket / randstr。

tcaptcha/tdc_browser.js 已经把 CDP 骨架写好了(启动 Chrome / 注入脚本 / 派发可信事件 / 读回 TDC.getData()),只需把「注入 tdc.js 的空白页」换成 「真实验证码页面」。换 IP 的能力也已经内置(tcaptcha/proxy.py + --proxy-api,遇 12 自动换 IP),届时可直接复用。

6.5 求解器鲁棒性修复(本次线上测试暴露的真实缺陷)

代理测试暴露出 2 例定位失败,根因是 HSV 下限 V>=145 把暖色草地/沙地并进了字形掩膜:

样本 现象 根因
长 澄 班 只检出 1~2 个字形 草地 H≈25 S≈170 V≈156 落在阈值内,把 澄/班 一起吞进大块背景连通域
菜 倍 钵 只检出 2 个字形 倍/钵 竖排相邻,13px 膨胀把它们粘成 332x80 长条,被长宽比过滤器丢弃

实测颜色统计:汉字 H≈20.5 S≈200 V≈232;草地 V≈122~156 —— 亮度 V 才是干净的分隔维度。 把下限收紧为 H>=12 S>=145 V>=190 后,8 张图(含这 2 张失败样本)全部稳定检出 3 个字形, 两张失败样本也已加入 tcaptcha/tests/fixtures/ 作为回归用例。

另外把 min_posterior 从 0.6 提到 0.95:实测全部正确解的后验都是 1.000, 而那次后验 0.789 的提交确实被判 50(点错),门限收紧可避免提交不可靠答案。

6.6 题型覆盖

20 次连续抽题统计(aid=195672344):

subcapclass 题型 占比 状态
2408 图标点选(提示为 img_index=0 图标图) 35% 需按 1ms 的图标类别重训模型
2404 文字点选(本次目标题型) 15% ✅ 求解器完成并验证 (8/8 回归)
2402 / 2401 滑块拼图 50% 未实现(需模板匹配找缺口)

题型在会话内固定:getsig 不换题型,只有重新 prehandle 才能抽到别的题型。 client.solve 已内置「抽到目标题型再提交」的循环(默认 max_refreshes 次)。


七、errorCode=12 根因分析 —— 证据账本(v2,推倒重写)

v1 版本(已废弃)里我下了多个无证据结论,本节按 §1.3 重写: 只写有证据的"已知"、明确的"未定"、以及被推翻的假设。

7.1 已确证事实(每条附证据)

# 事实 证据
E1 答案判错→50,判对→12,稳定复现 乱坐标 4 次→50;正确 8+ 次→12;正确但顺序点反→50
E2 同一套纯协议代码在雨云 aid 上 → 0 + 真实 ticket aid=2039519451 端到端实测
E3 真实签到流程(已登录、真页面、真浏览器、同 IP 118.248.52.65)→ 0 + 真实 ticket 签到成功-1.har 条目 66/67
E4 官方 SDK 注入在 1ms.run 首页(未登录)+ 真人点击 → 12 solve_in_browser.js 抓到的 verify 响应
E5 域不在白名单时 SDK 直接短路:不出题、立刻回 trerror_1006_... 错误票据 127.0.0.1 页面上 SDK 加载 1 秒内即回调 ret:0,无任何挑战 UI
E6 纯 Python 的 collect 各种变体(空/垃圾/截断/真实环境值/真实轨迹 dt)→ 全部 12 变异矩阵 + 多轮实测
E7 成功流程的 captcha 请求带 qcloud.com 域 cookie(uin/skey/qcmainCSRFToken/intl);我们不带 HAR 条目 60/61/62 请求头
E8 captcha 域"带 / 不带"腾讯云登录 cookie,结果完全相同:单变量 A/B 均为 12 2026-10-11 scripts/experiment_h3_cookie_ab.py --both:A=12、B=12(同一次运行、同一 tdc.js 构建,题目 2404 求解 posterior=1.000 全对,collect_len=1856)

7.2 显式撤回的错误结论(§5.7)

曾下的结论 撤回理由
❌ "服务端不看 collect" E6 里 collect 置空也 12,更可能是"校验失败即 12",不能反推"不看"
❌ "环境指纹取值是瓶颈" 填入真实值后仍 12,未证实
❌ "CDP/临时 profile 不被检测" 依据无效:E5 显示 127.0.0.1 上根本不出题,ret:0 是域授权短路,与风控无关
❌ "12 是业务账号上下文问题"(腾讯云 cookie) 仅一次用可能过期的 skey 测试,不能定论

7.3 关键混淆变量(必须承认)

E3(成功)与 E4(失败)之间同时变了三个变量: ① 真实签到页 vs 注入首页 ② 已登录 vs 未登录 ③ 无 CDP vs 有 CDP

所以目前无法把 12 归因于其中任何一个 —— 之前归因"腾讯云 cookie"是过度解读。

7.4 剩余假设(互相独立,需单变量实验区分)

# 假设 状态
H1 需要已登录业务会话 / 真实验证码页面上下文(页面自己初始化的验证码) 唯一未被单变量区分的剩余假设
H2 CDP / 自动化被检测 唯一未被单变量区分的剩余假设
H3 需要 qcloud.com 账号 cookie(有效 uin/skey) ❌ 已排除(E8:带/不带 cookie 单变量 A/B 均为 12)

7.5 决定性实验(单变量,二选一即可闭合)

在同一浏览器里登录 1ms,走真实签到页,让页面自己的验证码弹出并人工完成(我方仅监听 verify):

  • 结果 0 → H1 成立(CDP/自动化无嫌疑),修复方向 = 复刻登录态与业务会话
  • 结果 12 → H2 成立(CDP 被检测),纯算+CDP 均不可行,只能半自动

若两者都排除,再做 H3(取有效 qcloud.com cookie 复测)。

7.6 与本题无关但已完成的资产

  • XTEA 算法逐字节还原 + 每构建密钥提取(tcaptcha/crypto.py、tdc_worker.js captureKey)
  • 文字点选求解器 8/8 回归(tcaptcha/tests/test_glyph_solver.py)
  • 模板标定 + 纯 Python 回填管线(generate_collect_success)

八、路线修订(2026-10-11 晚)

8.1 已废弃「补环境」路线

曾实现 3 个"在伪造/真实环境里跑 tdc.js 产出 collect"的后端(node VM / browser CDP / probe 深度 Proxy),现已全部从代码移除。依据:§7.1 E6 与 §6.2 对照 B/C 已证明 collect 内容、遥测后端、出口 IP、静默时长 都不是 12 的自变量。

废弃后唯一路径:python_keyed(Node 仅提取 XTEA 密钥字)+ 纯 Python 模板回填。 已删除的模块级符号:apply_real_environment、4 段拼接旧结构、human_dt_ms、 generate_telemetry_node/browser/probe、generate_collect_pure。

8.2 一处必须留意的置信度缺陷(已修)

glyph_solver 的"字形一致性强校验"此前用单引擎 ocr_text 判断,会把正确解误判为 不匹配(实测 簿→垂、笆→芭、筹→努),导致回归从 8/8 掉到 5/8。 现改为用用于指派的集成得分矩阵行 argmax 做自洽校验(每个字形的行 argmax 必须等于 其被指派目标),8/8 恢复,且该校验仍能拦住真正的"图字不符"。

8.3 下一步(唯一有价值的实验)

按 §7.5 做单变量实验,不要再用环境假说:

  1. captcha 域带上成功样本的 uin/skey/qcmainCSRFToken/intl cookie,其余不变;
  2. 登录态真页面触发 vs 未登录触发。

二选一闭合即可判定 H1(页面上下文)还是 H2(自动化检测)。