# 反 VM 密钥还原 & 纯算交付 进度文档 > 目标: **算法 100% 纯 Python**,Node 仅用于"跑出密钥 + 一次性标定明文模板"。 > 依据: AGENTS.md §2.4 分层交付 / §3.3 字节码 VM 还原 / §4.3 运行时捕获 / §5.8 标定式交付。 > 最后更新: 2026-10-11 --- ## 一、已知(附证据 + 确信度) | # | 结论 | 证据 | 确信度 | | --- | --- | --- | --- | | 1 | `collect` 的加密算法 = **教科书 XTEA**(32 轮,`delta=0x9E3779B9`,sum 取 mod 2³²) | 用提取密钥做纯 Python 加密,与真实 tdc.js 产物**前 48 字节逐字节一致**(`f6a24e3e4fb5dd2b...`) | 确证 | | 2 | 原 `crypto.py` 里带加盐的写法与该算法**等价**,只是把两个密钥字折叠了:`K' = [k0, k1+salt1, k2+salt2, k3]` | 标准 XTEA + `K'=[1448563793,1850559083,1733513813,1364613714]` 解密样本 collect 得到 `{"cd":[[],"1920-1080..."` | 确证 | | 3 | **密钥按构建固定,不随运行变化** | 同一 tdc.js 连续 3 次运行,密文首块均为 `f6a24e3e4fb5dd2b`(首段明文是常量) | 确证 | | 4 | 密钥不在脚本字面量里(全脚本只 3 个数 >1e9,无一个是密钥字) | 正则清点全部 ≥7 位数字 | 确证 | | 5 | 密钥由 VM 在运行时通过 ADD 算出:`(sum + K[j])` 的两操作数必有一个等于 `r*DELTA` | 插桩 ADD opcode 后,样本 Top4 候选恰好是 `K'`;实时构建同样只出现 4 个高频道候选 | 确证 | | 6 | opcode 表的**栈变量名每份构建都不同**(样本 `g` / 雨云实时 `j` / 1ms `M`) | 同一正则在三份脚本上分别命中,写死 `g` 时实时脚本匹配数为 0 | 确证 | | 7 | tdc.js 至少两代明文布局,**新版字段序还随构建变化** | 旧版首段 `{"cd":[[],"1920-1080..."`;雨云实时 `{"cd":[1,[],"Apple M1",24,...`;1ms 实时 `{"cd":[1791712995,0,...` | 确证 | | 8 | 服务端**不校验** `collect` 与 `eks` 的同源性(宽松 aid) | 雨云 aid + 硬编码旧密钥 + 旧版明文 → `errorCode=0` + 真实 ticket | 确证 | | 9 | 「Node 跑密钥 + Python 全算法」端到端可用 | 雨云 aid 纯 Python 出包 → `errorCode=0`,`ticket=tr03IsAVmQyixMlGay--3wugC_...` | 确证 | --- ## 二、未知(附假设 + 确信度) | # | 未知 | 当前假设 | 确信度 | | --- | --- | --- | --- | | 1 | 1ms aid=195672344 为何在答案正确时返回 `errorCode=12` | 该 aid 启用了更严格风控,要求会话具备**真实验证码页面上下文** | 中 | | 2 | 新版明文字段语义(哪一位是时间戳/指纹/轨迹) | 时间戳是 `1[6-9]\d{8}` 形态的大整数;轨迹是 `[x,y,t]` 三元组数组 | 中高 | | 3 | 明文里 `[360,360]`、`"1920-1080-1055-24-*-*-|-*"` 的作用 | 后者是屏幕/窗口指纹串,在新版被移到了 `cd` 数组末尾 | 中 | --- ## 三、已排除的假设(负结果也是资产,§5.7) | 假设 | 否定证据 | | --- | --- | | 密钥每次运行随机 | 同一脚本 3 次运行首块完全一致 | | 密钥可从 `eks` 推导(MD5/SHA/切片) | 34 种推导 + 40 种偏移切片全部解密失败 | | 密钥是脚本里的字面量 | 全脚本 >1e9 的数只有 3 个 | | 用 `Array.prototype.push` 打桩能抓到密钥 | VM 算术 opcode 用 `g[g.length-2]=...` 直接下标赋值,`push` 桩只记录到 5783 个值且无信号 | | 实时构建复用旧版明文布局 | 旧版校验器对两份实时脚本全部失败;解密后首部格式完全不同 | | 换出口 IP 能解决 1ms 的 12 | 4 个不同省份住宅 IP(含代理)全部 12 | | 换遥测引擎能解决 1ms 的 12 | `python` / `python_keyed` / `node` / `browser`(真实 Chrome)全部 12 | | 长静默窗口能解决 1ms 的 12 | 静默 9 分钟 + 单发一次 → 仍 12 | --- ## 四、本轮目标(已完成) - [x] 识别 `collect` 加密算法为教科书 XTEA 并逐字节验证 - [x] 实现版本无关的 opcode 插桩(反向引用自动识别栈变量名) - [x] 从任意 tdc.js 无歧义复原 4 个密钥字(含索引歧义处理) - [x] 实现模板标定 + 纯 Python 回填,端到端拿到真实 ticket - [x] 固化回归测试(`tcaptcha/tests/test_xtea_key.py`,11 项断言) ## 五、下一步(可被下一轮直接执行) 1. **拿下 1ms 的 12**:用 CDP 打开真实验证码页面,让原生 SDK 自己跑 tdc.js, 复用本仓库视觉求解器算坐标、`Input.dispatchMouseEvent` 在 iframe 内真实点击。 骨架在 `tcaptcha/tdc_browser.js`。 2. **补齐新版明文字段语义**:对照 10+ 份实时模板做差分,把时间戳/指纹/轨迹位点名。 3. **实现 2408 图标点选**:用 1ms 的图标类别重训 `yolo_detector` + `icon_classifier`。 4. **实现滑块 (2401/2402)**:模板匹配找缺口 + 生成滑动轨迹。 --- ## 六、复验入口 ```bash .venv/bin/python -m tcaptcha.tests.test_xtea_key # XTEA + 密钥提取 (11 项) .venv/bin/python -m tcaptcha.tests.test_glyph_solver # 文字点选求解 (8 张真实样本) .venv/bin/python -m tcaptcha.selftest # 全部 ```