结论 (已知 #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 + 取槽位表两条路)
200 lines
21 KiB
Markdown
200 lines
21 KiB
Markdown
# 反 VM 密钥还原 & 纯算交付 进度文档
|
||
|
||
> 目标: **算法 100% 纯 Python** —— collect 的明文组装 / 轨迹 / 时间戳 / XTEA 加密 / 编码
|
||
> 全部在 Python 内完成; ~~Node 只保留一个用途: 从当前 tdc.js 提取本次构建的 XTEA 密钥字~~
|
||
> **2026-10-11 起 Node 连提取都不再需要**(纯 Python 静态还原,见已知 #19~#22;Node 仅作兜底)。
|
||
> 依据: AGENTS.md §2.3(拒绝补环境) / §2.4(分层交付) / §3.3(字节码 VM) / §4.3(运行时捕获) / §5.8(标定式交付)。
|
||
> 最后更新: 2026-10-11 (纯 Python XTEA 密钥静态提取落地 + opcode 表按构建推导)
|
||
|
||
---
|
||
|
||
## 〇、路线变更(重要)
|
||
|
||
**已废弃「补环境」路线。** 曾经实现过 3 个"在伪造/真实环境里跑 tdc.js 产出 collect"的后端
|
||
(`node` VM 沙箱 / `browser` CDP Dispatch / `probe` 深度 Proxy),现已从代码中**全部移除**,
|
||
只保留纯算单一路径 (`python_keyed` / `python`)。
|
||
|
||
废弃依据(本项目自己的实验,见 `ANALYSIS.md` §7):
|
||
|
||
| 变量 | 结果 |
|
||
| --- | --- |
|
||
| collect 变体(空 / 垃圾 / 截断 / 真实环境值 / 真实轨迹 dt)| 全部 `errorCode=12` (E6) |
|
||
| 遥测后端 python / python_keyed / node / browser | 全部 12 |
|
||
| 出口 IP(4 个不同省份住宅代理)| 全部 12 |
|
||
| 静默 9 分钟 + 单发一次 | 仍 12 |
|
||
|
||
即"环境 / 指纹 / IP / 时序"都**不是** 12 的自变量 → 补环境没有收益,只会引入大量手写桩的脆弱面。
|
||
|
||
---
|
||
|
||
## 一、已知(附证据 + 确信度)
|
||
|
||
| # | 结论 | 证据 | 确信度 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | `collect` 的加密算法 = **教科书 XTEA**(32 轮,`delta=0x9E3779B9`,sum 取 mod 2³²) | 用提取密钥做纯 Python 加密,与真实 tdc.js 产物**前 48 字节逐字节一致**(`f6a24e3e4fb5dd2b...`) | 确证 |
|
||
| 2 | 早期带加盐的写法与该算法**等价**,只是把两个密钥字折叠了:`K' = [k0, k1+salt1, k2+salt2, k3]` | 标准 XTEA + `K'=[1448563793,1850559083,1733513813,1364613714]` 解密样本 collect 得到 `{"cd":[[],"1920-1080..."` | 确证 |
|
||
| 3 | **密钥按构建固定,不随运行变化** | 同一 tdc.js 连续 3 次运行,密文首块均为 `f6a24e3e4fb5dd2b` | 确证 |
|
||
| 4 | 密钥不在脚本字面量里 | 正则清点全脚本 ≥7 位数字,无一个是密钥字 | 确证 |
|
||
| 5 | 密钥由 VM 运行时通过 ADD 算出:`(sum + K[j])` 有一操作数等于 `r*DELTA` | 插桩 ADD opcode 后 Top4 候选即 `K'` | 确证 |
|
||
| 6 | opcode 表的**栈变量名每份构建都不同**(样本 `g` / 雨云 `j` / 1ms `M`) | 同一正则在三份脚本分别命中;写死 `g` 时实时脚本匹配 0 | 确证 |
|
||
| 7 | tdc.js 至少两代明文布局,**新版字段序随构建变化** | 旧版首段 `{"cd":[[],...`;雨云实时 `{"cd":[1,[],...`;1ms 实时 `{"cd":[1791712995,0,...` | 确证 |
|
||
| 8 | 宽松 aid 下服务端**不校验** `collect` 与 `eks` 的同源性 | 雨云 aid + 硬编码旧密钥 + 旧版明文 → `errorCode=0` + 真实 ticket | 确证(仅宽松 aid,勿外推) |
|
||
| 9 | 「Node 跑密钥 + Python 全算法」端到端可用 | 雨云 aid 纯 Python 出包 → `errorCode=0` | 确证 |
|
||
| 10 | **XTEA 密钥字是 4 个「ASCII 打包的 4 字符串」**(每字 = 4 个可打印字符按大端拼成 32bit)| `core_js/tdc_sample.js` 的密钥 `[1818510928,1381395306,1245924197,1768384072]` 大端解码 = `ldFP`/`RVkj`/`JCKe`/`igfH`;root 样本 = `VWTQ`/`nMJk`/`gSRU`/`QVZR` | 确证 |
|
||
| 11 | 密钥字**既不是立即数、也不是字面字符串**(无论明文、大端字节序列、base64 都不在 tdc.js/字节码里)| 立即数 1.3e9~1.9e9 区间为空;`ldFPRVkjJCKeigfH` 及其 base64 均搜不到 | 确证 |
|
||
| 12 | 密钥数组在 XTEA 轮里通过**寄存器间接寻址**读取,且经闭包捕获传入 | 反汇编 `reg7[(sum>>>11)&3]`;`@09463 CREATE_CLOSURE target=@9109 caps=[r7=r8]` | 确证 |
|
||
| 13 | 轮加盐**每次构建都不同**(但都是可见立即数)| 本构建 = `723726`(j==2) / `855311`(j==3);台账另一构建 = `2829068` / `328986` | 确证 |
|
||
| 14 | **密钥字的构造算法(已完全还原)**:`word = Σ ((charCodeAt(源串,k) + off_i) & 0xFF) << (8k)`,4 个源串各自一个**独立偏移常量**,常量是打包点的立即数 | 神谕二分 + DUMP_PC 取到源串;反汇编读出常量:<br>`("IA_t", -8)` `("9:%!", +49)` `("nTLS", -9)` `("7XYf", +3)` | 确证 |
|
||
| 15 | **有效密钥 = base 字 + 两个轮加盐** | `real[0]-base[0]=855311`、`real[3]-base[3]=723726`(XTEA 轮里的立即数) | 确证 |
|
||
| 16 | **纯 Python 可完整复算密钥,与 `extract_xtea_key` 逐字一致** | `base=[1817655617,1381395306,1245924197,1767660346]` → 折盐 → `[1818510928,1381395306,1245924197,1768384072]` == Node 结果 | 确证 |
|
||
| 17 | 密钥链路的运行时位置(神谕实测) | 逐字首次出现 PC:7740/7526/7895/8852(均 reg9);数组组装 @8886(reg7);进 XTEA 闭包 @9030 `CALL_FUNC[2]`;入口 @9109 | 确证 |
|
||
| 18 | `extract_xtea_key`(Node)本身正确 | 其密钥把 probe 的真实 collect 解成 `{"cd":[0,10,"top",[360,360],"MacIntel",1,"undefined?rand…` | 确证 |
|
||
| 19 | **opcode 编号每份构建整体重排**(不是只有变量名/加盐在变) | 同一源码的 pc0 `SET_STACK_LEN` 在 6 份构建里 = 53/19/32/57/55/1;`JUMP` = 60/17/42/58/21/2;core 表去解其它构建会在 pc0 就 `RET_TRUE` 假象 | 确证 |
|
||
| 20 | **opcode 表可从该构建解释器源码推导**:主循环 `X[Y[Z++]]()` 的 `X` 就是 handler 数组,下标即 opcode;handler 规范化指纹可跨构建精确匹配 | 6/6 份构建 **58/58 handler 精确匹配**、0 未识别(`tcaptcha/tdc_opcodes.py`)| 确证 |
|
||
| 21 | **密钥字闭包只写"被 XTEA 闭包捕获的那个数组"**;脚本同时算了两套 4 字(真 + 诱饵),靠**两层捕获链**区分:字闭包捕获的 B 帧寄存器 `w_src`,经"定义 B 的那个闭包"的 caps 映射到 G 帧后须 == XTEA 闭包捕获的寄存器 | core/root/1ms×3/实时新抓 全部选对;诱饵组按同规则被排除 | 确证 |
|
||
| 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。⚠️ 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 与"请求侧保真度"无关**(原结论"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()` 虽然回填了新轨迹/时间戳,但**槽位序是那一次的** | 确证 |
|
||
|
||
---
|
||
|
||
## 二、未知(附假设 + 确信度)
|
||
|
||
| # | 未知 | 当前假设 | 确信度 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | 1ms aid=195672344 为何在答案正确时返回 `errorCode=12` | 需要"真实业务会话/真实验证码页面上下文"(登录态),而非环境指纹 | **中(未做单变量实验)** |
|
||
| 2 | 新版明文字段语义(哪一位是时间戳/指纹/轨迹) | 时间戳是 `1[6-9]\d{8}` 大整数;轨迹是 `[x,y,t]` 三元组 | 中高 |
|
||
| 3 | 明文里 `[360,360]`、`"2560-1440-1345-24-*-*-| -*"` 的作用 | 屏幕/窗口指纹串 | 中 |
|
||
|
||
---
|
||
|
||
## 三、已排除的假设(负结果也是资产,§5.7)
|
||
|
||
| 假设 | 否定证据 |
|
||
| --- | --- |
|
||
| 密钥每次运行随机 | 同一脚本 3 次运行首块完全一致 |
|
||
| 密钥可从 `eks` 推导 | 34 种推导 + 40 种偏移切片全部失败 |
|
||
| 密钥是脚本里的字面量 | 全脚本 >1e9 的数只有 3 个 |
|
||
| 用 `Array.prototype.push` 打桩能抓密钥 | 算术 opcode 用 `g[g.length-2]=...` 直接下标赋值 |
|
||
| 实时构建复用旧版明文布局 | 旧版校验器对两份实时脚本全部失败 |
|
||
| **换出口 IP 能解决 1ms 的 12** | 4 个不同省份住宅 IP 全部 12 |
|
||
| **换遥测引擎能解决 1ms 的 12** | python / python_keyed / node / browser 全部 12 |
|
||
| **长静默窗口能解决 1ms 的 12** | 静默 9 分钟 + 单发 → 仍 12 |
|
||
| **"补环境"能解决 1ms 的 12** | 同上三条;环境不是自变量 |
|
||
| **captcha 域带腾讯云登录 cookie 能解决 12 (H3)** | 单变量 A/B:带/不带均 12(`scripts/experiment_h3_cookie_ab.py`,2026-10-11)|
|
||
| 轨迹 dt 太小是 12 的"直接原因" | 换成真实 ms 分布后仍 12(E6) |
|
||
| **opcode 数值是跨构建固定的 VM 常量** | 6 份构建实测 pc0 的 `SET_STACK_LEN`/`JUMP` 数值全不同(见已知 #19);`tools/tdc_disasm.py` 里固化的表**只对 core_js 有效**,已改为"可注入按构建推导的表" |
|
||
| **"密钥字必须靠 Node 插桩才能拿到"** | `tcaptcha/tdc_key.py` 纯 Python 零 Node 复算,6/6 构建与 Node 逐字一致(已知 #22)→ Node 降级为兜底 |
|
||
| **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~~ ⚠️ **该否定实验本身有缺陷(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 与保真度无关 |
|
||
|
||
---
|
||
|
||
## 四、当前实现(纯算单一路径)
|
||
|
||
```
|
||
TencentCaptchaClient(telemetry_mode="python_keyed") # 默认
|
||
├─ extract_xtea_key(tdc.js) **纯 Python 静态还原** (tcaptcha/tdc_key.py, 零 Node)
|
||
│ ├─ tdc_opcodes.py 按构建从解释器源码推导 opcode 表 (编号每份都重排)
|
||
│ ├─ tools/tdc_disasm.py 可达性 CFG 反汇编 (支持注入按构建的表)
|
||
│ ├─ 定位 XTEA 闭包 (delta 常数) -> 两层捕获链选出真密钥数组
|
||
│ └─ 读出 4 个字 (源串+偏移+下标) + 折入轮加盐
|
||
│ (Node 插桩版本保留为**兜底**: 纯 Python 失败时自动回落)
|
||
├─ generate_collect_success() 模板回填: 轨迹 / 当前时间戳 / 随机盐 → XTEA → Base64 (单层 URL 编码)
|
||
└─ extract_eks(tdc.js) 通用正则取最长 Base64 候选 (不再写死变量名)
|
||
```
|
||
|
||
* 纯 Python 提取器: `tcaptcha/tdc_key.py` (算法) + `tcaptcha/tdc_opcodes.py` (opcode 表推导)
|
||
* CLI: `tools/tdc_key_pure.py [tdc.js] [-v] [--opcodes]`
|
||
* 模板: `tcaptcha/models/success_collect_template.b64`(真实成功样本明文)
|
||
* 加密: `tcaptcha/crypto.py` 中**唯一**的教科书 XTEA
|
||
* Node 现存用途: 仅 `tdc_worker.js` 兜底提取密钥(主路径已不需要)
|
||
|
||
---
|
||
|
||
## 五、下一步(可被下一轮直接执行)
|
||
|
||
0. ~~**静态提取 XTEA 密钥(去掉最后一个 Node 依赖)**~~ —— **已完成**(已知 #19~#22):
|
||
`tcaptcha/tdc_key.py` 纯 Python 零 Node,6/6 构建与 Node 逐字一致。
|
||
残留增强项(非阻塞):① 若服务端改了 handler 语义,`tdc_opcodes.derive_opcode_table`
|
||
会抛"指纹未识别",此时应更新参考指纹而非改定位逻辑;② 抗"多套诱饵数组 >2 层捕获"的
|
||
兜底(当前两层已覆盖实测全部 6 份)。
|
||
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
|
||
.venv/bin/python - <<'PY'
|
||
from tcaptcha.client import TencentCaptchaClient, KIND_TEXT_CLICK
|
||
c=TencentCaptchaClient(aid="190758559",
|
||
entry_url="https://mliev.login.wang/auth/#/oidc/authorize",
|
||
referer="https://mliev.login.wang/", wb="1", accept_kinds=(KIND_TEXT_CLICK,))
|
||
print(c.solve()["raw_response"].get("errorCode")) # 期望 0
|
||
c.close()
|
||
PY
|
||
```
|
||
3. **补齐新版明文语义 + 槽位表**:语义(哪一槽是 UA / 屏幕串 / WebGL / 轨迹 / 时间戳)已从
|
||
已知 #30 的真机明文基本对齐;**关键是每份构建的槽位序**(已知 #31)——
|
||
见上面 1(b)。分析入口:`scripts/analyze_collect_layout.py`。
|
||
4. **实现 2408 图标点选**:按 1ms 图标类别重训 `yolo_detector` + `icon_classifier`。
|
||
5. **实现滑块 (2401/2402)**:模板匹配找缺口 + 生成滑动轨迹(1ms 大幅下发滑块,见抓包)。
|
||
|
||
|
||
---
|
||
|
||
## 六、复验入口
|
||
|
||
```bash
|
||
.venv/bin/python -m tcaptcha.tests.test_crypto_core # XTEA 往返 + encrypt_string 一致性
|
||
.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)
|
||
```
|