Files
hello_js_reverse_skill/har/1ms_dianxuan/PROGRESS.md
T
yml2213 2e2bbc876a feat(密钥): 纯 Python 静态提取 XTEA 密钥 —— 去掉最后一个 Node 依赖 (6/6 构建与神谕逐字一致)
做了什么
- 新增 tcaptcha/tdc_opcodes.py: 按构建从解释器源码推导 opcode 表。
  实测 opcode 编号**每份构建整体重排**(同一源码 pc0 的 SET_STACK_LEN 在 6 份构建里
  = 53/19/32/57/55/1, JUMP = 60/17/42/58/21/2); 旧固化表只对 core_js 有效 ——
  用它反汇编其它构建会在 pc0 撞上 RET_TRUE, 得到"空反汇编"的假象。
  做法: 定位主循环 X[Y[Z++]]() 的 handler 数组 -> JS 感知切分 -> handler 源码规范化
  (去空白 + 局部变量按首次出现重命名) 取指纹 -> 与内置 58 条 core_js 参考指纹匹配;
  数组下标即该构建的 opcode, argc 由语义名取 (与 handler 体内 x[y++] 次数实测一致)。
- 新增 tcaptcha/tdc_key.py: 纯 Python XTEA 密钥静态还原器 (零 Node / 零 pc 硬编码)。
  反汇编 -> 定位含 delta 的 XTEA 闭包 -> 用"两层捕获链"选出真密钥数组:
  字闭包捕获的 B 帧寄存器, 经"定义 B 的那个闭包"的 caps 映射到 G 帧后须 == XTEA 闭包
  捕获的寄存器; 借此排除脚本里并行计算的第二套诱饵字 -> 读 4 个 (源串, 偏移, 下标)
  -> 折入轮加盐。
- tools/tdc_disasm.py: disassemble_cfg/_decode_one 支持注入按构建推导的 opcode 表
  (names/arity/dynamic), 默认仍回落 core_js 表; 控制流 opcode 改为按语义名定位。
- tools/tdc_key_pure.py: CLI (-v 打印推导过程 / --opcodes 打印 opcode 表 / --json)。
- tcaptcha/crypto.py: extract_xtea_key 改为"纯 Python 主路径 + Node 插桩兜底"。
- 新增 tcaptcha/tests/test_tdc_key.py 并接入 selftest;
  新增入库 fixture tcaptcha/tests/fixtures/tdc_fresh_2026-10-11.js (现场新抓的 tdc.js)。

为什么
- AGENTS.md §2.4 分层交付 / §1.1: 去掉最后一个 Node 依赖, 提高并发与抗发版能力;
- §5.7 负结果也是资产: 显式撤回"opcode 数值跨构建固定"与"密钥只能靠 Node 插桩拿"两条旧结论。

怎么验证
- 6 份构建纯 Python 结果 == Node 插桩 extract_xtea_key, **逐字一致**:
  core_js / root / 1ms tdc.js / tdc_live / tdc_latest / 2026-10-11 现场新抓 tdc_fresh。
- opcode 表 6/6 构建 58/58 handler 精确匹配, 0 未识别。
- .venv/bin/python -m tcaptcha.selftest 全绿 (含既有逐字节对拍 test_xtea_key)。
- 把 node_bin 指向不存在的程序时 generate_telemetry 仍产出 collect (证明已零 Node)。
2026-10-11 22:49:23 +08:00

11 KiB
Raw Blame History

反 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 取到源串;反汇编读出常量:
("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]) 确证

二、未知(附假设 + 确信度)

# 未知 当前假设 确信度
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 降级为兜底

四、当前实现(纯算单一路径)

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 兜底提取密钥(主路径已不需要)

五、下一步(可被下一轮直接执行)

  1. 静态提取 XTEA 密钥(去掉最后一个 Node 依赖) —— 已完成(已知 #19~#22): tcaptcha/tdc_key.py 纯 Python 零 Node,6/6 构建与 Node 逐字一致。 残留增强项(非阻塞):① 若服务端改了 handler 语义,tdc_opcodes.derive_opcode_table 会抛"指纹未识别",此时应更新参考指纹而非改定位逻辑;② 抗"多套诱饵数组 >2 层捕获"的 兜底(当前两层已覆盖实测全部 6 份)。
  2. 拿下 1ms 的 12:做账本 §7.5 的单变量实验 —— (a) 给 captcha 域带上成功样本里的 uin/skey/qcmainCSRFToken/intl cookie(已做,H3 已排除); (b) 登录态真页面触发 vs 未登录触发。二选一即可闭合。
  3. 补齐新版明文字段语义:对照 10+ 份实时模板做差分。
  4. 实现 2408 图标点选:按 1ms 图标类别重训 yolo_detector + icon_classifier。
  5. 实现滑块 (2401/2402):模板匹配找缺口 + 生成滑动轨迹。

六、复验入口

.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.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 表