Commit Graph
24 Commits
Author SHA1 Message Date
yml2213 344eea2194 refactor(结构): 分层重组 + 清除全部废弃路线
做了什么:
- 包改为分层: tcaptcha/{protocol,telemetry,solvers,ocr,adapters,models,tests}
  * protocol/  client.py  transport.py  proxy.py
  * telemetry/ crypto.py  values.py  key.py  opcodes.py
               disasm.py  program.py  layout.py  modules.py
  * solvers/   text_click.py  icon_click.py  icon_click_rotate.py  slider.py
  * ocr/       engines.py  slicer.py
  * adapters/  one_ms.py
- tools/ 里原本是**包运行时依赖**的 tdc_{disasm,program,layout,modules}.py 并入 telemetry/
  (放在 tools/ 属于分层倒置),tools/ 只留 CLI: tdc_key_pure.py / tdc_profile.py / cdp_*.js
- crypto.py 529 -> 340 行: 切除「成功样本模板回填」与「Node 插桩兜底」两条老路;
  generate_telemetry 只剩 python_values 一种模式,传别的会直接报错并说明原因
- 求解器改名分清: solver.py -> solvers/icon_click.py(client 实际在用的 YOLO+ResNet18 ONNX),
  icon_click_solver.py -> solvers/icon_click_rotate.py(仅测试在用,留作对照)
- pyproject: 去掉 ultralytics/onnx(训练栈已删),补上真正在用的 onnxruntime

删除了什么:
- 雨云整条线 + dist/rainyun_qinglong(104M 旧快照)
- 模板回填: models/success_collect_template.b64、tdc_worker.js、analyze_collect_layout.py
- 「补环境」时代浏览器 JS: chrome_fetch/manual_captcha/probe_captcha/solve_in_browser/tdc_browser
- 旧 VM 探索工具 10 个(tdc_collectgen/tdc_slotmap/tdc_lift/tdc_vm_run/tdc_query/tdc_probe 等)
  + 8 个基于它们的旧 verify 脚本
- 训练/标注/评估: train_classifier/train_detector/prepare_yolo/web_labeler/collector/
  siamese/evaluate/benchmark_verify
- tcaptcha/core_js/(60 个分析产物)、TOOLCHAIN_REPORT.md(整篇在描述已删工具)
- 3 个已无任何引用的 PyTorch 权重:icon_classifier.pt 43M / yolo_detector.pt 5.9M /
  siamese_matcher.pt 1.6M —— 线上只跑 onnx,tcaptcha/ 117M -> 66M

为什么:
- 项目后面要专门承载「滑块 / 文字点选 / 图标点选」三种求解器;扁平包 + 运行时依赖混在
  tools/ + 两条已废弃的 collect 生成路线,会让后续扩展持续踩坑

怎么验证:
- .venv/bin/python -m tcaptcha.selftest                    全绿
- scripts/verify/verify_offline.py                         逐槽全等 + 分段几何一致
- scripts/verify/verify_values.py                          4 份真机明文 152/152 逐槽全等
- scripts/verify/send_test.py --mode python_values         1ms 签到页文字点选 errorCode=0
- 回滚点: tag backup/pre-refactor-20261012
2026-10-12 12:34:57 +08:00
yml2213 51dff870cf feat(collect): 纯离线生成打通 1ms 签到文字点选 —— errorCode=0 收口
做了什么:
- 新增 tcaptcha/tdc_values.py: 37 个语义名的取值生成器(静态画像 / 会话量 /
  每次采集量三层)+ build_plain 四段组装 + generate_collect 纯离线出包
- crypto: 新增 generate_telemetry(mode="python_values");轨迹生成统一到
  trajectory_points()(首点绝对坐标 + 增量 + 固定收尾 [1,1,12])
- crypto.split_collect_segments 重写: pad 只有 1 个空格时旧 ` {2,}` 判据会漏段,
  且 UA 串里的普通空格会假阳性 —— 改为「模 24 算术 + 不落在 JSON 字符串内部 + 结构校验」
- tools/tdc_modules: trajectory 指纹原先指向只做 JSON.stringify 的模块,改为
  mouseMoveEvent/touchStartEvent(真轨迹模块),37/37 不变
- client 默认 telemetry_mode 改为 python_values
- 新增 scripts/verify/{align_session,audit_names,verify_values,verify_offline,send_test}.py
  与 tools/{cdp_collect,cdp_dump_scripts,tdc_profile}.py

为什么:
- 账本 #40「谁在发 HTTP 是 12 的剩余自变量」被推翻。同 aid/同客户端/同求解器、
  答案都对,只换 collect 构造方式: 本次会话构建生成 -> 0 (4/4),
  旧模板回填 -> 12 (2/2)。真因是 collect 必须与本次会话构建同源。

怎么验证:
- .venv/bin/python -m tcaptcha.selftest  全绿(含新增四段切分与纯离线闭环回归)
- scripts/verify/verify_values.py        4 份真机明文 152/152 逐槽全等
- scripts/verify/verify_offline.py       逐槽全等 + 分段几何一致
- scripts/verify/send_test.py --mode python_values  1ms 签到页文字点选 errorCode=0 x4
2026-10-12 03:38:18 +08:00
yml2213 80a122245a feat(collect): 按 docs/tdc-案例.md 重做主链路 —— 四段加密 + 字符串指纹识别 37/37
做的什么:
  1. 判决并修正 collect 结构: 不是「整体一次加密」, 而是
     chunk1 / trajectory / chunk2 / sd **四段独立加密**, 前 3 段
     空格 pad 到 24 字节对齐、sd 不 pad, 4 段 base64 直接拼接。
  2. 按案例 §5.3 实现字符串+结构指纹的模块识别器, 把 37 个采集器
     全部识别且跨构建唯一。
  3. 修 reorder 的恒等映射 bug, 并补强断言。
  4. 在线抓取全新样本作为唯一数据源, 清理探索性脚本。

为什么:
  * 旧「整体加密 + \x00 补齐」与真机数据矛盾 —— 导致多轮实验
    建立在结构错误的 collect 上(这正是此前反复"乱碰"的根因)。
  * 模块 id 每份构建重排, 不能当跨构建锚点; 但模块的**代码**
    (字符串集合、结构)不变 —— 这是案例指明的不变量。
  * reorder 旧实现用「目标槽下标」切「源数组」, 顺序不同即全盘错位;
    原测试只断言"是置换", 恒等函数也满足, 所以 bug 被放过了。

怎么验证:
  * 四段结构(3 条独立证据, 其中一条不需密钥):
      - 真机明文 3 处连续空格串 [+5,+10,+24], 且 pad == 24-len%24 全中
      - 密文长度算术 480+96+416+56 = 1048 == 真实 1048 字符
      - '=' 只出现在 1046/1048, 对应 sd 段 33B→补 7→40B
  * 指纹: 4 份全新在线构建 37/37 识别、0 unknown、跨构建语义名集合
    完全一致、单构建内 0 重名
  * 配对正确性: 独立证据 37/37 一致; 负对照(清单下标 / 模块 id /
    随机配对)均只有 2-3/37 —— 证明归一化没有过拟合
  * 端到端重排 3/3 正确且非恒等
  * selftest 全绿; 强断言自查: 旧实现过不了新断言

新增:
  tools/tdc_modules.py            字符串+结构指纹识别(案例 §5.3)
  scripts/fetch_online_samples.py 在线抓取全新 tdc.js
  scripts/verify/*.py             7 个可复现的验证脚本
  docs/tdc-案例.md                案例原文(看雪, 作者执着的猫)

修改:
  tcaptcha/crypto.py          +build_collect / split_collect_segments(四段)
  tools/tdc_collectgen.py     reorder 改为跨构建锚点对齐 + spec_from_seq
  tools/tdc_layout.py         +static_fingerprint / opcode_histogram
  tools/tdc_program.py        修符号求值插值类型崩溃
  tcaptcha/tests/test_collectgen.py  补「非恒等」强断言 + 在线新样本端到端
  tcaptcha/client.py          debug 目录改到系统临时目录(不再写仓库内 scratch/)
  har/1ms_dianxuan/PROGRESS.md      新增已知 #41~#49, 显式修正被推翻的旧结论
  .gitignore                  +scratch/  +har/online/

删除:
  scratch/(含 10-11 遗留的一次性脚本, 已确认不可恢复后清理)

注意:
  * 账本记录了一次自我纠错: 曾据案例 §5.5 误判 kind=2「不进 cd」,
    实测(cd 38 槽、轨迹在第 10 槽)否定后已撤回。
  * 仍未打通: 纯离线生成需要每个语义名的取值来源(已知 #38)。
2026-10-12 02:28:08 +08:00
yml2213 6aaf6bc290 feat(collect): 按本次构建生成 collect 的工具链 + 回环回归测试
tools/tdc_collectgen.py: 静态导出槽位序 -> 按 #36 规则算「项->槽」-> 重排 -> 该构建密钥加密。
同一构建实测: 37 项 -> 38 槽, 明文 686B / collect 920 字符, 解密回环逐字节一致。

规则修正(重要):
  * kind=1 的串只按**括号外**逗号切槽 —— '[]'/'[360,360]'/'["zh-CN","zh"]' 内逗号不构成新槽
  * kind=2 的项**仍产出 1 槽**(r3 清空转存 r4, r4 在收尾 @4905 拼回)

新增 tcaptcha/tests/test_collectgen.py (已并入 selftest) 与 fixture:
  outer_commas / 槽数推算 == 真机槽数 / 重排是置换 / 加解密回环 / 不匹配必须报错。
账本新增已知 #37(工具链跑通) 与 #38(纯离线生成尚未打通, 诚实标注)。
2026-10-12 00:45:23 +08:00
yml2213 6460dd78d6 feat(VM): 打通「清单项 -> cd 槽」的精确映射规则, 新增 tools/tdc_slotmap.py
装配循环(@4362)分支树读通:
  kind===2(@4820) -> 不进 cd: 清空 r3 转存 r4, r4 在收尾 @4905 另作他用
  v===null/undefined(@4799) -> 'null'
  typeof v==='number'(@4738) -> (v||0)
  kind===1(@4724) -> 原样拼(v 是 JSON 碎片串, 串内逗号会切出额外槽)
  其余(@4703) -> 加引号
  收尾 @4753: typeof w==='number' 才再 +1 槽

同一构建端到端校验: 37 项 -> 38 槽, 仅第 33 项(模块1)占 2 槽
('641547489,1791732001' -> 641547489 | 1791732001 精确命中)。
工具支持 --items(运行时取样) + --plain(解密明文) 对拍。
2026-10-12 00:36:31 +08:00
yml2213 d2b3889132 feat(VM): 运行时反证通过 —— 静态导出的 cd 槽位序与真机 require 序逐位一致
做法: 真机 iframe 里包 Function.prototype.apply, 模块工厂调用形状实测为
4 参 [object,object,object,function](require 是最后一个), 按工厂实例分组记账;
再用新增 tools/cdp_script_source.js(Debugger.scriptParsed + getScriptSource)
取运行时真正执行的那份 tdc.js(不能用重新 fetch —— 同 URL 每次返回不同构建)。

结果: 静态 == 运行时 =
[8,9,10,13,14,15,16,17,18,19,20,21,23,24,25,30,31,32,34,36,37,1,38,39,40,41,42,43,44,47,48,49,50,51,56,57,58]
37 项逐位相同 -> 槽位序=require序 这条链闭合; 之前语义对不上是标注启发式的问题。

附带: window.TDC(含 getData) 只在验证码 iframe, 可直接调用取证; cdp_eval 新增 --init-script。
账本新增已知 #35。
2026-10-12 00:24:13 +08:00
yml2213 fe1415508a feat(VM): cd 槽位表可静态导出 + 槽位语义还原(符号求值)
tdc_program 新增 symbolic_assignments(): 对小程序做线性符号求值, 把字节码还原成
`r6 = c.navigator.userAgent` 这样的可读表达式(覆盖字面串/BIND_THIS/MAKE_(REG_)PROP_REF/
GET_GLOBAL_PROP/NEW_FUNC/CALL_*/STORE_REG/LOAD_REG_IMM/REVERSE_TOP2/DUP/算术)。

tdc_layout 新增:
  * slot_value_expr(): 精确规则 —— 采集器模块把值存进某寄存器, `get` 闭包只返回它
    (caps 的父寄存器就是取值寄存器); 含"模块里唯一闭包"的兜底
  * slot_semantics(): 合并"工厂赋值链 + require 委派 + 内部闭包"三条线索
  * 输出改成叶子语义 + 真机明文并排核对

实测确认(线上构建 + 同会话解出的真机 collect):
  * 清单模块 = registry[7], 构造方式是 `r6[r6.length] = require(N)` 追加
    -> **require 顺序即 cd 槽位顺序**(37 个 require + 1 个字面量槽 = 38, 与真机完全吻合)
  * 语义已精确命中: cd[3] <- c.navigator.userAgent, cd[5] <- c.navigator.languages
  * 仍有若干槽的语义标签未对齐(取值寄存器规则需再收口), 已在账本记录
2026-10-12 00:17:30 +08:00
yml2213 c4fe365e8c feat(VM): 吃透 Chaos VM 程序结构层 —— 模块注册表 + collect 槽位表可静态导出
补上 VM_SPEC §5/§6 自己点出的缺口("不是 VM 的问题, 而是模块系统 + 宿主调用序列")。

新增 tools/tdc_program.py(程序结构分析器):
  * closures / entries / 字面串折叠 / 边界感知的函数体切分
  * module_registry(): 识别顶层 "registry[id] = factory" 装配模式 -> 62 个模块工厂
  * call_graph / shared_bodies / find_strings / --func 导出自身指令

修掉两个真 bug:
  * tdc_disasm: RET_TRUE / THROW 未当终结符 -> 控制流穿过函数结尾线性走下去,
    把父函数续段误判给夹在中间的兄弟函数(@4940 曾误归 @4362, 实为 @4160)
  * tdc_lift: 终结符判定写死 opcode 数值 19/49 -> 改为按语义名

新增 tools/tdc_layout.py(静态导出 cd 槽位表):
  装配链已完全厘清:
    顶层 = 62 个模块工厂的注册表(工厂形参 (module, exports, require))
    -> 清单模块 arr[arr.length] = require(N)  <<< 这串 require 的顺序就是 cd 槽位序
    -> 分发模块按 .on/.reset/.get 分三数组
    -> 装配模块 r3='{"cd":[' + for(...) r3 += r8.get() + '],"sd":{...}'
  跨构建实测: core_js 的 require 串与线上构建的串**不同**(槽位序按构建随机化, 与 #31 一致);
  语义对位已验证若干槽(userAgent->cd[3], href->cd[4], languages->cd[5] 命中)。

VM_SPEC 新增 §9 程序结构层(模块系统 / 入口 / collect 装配 / 为何决定 12)。
selftest 全绿。
2026-10-12 00:13:27 +08:00
yml2213 c55f833c66 feat(诊断): 用真机复现解出 1ms 成功会话的 collect 明文 —— 归因改写为「cd 槽位序按会话随机」
结论 (已知 #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 + 取槽位表两条路)
2026-10-11 23:59:42 +08:00
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
yml2213 9286f77661 docs(账本)+feat(神谕): 完全还原 XTEA 密钥构造算法 (纯 Python 复算与 Node 逐字一致)
做了什么:
- tdc_probe.js 神谕增强: 逐字追踪"某值首次出现的 PC"(__WP), DUMP_PC 支持记录多次命中
- PROGRESS.md 已知表新增 14-18 条: 密钥字构造算法 / 盐折叠 / 纯Python复算一致 /
  运行时链路位置 / extract_xtea_key 正确性验证

为什么:
- 用"运行时神谕 + 反汇编阅读"替代逐条模拟(打地鼠), 定位并读懂密钥构造

怎么验证(硬结果):
- 密钥字算法(反汇编读出 + 运行时寄存器佐证):
    word = Σ ((charCodeAt(src,k) + off) & 0xFF) << (8k)
  四个 (源串, 偏移) = ("IA_t",-8) ("9:%!",+49) ("nTLS",-9) ("7XYf",+3)
- 运行时逐字首次出现: 7740/7526/7895/8852; 数组组装 @8886; 进XTEA @9030; 入口 @9109
- 纯 Python 复算 base=[1817655617,1381395306,1245924197,1767660346]
  + 折盐(855311->word0, 723726->word3)
  = [1818510928,1381395306,1245924197,1768384072] == extract_xtea_key  ✅
2026-10-11 22:27:29 +08:00
yml2213 74d478050c feat(神谕): PC 二分定位 + 任意 PC 全寄存器 dump, 破译密钥字算法
做了什么:
- tdc_probe.js 钩子增强:
  * 扫描 E, 报告"基字数组"与"基字[0]"首次出现的 PC (二分定位构造点)
  * DUMP_PC=<pc> 时 dump 该 PC 的全部寄存器 (类型+值)
  * report 回传 __arr / __wrd / __dump

为什么:
- 需要"某个值在哪段字节码诞生"的运行时神谕(AGENTS.md §3.2 第3条), 而不是逐条猜

怎么验证(本轮硬结果):
- 基字[0] 首次出现 @7740 reg9; 基字数组首次出现 @8886 reg7;
  XTEA 闭包在 @9030 CALL_FUNC[2] 被调用, 入口 @9109
- DUMP_PC=7726 显示 E[3]="IA_t", E[10]=ref("IA_t","charCodeAt"), E[4]=0(i)
- 反汇编 @7712..@7740 读出的算法:
      word |= (str.charCodeAt(i) - 8) << (8*i)        // 小端打包
  验证: LE_pack('IA_t' 各字符-8) = 1817655617 == 基字[0]  ✅
- extract_xtea_key 交叉验证: 其密钥可把 probe 的 collect 解成 {"cd":[0,10,"top",... 真明文
  且 real[i]-base[i] 在 i=0/3 恰等于 XTEA 轮里的可见立即数 855311 / 723726
2026-10-11 22:24:19 +08:00
yml2213 5c949090c1 feat(vm): 神谕钩子 —— 在真 VM 中按 PC 快照寄存器 (定位/校验任意值)
做了什么:
- tools/tdc_probe.js 注入自包含钩子(window.__H): 记录 PC 轨迹, 并在指定 PC
  dump 指定寄存器 (默认 9109 / E[7]), 通过 report.__xtea / report.__pc_len 回传
- tools/tdc_keytrace.js 同步支持 TARGET_PC 钩子

为什么:
- 需要"某个值在哪段字节码诞生"的运行时神谕(AGENTS.md §3.2 第3条: 与运行时观测对齐)
- 钩子自包含写在沙箱 window 上(沙箱内没有 globalThis)

怎么验证(本轮的关键结论):
- 命中: @9109 入口 E[7] = [[1817655617,1381395306,1245924197,1767660346]];
  其后 PC 序列 tail = [...9021,9022,9023,9024,9026,9028,9030,9109]
  -> 说明 XTEA 闭包是在 @9030 CALL_FUNC[2] 被调用的
- 交叉验证: 用 extract_xtea_key 的密钥解密 probe 的 collect -> {"cd":[0,10,"top",...
  (真明文) => extract_xtea_key 正确;
  而 E[7] 解不开 => E[7] 是"基字", 不是生效密钥
- 结构确认: real[0]-cap[0]=855311, real[3]-cap[3]=723726 (XTEA 轮里的可见立即数)
  => 有效密钥 = 脚本基字 + 两个轮加盐
2026-10-11 22:21:53 +08:00
yml2213 9c889215f2 feat(vm): 提升器按闭包边界切分函数 + 递归展开全部子程序
做了什么:
- tdc_disasm.disassemble_cfg 增加 follow_closures 开关
  (提升单函数时关闭, 避免"子函数体的可达性溢进父函数")
- tdc_lift.run 递归提升所有被引用的闭包: 229 个独立函数
- 函数名统一为 sub_<target>, 便于按 pc 定位

为什么:
- 原来整个程序挤在一个 function 里(闭包 body 与父函数地址连续), 无法按子程序阅读
- AGENTS.md §3.3 步骤5 明确要求"子程序调用(闭包创建)递归展开"

怎么验证:
- .venv/bin/python tools/tdc_lift.py tcaptcha/core_js/tdc_sample.js
  -> lifted functions=229; XTEA 例程 = sub_9109, 内含 r2=(r2+2654435769)
- .venv/bin/python tools/tdc_query.py .../tdc_sample.lifted.txt who r2
  -> sub_9109 @9109: r2 = 0;  @9285: r2 = (r2 + 2654435769);
2026-10-11 22:18:42 +08:00
yml2213 942421d811 feat(vm): 提升器改用"执行流栈快照(DFS)"渲染, 消除跨跳转失步
做了什么:
- _lift_func 重写为两遍:
  第一遍: 沿执行流 DFS 传播"求值栈快照"(每条 pc 记录进入时的栈)
  第二遍: 按地址序渲染, 块首用快照还原栈
- 新增 _next_pc(): 用规范操作数长度计算后继(含 CREATE_CLOSURE 动态长度)

为什么:
- 原来按"地址序 + 块首清空栈"提升, 表达式跨分支传递时会整体丢失
- 栈机必须在"执行流"上跟踪栈, 而不是地址序

怎么验证:
- XTEA 轮仍可读: r2=(r2+2654435769); r?+=(((r6<<4)^(r6>>>5))+r6)^(r2+r7[((r2>>>11)&3)])
- 闭包图正确: // closure func_9109(p0->r3,p1->r4) caps=[r7<-r8] target=@9109
- 已知残留: 汇合点(多前驱)取首路径栈, 仍会产出少量 r<?>  (下一步: phi 合并/对齐运行时轨迹)
2026-10-11 22:17:55 +08:00
yml2213 eed7004754 chore(vm): 归档解释器 tdc_vm_run.py 到正确语义 (VM 返回 = 栈顶/切片)
做了什么:
- 修正 VM 返回语义: `A ? (E.pop(), E.slice(3+VM.v)) : E.pop()` (闭包调用返回栈顶)
- JArr 缺键返回 undefined; E 初值 [this]=全局; CALL_FUNC 的 this=c

为什么:
- 该工具已判定为"用 Python 复刻 JS 运行时"的弯路(AGENTS.md §2.3 铁律2 打地鼠陷阱)
- 但已实现的 58 个 opcode 语义 + 诊断钩子仍有储备价值, 故归档而非删除(§1.4)

怎么验证:
- py_compile 通过; 与真值 PC 轨迹对拍仍显示前 713 步一致(未继续推进, 按新路线改走 lift)
2026-10-11 22:15:51 +08:00
yml2213 2e7aaff9ec feat(vm): 自上而下重构工具链 —— 新增提升器(lift)与查询CLI(query)
做了什么:
- 新增 tools/tdc_lift.py: 字节码 -> 可读伪代码 (AGENTS.md §3.3 步骤5)
  * 表达式栈还原 (算术/比较/调用/属性)
  * 寄存器引用惯用法还原 (PUSH_IMM_ARRAY n + MAKE_REG_PROP_REF -> rN[key])
  * 控制流 label/goto/if; CREATE_CLOSURE 递归展开成具名函数
- 新增 tools/tdc_query.py: who rN / at @pc / funcs / grep RE (对齐 §4.2 验收)
- 新增 tools/tdc_keytrace.js: 用真 VM 当神谕, 定位某值在哪段字节码诞生
- VM_SPEC §7/§8 重写为"自上而下五件套 + 路线", 并把 tdc_vm_run.py 标注为归档(打地鼠陷阱)
- 修正 lift 的 [n]->rN 归一 与 标签刷屏

为什么:
- 之前用 Python 复刻 JS 运行时去"跑"VM = §2.3 铁律2 的打地鼠陷阱, 收敛极慢
- §3.3 步骤5 要的是"读指令流得到算法; 闭包递归展开" -> 提升(lift)才是正路
- §2.1 最后一步: 读出算法后用目标语言原生重写, 跳过 VM/环境层

怎么验证:
- lift 首次把 XTEA 轮读成可读式: r2=(r2+2654435769); r?+=(((r6<<4)^(r6>>>5))+r6)^(r2+(r7[((r2>>>11)&3)]+855311))
- query 三个验收问题均可秒答: who r2 / at @9285 / funcs
2026-10-11 22:15:43 +08:00
yml2213 9008af0d18 fix(vm): 3 处保真修复 + 精确定位到模块实参为 null
做了什么:
- JArr.__getitem__ 缺失键返回 undefined (JS 语义), 原来返回 Python None
- E 初值 E[0]=[this], 顶层 this=全局对象 c (原来错写成 None)
- MAKE_REG_PROP_REF / CALL_METHOD / closure 调用 增加 DBGCALL 诊断输出

为什么:
- 用 PC 轨迹对拍逐条缩小: 前 713 步一致, 分歧在 @206 CALL_METHOD
- 根因链: @45631 CALL_FUNC(模块工厂) 的实参是 [null] -> 参数 r3=[null]
  -> 闭包 @20 捕获 r6=父r3 -> E[6][0]=null -> ref=[null,'call'] -> 调用不到

怎么验证:
- node tools/tdc_trace.js ... -> 19266 步(真值); 我们仍 739 步
- 分歧点/根因链见上 (已用 DBGCALL 定位到具体指令与寄存器)

下一步线索: 追 "谁把该实参压成了 null" (候选: PUSH_NULL(29) 被误用, 或某次属性读取应得对象却拿到 null)
2026-10-11 22:07:16 +08:00
yml2213 3f91b0a4e3 feat(vm): PC 轨迹对拍工具 + 2 处语义修复(定位到首个执行流分歧)
做了什么:
- 新增 tools/tdc_trace.js: 给分发器 M=G[z[H++]]() 打补丁, dump 真实执行 PC 序列
- tdc_vm_run.py: 新增 TRACE_FULL 全量 PC 轨迹; 子 VM 共享 pcs/events(轨迹是全局的)
- 修复 CALL_FUNC 的 this 绑定: 真实语义 fn.apply(c, B) -> this = c (原来传 None)
- DBGCALL 调试输出(callee 类型/closure 异常/ref 与栈尾)

为什么:
- 单靠读语义会无限试错; PC 轨迹对拍能一步定位首个分歧

怎么验证:
- node tools/tdc_trace.js tcaptcha/core_js/tdc_sample.js  -> 真实 19266 步
- 我们的解释器 739 步; 逐条 diff 后:**前 713 步完全一致**
- 首个分歧 @step714: real 进入 @1544, 我们 fall-through 到 @208
  对应指令 @206 CALL_METHOD [4]; 我们的 ref=[None,'call'] (base 为 None),
  真实应为一个可调用对象 -> 说明某个寄存器在我们这边是 None
2026-10-11 22:04:44 +08:00
yml2213 1fbd8f46f5 fix(vm): 修正解释器 4 处保真度问题 + 增加事件追踪
做了什么:
- E 初值改为 [[this],[{}]] (与 VM 源码默认一致)
- Stub 改为真正保存被赋属性 (模块工厂挂在对象上, 丢弃就找不到模块)
- Object/Array/String/... 改为 HostFn, new 时真正构造容器
- getprop/setprop 采用 JS 非严格语义 (原始值: 读=undefined, 写=静默忽略)
- 新增 TRACE=1 事件追踪 (CREATE_CLOSURE/CALL_*/RET_TRUE) 与模块表收集

为什么:
- 这些是解释器与真实 VM 保真度不符的根因, 导致顶层跑完却收不到任何模块闭包

怎么验证:
- py_compile 通过; TRACE=1 跑出 66 条关键事件(其中 CREATE_CLOSURE ~65)
- 现状: 仍只跑完顶层引导(739 步), 模块表为空 -> 待用 PC 轨迹对拍定位首个分歧
2026-10-11 22:02:20 +08:00
yml2213 70f2ece32c docs(vm): 新增 Chaos VM 规范 + WIP 纯 Python 解释器
做了什么:
- 新增 tcaptcha/VM_SPEC.md: 从源码/反汇编事实整理 VM 全套规格
  * §1 架构(VM(H,z,c,E,I,D,U,C), c=window, 主循环 M=G[z[H++]]())
  * §2 数据模型(E 一物两用=寄存器区+求值栈; 寄存器单元 E[i][0]; 引用=二元数组 [base,key])
  * §3 完整 opcode 表(70 槽/58 handler/12 空洞, 附 handler 源码与 argc, 由 AST 自动导出)
  * §4 闭包与调用(CREATE_CLOSURE 动态布局, caps 共享单元, CALL_FUNC/METHOD 的 this 绑定)
  * §5 引导流程(实测顶层只注册模块) §6 宿主与驱动(c 即 window, 环境全走 c)
  * §7 工具链映射 §8 解释器路线图(已完成①-④, 未完成⑤模块系统+宿主驱动)
- 新增 tools/tdc_vm_run.py: 58 个 opcode 的具体解释器 + 宽松 JS 宿主 (WIP, 跑通顶层引导)

为什么:
- 之前"没头绪"的根因是数据模型(E 一物两用/寄存器单元/引用二元数组), 必须写成确定规格
- 并定位真正的卡点: 不在 VM 而在"模块系统 + 宿主调用序列"

怎么验证:
- tools/tdc_vm_run.py 跑 739 步后 RET_TRUE (仅顶层引导), 与 CFG 可达性一致
2026-10-11 21:59:06 +08:00
yml2213 d52368be2b feat(工具链): 纯 Python 可达性 CFG 反汇编器 + 废弃旧线性反汇编器
做了什么:
- 新增 tools/tdc_disasm.py: 纯 Python 提取 tdc.js 字节码数组 + 可达性 CFG 反汇编
  * opcode 表由 VM 源码 AST 推导 (70 槽 / 58 handler, 操作数个数 = handler 内 z[H++] 次数)
  * 复刻 VM 内联的 base64 解码 + 游程展开 (含科学计数法常量), 无任何 JS 运行时
  * 遍历根含 JUMP/JUMP_IF_TRUE/PUSH_TRY catch/CREATE_CLOSURE 闭包体
- tools/tdc_vm_disasm.js 降级为废弃 shim (转发到 Python 版)
- TOOLCHAIN_REPORT.md 更新工具行与复验命令; tdc_sample.disasm.txt 用正确实现重新生成

为什么:
- 旧线性反汇编对字节码里的数据区继续扫描会失步: 实测 73 处 UNKNOWN (16 处 >69 的
  "不可能" opcode), 且未把闭包体当遍历根 -> 大半个程序不可达, 输出不可信

怎么验证:
- .venv/bin/python tools/tdc_disasm.py tcaptcha/core_js/tdc_sample.js
  -> bytecode=45636 (与旧实现一致), 可达指令 26168, illegal opcodes: []
- 反汇编首次干净读出 XTEA 轮函数: PUSH_IMM 2654435769 / ADD / ... / SHR_UNSIGNED 11 / &3
  并定位密钥数组在寄存器 7, 本构建轮加盐为 723726 / 855311 (均为立即数)
2026-10-11 21:47:27 +08:00
yml2213 60ae14d54a feat(captcha): 增加全题型图片落盘、字形一致性强校验与防秒换拟人交互机制
- 题目图片自动下载: 所有题型原图统一落盘至 scratch/captcha_debug,文字点选自动生成带编号红圈标注图便于肉眼核验
- 字形严格一致性校验: 在 SolveResult 增加 text_match_ok 检查,对图字不符或混淆干扰题直接拒答换题,避免错答引发 errorCode=12
- 防秒换与拟人延时: 遇到非预期题目换题等待 2.8~4.2s (对齐 HAR 真实抓包),求解失败等待 2.5~3.8s,作答前模拟交互 5.5~7.5s
- 代理容错增强: 在 OneMsAdapter 捕获代理断流与鉴权异常,支持动态轮换新代理重试
2026-10-11 20:51:12 +08:00
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