# 毫秒 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.."} 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= &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=&t=", "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 的 236~248 字符 (= 176~184 字节),推测是用服务端公钥包裹的会话密钥,服务端用它解密 `collect`。 * 早期用写死变量名的正则(`window.KQnlRHY...`)提取,得到 236 字符,与真实浏览器的 248 不一致。 现已改为**通用**匹配(任取最长的 `window.<随机名>=''`),在 2026-10-11 的实时构建上 稳定取到 **248 字符**,与真实浏览器观测一致(见 E8 那次运行 `eks_len=248`)。 `eks` 的来源与 `collect` 必须**同源**(同一份 tdc.js、同一次执行),不可混用。 ### 4.4 `ans` —— 点击坐标(明文,无需签名) ```json [{"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(自动化检测)。