做了什么: - 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)
25 KiB
毫秒 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 无法确定的内容(明确声明,不虚构)
- XTEA 会话密钥的派生过程:tdc.js 每次加载都会重新混淆,密钥常量未以字面量出现
(
1448563793等 4 个常量在 tdc.js 中检索不到),需要源码级插桩才能完全还原。 本实现改为执行真实 tdc.js,绕过该问题。 eks的加密算法与字段布局:未逆向,直接复用 tdc.js 产出。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:
- HSV 阈值分割(H∈[8,34] S>85 V>145)+ 形态学闭运算 + 13px 膨胀把偏旁并成整字
- 连通域得到包围盒,按面积/长宽比过滤,得到候选字形
- 掩膜 → 白底黑字 → 3 倍放大,交给 OCR 集成打分
- 得分 = 每个引擎在 CTC 各时间步上目标字概率的最大值,取多预处理变体最大值后加权求和
- 剔除全部目标字得分都 < 0.02 的伪字形(照片暖色区域会误检)
- 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 ✅
这一条同时证明了两件事:
- 纯 Python 交付形态完全有效,服务端不校验 collect 与 eks 的同源性;
- 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.jscaptureKey) - 文字点选求解器 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 做单变量实验,不要再用环境假说:
- captcha 域带上成功样本的
uin/skey/qcmainCSRFToken/intlcookie,其余不变; - 登录态真页面触发 vs 未登录触发。
二选一闭合即可判定 H1(页面上下文)还是 H2(自动化检测)。