feat(诊断): 用真机复现解出 1ms 成功会话的 collect 明文 —— 归因改写为「cd 槽位序按会话随机」
结论 (已知 #29~#32, 推翻旧 #25/#27 与"collect 逐字节同构"这条否定实验):
* 同一个 tdc.js URL 每次请求返回不同构建 (4/4 md5 + XTEA 密钥全不同)
* 同会话同时抓 tdc.js + verify -> 解密出真机 collect 明文 (784B/38 槽, 可打印率 .991)
* 5 份同日样本 (真机 cap2 / 签到成功-1 / 点选 / login_checkin / 仓库模板) 全部 38 槽、
同一批取值、同一个 sd, 但槽位序 5/5 互不相同
* 仓库在用的模板槽位序 == 签到成功-1 那一次; generate_collect_success() 只回填
轨迹/时间戳/entry_url, 槽位序仍是那一次的
=> 头号可证伪假设 H-LAYOUT: 服务端按"本次会话构建"的槽位表解释 cd, 用别的会话的槽位序
会被读成自相矛盾的环境 -> 12 (答案错理应是 50/9)。这也解释了为何换 IP / 换遥测后端 /
换传输指纹 / 换 cookie / 长静默全都无效 —— 它们都不动槽位序。
新增:
* scripts/analyze_collect_layout.py —— collect 槽位序对比 (复现 #31)
* tcaptcha/slider_solver.py + test —— 滑块缺口求解 (ZNCC, 真机首轮命中 ret=0)
* tcaptcha/icon_click_solver.py + test —— 图文点选形状模板求解
* scripts/bridge_1ms_captcha.py —— 真机桥接三模式 (slider/icon/text) + --probe 题型侦察
* tools/cdp_eval.js / cdp_click.js / cdp_shot.js / cdp_net_capture.js / cdp_capture.js
* selftest 纳入 icon_click / slider 两个套件
账本: §一 修正 #25/#27、新增 #29~#32; §三 标注"collect 逐字节同构"否定实验有缺陷;
§五 重写 12 的归因与下一步 (决定性实验 A + 取槽位表两条路)
This commit is contained in:
@@ -55,10 +55,14 @@
|
||||
| 22 | **纯 Python 静态提取 XTEA 密钥成功,零 Node** | `tcaptcha/tdc_key.py`:6/6 份构建与 Node 插桩 `extract_xtea_key` **逐字一致**;含 2026-10-11 现场新抓的 `tdc.js`(`har/1ms_dianxuan/live/tdc_fresh.js`,77967B,密钥 `[1397244740,1799507557,1399740484,1516463189]`)| 确证 |
|
||||
| 23 | **纯 Python 密钥能解密 1ms 真实成功样本的 collect** | 用 `har/签到成功-1.har` 里那次 ec=0 之前的 tdc.js 提密钥 → 解出该次 collect 明文 `{"cd":[2560,"MacIntel",...` 1353B;且与 `models/success_collect_template.b64` **逐字节同构**(仅轨迹/时间戳/盐被回填)| 确证 |
|
||||
| 24 | **1ms 真实成功链路已完整解析** | prehandle(带腾讯云登录 cookie) → tdc.js → getcapbysig → verify **ec=0**(ticket `tr03…`) → `POST /api/v1/mall/checkin`,body = `{captchaTicket:{provider,data:{ticket,randstr}}, sessionUuid, identity_context}`;`/api/v1/captcha/params` 的 body 带 `identity_context`(`emctx1.<b64>.<sig>`) | 确证 |
|
||||
| 25 | **真实浏览器对 verify 从不吃 12** | `点选.har`(同一 cookie/同一 aid) 三次 verify = 50/50/9;`签到成功-1.har` = 0。而我们**任何**客户端形态都是 12 → 12 是"请求方身份"判定,不是答案/载荷 | 确证 |
|
||||
| 25 | **真实浏览器对 verify 从不吃 12** | `点选.har`(同一 cookie/同一 aid) 三次 verify = 50/50/9;`签到成功-1.har` = 0。而**当时的**客户端形态都是 12。⚠️ 2026-10-11 晚修复:该结论把"载荷"只理解成"答案",漏掉了 **`cd` 槽位序**(见已知 #31/#32),表述已过强 | 证据成立 / 归因已修正 |
|
||||
| 26 | **纯算链路在真实 aid 上端到端成功出 ticket**(2026-10-11 实测)| 用新抓包 `har/原始抓包/登录抓包-点选.har` 的 aid=**190758559**(mliev.login.wang 短信登录),本客户端 `solve()` 返回 `errorCode=0` + 真 ticket:<br>`tr03lxdviLWtn4uXGjWjmrLQ` / `tr03A5zTSQ9bCog51sDm` / `tr039KeeQxXX-OdduJoY`。**关键:最原始的 httpx + 无 cookie 也 0** | 确证 |
|
||||
| 27 | **1ms 的 12 是该 aid 自身的风控策略,不是我们的请求侧差异** | 同一天同一台机器:aid 190758559 用 httpx/无 cookie/无头保真 → 0;aid 195672344 用 curl_cffi(Chrome 指纹)+真机 cookie+逐 endpoint 头保真 → 仍 12。→ 请求侧已无自变量(§2.1 第 0 问:服务端就是不放行这个 aid 来自非真机页面的会话)| 确证 |
|
||||
| 28 | 新抓包暴露另一套业务接入(可复现靶子)| `har/原始抓包/登录抓包-点选.har`:`POST /api/captcha/params`(scene=sendSms + fingerprint/userAgent/platform/language/timezone) → 腾讯点选 → `POST /api/oidc/send-register-verification-code`(`{type,receiver,captchaTicket:{provider,data:{ticket,randstr,fingerprint}}, sessionUuid}`) | 确证 |
|
||||
| 27 | **1ms 的 12 与"请求侧保真度"无关**(原结论"12 是该 aid 的服务端策略"已过强) | 成立部分:aid 190758559 上未做任何保真的 httpx + 无 cookie → 0;aid 195672344 上 curl_cffi + 真机 cookie + 逐 endpoint 头保真 + 传输指纹 → 仍 12。→ **请求侧细节不是自变量**(§三 反证仍有效)。<br>不成立部分:把 12 归因为"服务端不放行该 aid"缺少机制;现在有**更强且可证伪**的解释 → 见 #31/#32(collect 槽位序与本次会话构建不匹配)| 部分成立 / 归因待 #31 验证 |
|
||||
| 28 | 新抓包暴露另一套业务接入(可复现靶子)| `har/原始抓包/登录抓包-点选.har`:`POST /api/captcha/params`(scene=sendSms + fingerprint/userAgent/platform/language/timezone) → 腾讯点选 → `POST /api/oidc/send-register-verification-code`(`{type,receiver,captchaTicket:{provider,data:{ticket,randstr,fingerprint}},sessionUuid}`) | 确证 |
|
||||
| 29 | **同一个 `tdc.js` URL 每次返回的构建都不同**(不是"按构建固定",而是**按请求随机化**)| 连抓 4 次 `tdc.js?app_data=…&t=…`:md5 = `b6db2555…` / `032afea5…` / `1b8b7d4d…` / `b1ad4bdd…`,四份长度 77917~78013B、**XTEA 密钥 4 个全不同**。→ 结论 #3"密钥按构建固定"要限定为"**同一份已下载的脚本**内固定",不能外推到"重新请求还是同一份" | 确证 |
|
||||
| 30 | **真机某次成功会话的 collect 明文已完整解出** | 抓包同时拿到**同会话**的 `tdc.js`(`app_data=1793575418729725952`)与 `cap_union_new_verify` 的 collect,用该构建密钥解密:784B 密文 → 可打印率 0.991,得<br>`{"cd":["",[360,360],0,"Mozilla/5.0 (Macintosh…) Chrome/155…","https://turing.captcha.gtimg.com/1/template/drag_ele.html?rand=…",["zh-CN","zh"],641547489,1791732001,"GgoAAA…png…",2560,[[34,96,3284],[154,50,370],…],1023,1440,"+08",1,0,"ANGLE (Apple, ANGLE Metal Renderer: Apple M4, …)",10,"",1,1986013704,"iframe","MacIntel",0,1791733986,"1440-2560-2530-24-*-*-|-*",1791733982,"Google Inc. (Apple)",24,"",[],0,"https://1ms.run/console/personal/benefits/check-in","98k",2,0,0,"UTF-8"],"sd":{"od":"C","ft":"qf_7Pf__H"}}`<br>(`tdc.js?app_data=…&appid=<aid>`:真机 URL 带 `appid`,我们客户端不带)| 确证 |
|
||||
| 31 | **`cd` 的槽位顺序按会话/构建随机化(不是按版本固定)** | 5 份同日样本(真机 cap2 / `签到成功-1` / `点选` / `login_checkin` / 仓库模板)全部 `len(cd)=38`、**同一批取值**、**同一个 `sd`**,但**槽位顺序 5/5 互不相同**(两两不同槽位 26~29 个)。复现:`.venv/bin/python scripts/analyze_collect_layout.py --json <cap>.json` | 确证 |
|
||||
| 32 | **仓库在用的 collect 模板 = `签到成功-1.har` 那一次的槽位序** | 模板指纹与 `签到成功-1#0` **逐槽一致**;与晚些时候的真机会话 29 个槽不同。→ `generate_collect_success()` 虽然回填了新轨迹/时间戳,但**槽位序是那一次的** | 确证 |
|
||||
|
||||
---
|
||||
|
||||
@@ -92,7 +96,7 @@
|
||||
| **H3(captcha 域腾讯云登录 cookie)能解决 12** | 用**抓包里的真实 cookie 值**(uin/skey/qcmainCSRFToken/intl)重跑,仍 12(`scripts/experiment_h3_cookie_ab.py --only b`,2026-10-11)→ 与早期结论一致,H3 确凿排除 |
|
||||
| **HTTP/TLS 传输指纹是 12 的自变量** | `curl_cffi`(impersonate=chrome: JA3/JA4 + HTTP/2 + Chrome 头序) + 真实 cookie,仍 12(`scripts/experiment_transport_ab.py --only b`)→ 传输层排除 |
|
||||
| **请求头保真度是 12 的自变量** | 按真实抓包逐 endpoint 保真:资源 GET 用 `Sec-Fetch-Dest=script/image` + `Mode=no-cors` + `Storage-Access=active` + cookie,POST 用 `empty/cors` + Origin 且**不带** cookie;仍 12 |
|
||||
| **collect 明文结构/取值是 12 的自变量** | 我们的 collect 明文与 1ms 真实成功样本**逐字节同构**(同 1353B、同字段序、同取值,仅回填轨迹/时间戳/盐),仍 12 |
|
||||
| **collect 明文结构/取值是 12 的自变量** | ~~我们的 collect 明文与 1ms 真实成功样本逐字节同构(同 1353B、同字段序、同取值,仅回填轨迹/时间戳/盐),仍 12~~ ⚠️ **该否定实验本身有缺陷(2026-10-11 晚推翻)**:所谓"同字段序"只是与**模板来源的那一次**样本比,而 `cd` 槽位序**按会话随机**(已知 #31),所以它没检验到真正会变的维度。修正后重开为:**"cd 槽位序必须与本次会话构建一致"是 12 的头号可疑自变量**(已知 #32)|
|
||||
| **H5:1ms 后端用 `identity_context` 把会话标记为可信** | 拿抓包里**签名过的** `identity_context`(599B `emctx1.*`) 先调 `POST /api/v1/captcha/params`(响应与不带时**完全相同**,无额外 token)再解题,仍 12 → H5 排除 |
|
||||
| **"请求侧保真度不够"这一整类** | 直接反证:aid 190758559 上 **未做任何保真的 httpx + 无 cookie** 也拿到 ticket(0);即请求侧细节本就足够,12 与保真度无关 |
|
||||
|
||||
@@ -127,14 +131,36 @@ TencentCaptchaClient(telemetry_mode="python_keyed") # 默认
|
||||
残留增强项(非阻塞):① 若服务端改了 handler 语义,`tdc_opcodes.derive_opcode_table`
|
||||
会抛"指纹未识别",此时应更新参考指纹而非改定位逻辑;② 抗"多套诱饵数组 >2 层捕获"的
|
||||
兜底(当前两层已覆盖实测全部 6 份)。
|
||||
1. **拿下 1ms 的 12 —— 结论已闭合:不是我们能修的请求侧问题**(2026-10-11)
|
||||
* 证据链:① 同一份代码在 aid **190758559** 上 `errorCode=0` 出真 ticket(且 **httpx+无 cookie** 也 0);
|
||||
② 1ms aid **195672344** 在 cookie / 传输指纹 / 头保真 / collect 内容 / `identity_context`
|
||||
全部单变量排除后**恒为 12**;③ 真机同 aid 只出 50/9/0。
|
||||
* 即 **1ms 这个 aid 的风控策略不放行"非真机页面"的会话**(§2.1 第 0 问:服务端就是判这个)。
|
||||
* 因此只剩两条路,且都不在"协议纯算"范围内:
|
||||
(a) **桥接保底**(§2.4 ③):在**真机页面**里跑一次拿 ticket,再走纯算业务提交;
|
||||
(b) 换一个风控更宽的入口/aid(如 190758559 那种),或与 1ms 沟通白名单。
|
||||
1. **拿下 1ms 的 12 —— 归因已改写(2026-10-11 晚,见已知 #29~#32)**
|
||||
旧归因"1ms 就是不放行非真机页面的会话"**证据不足**,已被下面这条更可证伪的链取代:
|
||||
|
||||
* 硬事实:
|
||||
① 同一个 tdc.js URL **每次请求都返回不同构建**(4/4 md5 与密钥全不同,已知 #29);
|
||||
② 真机某次成功会话的 collect 明文已**完整解出**(已知 #30);
|
||||
③ 5 份同日样本的 `cd` 都是 38 槽、同一批值、同一个 `sd`,但**槽位序 5/5 互不相同**(已知 #31);
|
||||
④ 仓库在用的模板槽位序 == `签到成功-1.har` 那一次(已知 #32),而 `generate_collect_success()`
|
||||
只回填轨迹/时间戳/entry_url —— **槽位序仍是那一次的**。
|
||||
|
||||
* **H-LAYOUT(头号假设,可证伪)**:服务端按"**本次会话构建**"的槽位表解释 `cd`。
|
||||
我们用**别的会话的槽位序**发出的 collect 会被读成"自相矛盾的环境"
|
||||
(如 `cd[1]` 该是 `[360,360]` 却是 `"MacIntel"`)→ 风控判 **12**,与答案对错无关
|
||||
(答案错理应是 50/9)。
|
||||
这条同时解释了旧账本的所有负结果:换 IP / 换遥测后端 / 换传输指纹 / 换 cookie / 长静默
|
||||
**都不动槽位序**;而宽松 aid(190758559 / 雨云)**不交叉校验布局**,所以 0。
|
||||
|
||||
* 下一步三件事(按顺序做):
|
||||
(a) **决定性实验 A(最便宜,先做)**:真机页面里 hook 掉 `cap_union_new_verify` 的发送,
|
||||
让 widget 把 collect **组好但不发**;再把这一包原样交给 **Python** 发。
|
||||
若 Python 也 0 → "谁在发 HTTP"彻底无关,12 只剩"包内容"一个自变量 → 坐实 H-LAYOUT。
|
||||
若 Python 仍 12(而同一包在浏览器里是 0)→ 说明还有"会话↔连接"绑定,需换实验 A'。
|
||||
(b) **拿到本次构建的槽位表**(二选一):
|
||||
① 神谕路(快):`tools/tdc_probe.js` / `tdc_keytrace.js` 在 JS 运行时 hook XTEA 输入,
|
||||
dump 出**本次构建的明文骨架** → 得槽位序;Python 按该序回填。
|
||||
② 纯算路(慢,但符合项目"零环境"目标):用 `tools/tdc_disasm.py` / `tdc_lift.py`
|
||||
反汇编构造 `cd` 的字节码段,还原 "push 顺序 ↔ 语义字段" 映射表。
|
||||
(c) **验证**:collect 按 (b) 的槽位序重排后重发 195672344,期望 **12 → 0**。
|
||||
注意:既然 tdc.js 按请求随机(#29),Python 客户端必须用**它自己那次 prehandle 拿到 sid
|
||||
之后请求的那份 tdc.js** 的槽位表,不能用缓存/模板。
|
||||
2. **可复现靶子(新增资产)**:aid 190758559 / `mliev.login.wang` 短信登录 —— **可反复测**,
|
||||
用来回归"纯算链路是否完好"(1ms 每天只能试一次,不适合做回归):
|
||||
```bash
|
||||
@@ -147,7 +173,9 @@ TencentCaptchaClient(telemetry_mode="python_keyed") # 默认
|
||||
c.close()
|
||||
PY
|
||||
```
|
||||
3. **补齐新版明文字段语义**:对照 10+ 份实时模板做差分。
|
||||
3. **补齐新版明文语义 + 槽位表**:语义(哪一槽是 UA / 屏幕串 / WebGL / 轨迹 / 时间戳)已从
|
||||
已知 #30 的真机明文基本对齐;**关键是每份构建的槽位序**(已知 #31)——
|
||||
见上面 1(b)。分析入口:`scripts/analyze_collect_layout.py`。
|
||||
4. **实现 2408 图标点选**:按 1ms 图标类别重训 `yolo_detector` + `icon_classifier`。
|
||||
5. **实现滑块 (2401/2402)**:模板匹配找缺口 + 生成滑动轨迹(1ms 大幅下发滑块,见抓包)。
|
||||
|
||||
@@ -161,8 +189,11 @@ TencentCaptchaClient(telemetry_mode="python_keyed") # 默认
|
||||
.venv/bin/python -m tcaptcha.tests.test_xtea_key # XTEA + 密钥提取 (逐字节对拍, Node 参与)
|
||||
.venv/bin/python -m tcaptcha.tests.test_tdc_key # **纯 Python** 密钥提取 (6 份构建, 零 Node)
|
||||
.venv/bin/python -m tcaptcha.tests.test_glyph_solver # 文字点选求解 (8 张真实样本)
|
||||
.venv/bin/python -m tcaptcha.tests.test_icon_click_solver # 图文点选求解 (真机样本)
|
||||
.venv/bin/python -m tcaptcha.tests.test_slider_solver # 滑块缺口求解 (真机样本)
|
||||
.venv/bin/python -m tcaptcha.selftest # 全部
|
||||
|
||||
.venv/bin/python tools/tdc_key_pure.py -v har/1ms_dianxuan/live/tdc_fresh.js # 单份构建 + 推导过程
|
||||
.venv/bin/python tools/tdc_key_pure.py --opcodes tcaptcha/tdc_sample.js # 打印按构建推导的 opcode 表
|
||||
.venv/bin/python scripts/analyze_collect_layout.py --json /tmp/cap.json # collect 槽位序对比 (已知 #31)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user