Files
hello_js_reverse_skill/tcaptcha/TOOLCHAIN_REPORT.md
T
yml2213 d136fb7328 feat(tcaptcha): 建立AST解包、TDC虚拟机反汇编与深度Proxy语义探针工具链
- 解包 tcaptcha-frame bundle,圈定 122 个模块中的核心加签与通信模块
- 还原 __TENCENT_CHAOS_VM 架构:映射全部 70 个操作码语义并实现字符串折叠反汇编
- 提取 246 个核心字符串,全量排查并枚举 95 项环境检测与自动化探针特征
- 实现零浏览器依赖的深度 Proxy 探针,原生生成与会话自洽的 collect 与 eks
2026-10-11 20:13:14 +08:00

106 lines
5.8 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 字节码反汇编器** | [`tools/tdc_vm_disasm.js`](file:///Users/yml/reverse/hello_js_reverse_skill/tools/tdc_vm_disasm.js) | 映射全部 70 个操作码语义,自动折叠字符拼接,反编译 45,636 字节码为 8,152 条指令流 | 彻底透明化 TDC 字节码调度器,提取 246 个环境检测特征 (L2/L3 解构) |
| **深度 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"`)
---
## 三、旧方案致死原因归纳(为什么之前报 12)
1. **`re.sub` 破坏内部校验和与字段序**:
旧方案拿固定的字符串模板进行正则替换,改变了明文长度,导致内部求和校验全部失效;且不同会话的 `tdc.js` 构建字段顺序会动态轮换,静态模板必死。
2. **时间戳严重错位**:
旧方案使用数天前抓包的硬编码时间戳,与当前服务器时间差数天,风控系统秒判拒绝。
3. **机械假轨迹**:
旧代码用 `random.randint` 随机构造假点,dt 分布为 0 或 1ms(相当于 20ms 内完成数十次移动),动力学完全反人类。
4. **CDP 特征泄露**:
浏览器自动化直接被 `document.$cdc_asdjflasutopfhvcZLmcfl_` 抓现行。
---
## 四、一键复验入口
```bash
# 1. 验证 Webpack 解包与核心模块提取
node tools/unpack_webpack.js
# 2. 验证 TDC 虚拟机反汇编与字符串提取
node tools/tdc_vm_disasm.js 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"))
'
```