Files
hello_js_reverse_skill/tcaptcha/TOOLCHAIN_REPORT.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

125 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 腾讯验证码 (TCaptcha / TDC) 逆向工具链建设与协议解构报告
依据 `AGENTS.md`(自顶向下,拒绝打地鼠,工具优先)原则,已完成腾讯天御验证码全链路核心资产拉取、AST 工具链构建、混沌虚拟机(Chaos VM)反汇编以及运行时语义探针建设。
---
## 一、已建成的工具集清单
| 工具 | 路径 | 核心功能 | 解决什么问题 |
| :--- | :--- | :--- | :--- |
| **Webpack 解包索引器** | [`tools/unpack_webpack.js`](file:///Users/yml/reverse/hello_js_reverse_skill/tools/unpack_webpack.js) | 将 230KB 单文件 bundle 拆解为 122 个独立模块,建立加签/风控关键词索引 | 圈定加签核心,剔除 80% 开源库与 UI 噪音 (L1 解构) |
| **Chaos VM 字节码反汇编器 (纯 Python)** | [`tools/tdc_disasm.py`](file:///Users/yml/reverse/hello_js_reverse_skill/tools/tdc_disasm.py) | 纯 Python 提取 45,636 字节码 + 可达性 CFG 反汇编(26,168 条可达指令,**0 处非法 opcode**);opcode 表可**按构建注入**(见下) | 彻底透明化 TDC 字节码调度器 (L2/L3 解构) |
| **按构建推导 opcode 表** | [`tcaptcha/tdc_opcodes.py`](file:///Users/yml/reverse/hello_js_reverse_skill/tcaptcha/tdc_opcodes.py) | 从解释器主循环 `X[Y[Z++]]()` 取出 handler 数组 → 解析 → 规范化指纹 → 跨构建匹配出**该构建的 opcode 表** | **opcode 编号每份构建整体重排**,写死即换脚本全错(6/6 构建 58/58 handler 匹配) |
| **纯 Python XTEA 密钥静态提取** | [`tcaptcha/tdc_key.py`](file:///Users/yml/reverse/hello_js_reverse_skill/tcaptcha/tdc_key.py) + [`tools/tdc_key_pure.py`](file:///Users/yml/reverse/hello_js_reverse_skill/tools/tdc_key_pure.py) | 反汇编 → 定位 XTEA 闭包(delta) → 两层捕获链选出真密钥数组 → 读 4 个字 + 折盐 | **零 Node** 拿到该构建 XTEA 密钥;6/6 构建与 Node 插桩逐字一致 |
| **深度 Proxy 语义探针** | [`tools/tdc_probe.js`](file:///Users/yml/reverse/hello_js_reverse_skill/tools/tdc_probe.js) | 零 Headless/CDP 依赖,通过深度 Proxy 沙箱执行 `tdc.js`,拦截并记录全部 95 项环境读取 | 毫秒级生成与当前会话时间戳和密钥 100% 自洽的动态 `collect` 与 `eks` (L4 数据层) |
---
## 二、核心逆向成果与证据账本
### 1. TDC 混沌虚拟机(Tencent Chaos VM)指令集
`__TENCENT_CHAOS_VM` 采用经典基于栈的虚拟机架构,包含 70 条操作码:
- 操作码分发器循环:`for (var M = !1; !M;) M = G[z[H++]]();`
- `z`: 45,636 整数组成的解压字节码序列
- `H`: 程序计数器 (PC)
- 关键操作码:
- `00`: `PUSH_STR_CHAR`(字符串构造)
- `03/30/62/35/36/65`: 位运算 (`^`, `&`, `|`, `>>`, `<<`, `>>>`)
- `06/34/41/61`: 函数调用与对象实例化 (`apply`, `new`)
- `46/68/47`: 虚拟寄存器读写与分配
- `56/48/49`: 异常处理栈(Try-Catch 保护链)
- `57`: 闭包创建器(动态子程序展开)
### 2. TDC 关键检测项白名单(已由探针全量捕获,共 95 项)
探针运行实测表明,TDC 实际探测的环境特征仅 95 项,绝非海量属性:
1. **自动化/反爬标记检测**:
- `document.$cdc_asdjflasutopfhvcZLmcfl_`(精准探测 ChromeDriver 变量)
- `window.navigator.webdriver`
- `window.domAutomation`
- `window.fxdriver_id`
- `window.callPhantom` / `casper` / `CasperError` / `patchRequire`
- `window.ubot` / `WebPage`
2. **显示与硬件画像**:
- `screen.width` (2560), `screen.height` (1440), `availHeight` (1345), `colorDepth` (24)
- `deviceXDPI`, `logicalXDPI`, `fontSmoothingEnabled`
- `navigator.hardwareConcurrency` (10), `platform` ("MacIntel"), `languages`
3. **渲染上下文**:
- WebGL Renderer: `"ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)"`
- WebGL Vendor: `"Google Inc. (Apple)"`
- Canvas Fingerprint: 成功样本原图 Base64 (368 字节)
4. **容器关系**:
- `window.top !== window`(识别是否运行在验证码 iframe 内)
- `document.getElementById("tCaptchaDyContent")`
- `window.TCaptchaReferrer`(宿主页面 URL 绑定)
### 3. L4 遥测协议数据解密 (`cd` 38 维字段)
成功解密的 `collect` 明文格式为 `{"cd": [...], "sd": {"od": "C", "ft": "..."}}`:
- `cd[0..1]`: 屏幕宽、平台 (`2560`, `"MacIntel"`)
- `cd[2]`: Canvas 指纹
- `cd[8]`: 宿主页面 URL (`document.referrer`)
- `cd[9]`: User-Agent
- `cd[15/18]`: WebGL 渲染器与厂商
- `cd[19/22/27]`: **当前会话的秒级与毫秒级 Epoch 时间戳**(必须与请求自洽,旧方案硬编码是致死原因之一)
- `cd[20]`: **鼠标相对增量轨迹**:首点为绝对坐标 `[start_x, start_y, delay_ms]`,后续为增量 `[dx, dy, dt_ms]`(dt 均值 16~18ms,模拟 60Hz 采样)
- `cd[26]`: 会话校验和与随机种子
- `cd[31]`: 执行容器类型 (`"iframe"`)
---
## 三、关于 errorCode=12 的诚实结论(**已更正**)
> 本报告早期版本曾把 12 归因于"`re.sub` 破坏校验和 / 时间戳错位 / 机械假轨迹 / CDP 特征泄露"。
> 这 4 条**均与本项目自己的证据账本冲突**(见 `har/1ms_dianxuan/ANALYSIS.md` §7 与 `PROGRESS.md` §三),
> 现更正如下:
| 早期断言 | 现状 |
| --- | --- |
| 时间戳错位(硬编码数天前)| ❌ 撤回:纯算路径自模板回填起就刷新为当前时间戳,仍 12 |
| 机械假轨迹 dt=0/1ms | ❌ 撤回:换真实 ms 分布后仍 12 |
| CDP 特征泄露 | ❌ 撤回:纯 Python(无 CDP)后端同样 12 |
| `re.sub` 破坏内部校验和 | ⚠️ 仅为假设,无对照实验 |
**已确证**:12 与遥测后端 / 出口 IP / 静默时长均无关。真正的自变量
(登录态 / 真实验证码页面上下文)尚未由单变量实验区分 —— 见 `ANALYSIS.md` §7.5。
### 关于深度 Proxy 探针(`tools/tdc_probe.js`)的定位
探针是**分析工具**(用于枚举 tdc.js 真实读取了哪些环境属性),**已不在遥测生成路径上**
(`crypto.generate_telemetry` 只保留纯算)。它注入的 Canvas / WebGL / 屏幕取值来自
**单份成功样本**,属于"复刻一份画像"而非"真实环境",因此不作为交付依据。
---
## 四、一键复验入口
```bash
# 1. 验证 Webpack 解包与核心模块提取
node tools/unpack_webpack.js
# 2. 验证 TDC 虚拟机反汇编与字符串提取
node tools/tdc_vm_disasm.js tcaptcha/core_js/tdc_sample.js # 已废弃, 转发到下面的纯 Python 实现
.venv/bin/python tools/tdc_disasm.py tcaptcha/core_js/tdc_sample.js
# 3. 验证深度 Proxy 语义探针执行并产出 100% 合法 collect 与 eks
node tools/tdc_probe.js tcaptcha/core_js/tdc_sample.js
# 4. 验证探针生成的 collect 成功解密
.venv/bin/python -c '
import json, base64, urllib.parse, subprocess
from tcaptcha.crypto import extract_xtea_key, xtea_decrypt_bytes
with open("tcaptcha/core_js/tdc_sample.js") as f: code = f.read()
key = extract_xtea_key(code)
res = subprocess.check_output(["node", "-e", "const {probeTdc}=require(\"./tools/tdc_probe.js\"); const r=probeTdc(\"tcaptcha/core_js/tdc_sample.js\"); console.log(r.collect);"], text=True).strip()
raw = base64.b64decode(urllib.parse.unquote(res))
plain = xtea_decrypt_bytes(raw, key).rstrip(b"\\x00")
print("成功解密探针生成的遥测载荷,长度:", len(plain))
print("明文前 150 字节:", plain[:150].decode("latin1"))
'
# 5. 纯 Python XTEA 密钥提取 (零 Node) + 跨构建回归
.venv/bin/python tools/tdc_key_pure.py -v har/1ms_dianxuan/live/tdc_fresh.js
.venv/bin/python -m tcaptcha.tests.test_tdc_key # 6 份构建 (含实时新抓) 全部断言
.venv/bin/python tools/tdc_key_pure.py --opcodes tcaptcha/tdc_sample.js # 打印该构建的 opcode 表
```