22 Commits
Author SHA1 Message Date
yml2213 99565f65f0 feat(web): 新增 Web 观测台(形态 A)+ 抽出 render/targets 共用层
- tcaptcha/web/:FastAPI + SSE + 零构建单页。把同步阻塞的 solve() 包成可观测
  后台任务(工作线程 + 全局串行,因 onnxruntime session 非线程安全),用
  logging.Handler 零侵入把 client 日志变成事件流;按靶子实测 min_interval 强制节流。
- tcaptcha/render.py:标注图(点击顺序编号 + 质心偏差)与点位量尺,CLI 与 web 共用。
- tcaptcha/targets.py:靶子表提为单一来源(含实测频控间隔 1ms=60s / mliev=3s)。
- fastapi/uvicorn 走 optional extra `web`,核心包零 web 依赖(带回归断言)。
- 修两个真 bug:hook 求解器时 args[0] 是实例自身(真入参从 args[1] 起);
  client 默认每次新建 solver,预热实例未被复用(每发重载 56MB ONNX)。
- 复验:icon/text/slider 三题型各拿到 errorCode=0;selftest 全绿;依赖隔离通过。

用法:uv run --extra web python -m tcaptcha.web.app
2026-10-14 19:10:54 +08:00
yml2213 74a863c8b3 feat(icon): 轨迹时序指纹修复 —— 1ms 图标 4/8→7/8=0,errorCode 语义与轨迹对拍收口
- 真因:dt 分布与真机严重不符(旧 50% 落 0~16ms,真机 79% 落 13~20ms)+ 点数
  随机 57~65(真机 6/6 恒 61)。按 6 条真机轨迹重拟合 _sample_dt,新增 _fit_points
  钉死 61 点(补齐用 [0,0,dt] 驻留点,不能复制位移点——净位移会被放大)。
- A/B:1ms 图标 0×4→0×7(前 7 连 0);残余偶发 12 为连发频控形态,低频使用即可。
- errorCode 语义标定:50=答案被否(容差实测 25~40px,我们点偏仅 2~3px),
  12=包级拒答;判点位只看 50/0。
- 顺带修 build_plain:轨迹落末槽(k=37)时 chunk2 恒补前导逗号 → 非法 JSON。
- 新增 scripts/verify/send_campaign.py(批量真发+落图+统计)与 judge_icons.py
  (提示目标 vs 点击点并排判读);账本 #64~#74 + 专文 ICON2408.md。

复验:selftest 全绿;判读图 24/24 点位全对;mliev 图标 9/10=0。
2026-10-12 14:32:14 +08:00
yml2213 6a8b3b3ce4 feat(slider): 滑块 (2401/2402) 端到端打通 —— errorCode=0
做了什么:
- solvers/slider.py: 新增 SliderChallenge(把课题 JSON 解析成解题参数)+ piece_from_sprite
  (按 sprite_pos/size_2d 裁拼图块)+ track_limit 解析 / clamp_x / answer(按 data_type 组答案)
- telemetry/values.py: 新增 drag_trajectory_points(拖动轨迹:按下->横移->松手,
  首点绝对、其余增量、尾点固定 [1,1,12]);_sample_dt 抽成模块级供两条轨迹复用
- telemetry/{values,crypto}.py: generate_collect / generate_telemetry 接受 trajectory=
- protocol/client.py: 新增滑块分支(拉底图 + 精灵图 -> 裁块 -> ZNCC -> 组答案 -> 拖动轨迹),
  accept_kinds 默认扩为 文字点选 + 图标点选 + 滑块
- scripts/verify/send_test.py: 加 --kind text|slider|icon|any

关键结论(账本 #60~#63):
- 滑块答案是 DynAnswerType_POS,但坐标用**背景图原始像素**(不是页面显示像素):
  所以解题用 scale=1.0 直接提交 target_x_image;elem_id / data_type 均取自 move_cfg
- 拼图块可从课题配置直接裁,与旧 DOM 换算结果**逐像素相同**(测试已加交叉断言)
- **ddddocr 的滑块距离识别不可用**:fixture 偏 59px、实时偏 60px。判定手段是把拼图块
  alpha 轮廓叠到背景上目视 —— 本项目 ZNCC 与缺口描边严丝合缝,ddddocr 整体右偏
- 低置信度(margin=0.024 < 0.08)那次被正确拒答并换题,换题后立刻成功,安全网有效

怎么验证:
- .venv/bin/python -m tcaptcha.tests.test_slider_solver   全绿(新增 [6]/[7] 两组断言)
- .venv/bin/python -m tcaptcha.selftest                   全绿
- .venv/bin/python scripts/verify/send_test.py --kind slider   3/3 errorCode=0
- 文字点选回归不受影响:--kind text 仍为 errorCode=0
2026-10-12 12:46:35 +08:00
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 50734a854b test(12): H-LAYOUT 与「包内容」两个假设均被证伪 —— 剩余自变量收敛到「谁在发 HTTP」
实验 A/B (scripts/experiment_layout_verify.py --solve, 真解题):
  按本次构建槽位序 vs 仓库旧模板 -> 两组 9/9 全 12  => 槽位序不是自变量
严格对照(决定性): 同一批取值、同一长度 920、只颠倒槽位序 -> 6/6 全 12

实验 C: 本会话真机真实 collect(TDC.getData(true), 1012 字符) 原样交 Python 发
  -> 4/4 全 12  => 包内容不是自变量

附带关键事实: 12 只在「答案正确」后才判; 答错一律 50/9。
  滑块题型下按点选坐标答(必错)两组都是 50, 无区分度; 换成真解后立刻全 12。
  这解释了早期多轮对照为何无效 —— 今后做 12 的 A/B 必须真解题。

账本: §三 新增两条证伪; 新增已知 #39(12 的判定前提) 与 #40(剩余自变量+收口实验)。
2026-10-12 00:52:01 +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 b7025356bc docs(账本): 新增未知 #4 —— 采集器返回值是 tuple, 装配按 kind 分支, 朴素项序号对齐会错位
cap2 ground truth 校准发现: c.screen.width 的采集器闭包返回 [screen.width],
但 cd 里 2560 落在 9 号槽、1023 落在 11 号槽 -> 静态项序号不能直接等于 cd 槽号。
未解点: kind=1/2/其它各对应什么拼装规则(读透装配模块 @4362 的 4445~4939 分支)。
2026-10-12 00:26:05 +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 50c0ca33c6 docs(账本): 记录 VM 程序结构层打通(#33/#34)与槽位语义残留点
已知 #33: 顶层 = 62 个模块工厂注册表; 修了反汇编器的 RET_TRUE/THROW 终结符缺失
已知 #34: cd 槽位序 = 清单模块 require 顺序(追加式数组), tools/tdc_layout.py 可静态导出
§五 1(b) 重写: 静态提取已打通, 残留两件 —— 取值寄存器规则收口 + 运行时 require 序对拍
2026-10-12 00:17:46 +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 bc8cc5e124 feat(验证): 纯算链路在真实 aid 上端到端出 ticket —— 归因 1ms 的 12 为 aid 级风控
做了什么
- 新增 scripts/check_real_tencent_aid.py: 用**可反复测**的真实 aid 回归纯算链路
  (默认 aid=190758559 / mliev.login.wang 短信登录; 也可 --aid 195672344 对照)。
- 账本 har/1ms_dianxuan/PROGRESS.md: 已知 #26~#28、已排除新增三行、下一步重写为"已闭合结论 + 可复现靶子"。

为什么 (AGENTS §2.1 第 0 问 / §5.7)
- 用户提供可重复的真机登录抓包 har/原始抓包/登录抓包-点选.har, 得以把"12 到底是不是我们的问题"
  用一个能成功的目标做单变量反证。

怎么验证 (2026-10-11 实测)
- aid 190758559: 本客户端 solve() => errorCode=0 + 真 ticket, 连续 4 次:
  tr03lxdviLWtn4uXGjWjmrLQ / tr03A5zTSQ9bCog51sDm / tr039KeeQxXX-OdduJoY / tr03fGUOpcJTZWXWmZG7Uioc0Y8GlHPn
  其中 **httpx + 无 cookie + 无任何头部保真** 也拿到 0 —— 证明纯算链路本就完好。
- aid 195672344 (1ms): 在 cookie / curl_cffi Chrome 指纹 / 逐 endpoint 头保真 / collect 内容
  逐字节同构 / 带签名 identity_context 先行注册 —— 全部仍 12。
- 结论: 1ms 的 12 是该 aid 自身风控不放行"非真机页面"会话; 请求侧已无自变量,
  纯协议侧无解, 只能真机桥接或换入口。
2026-10-11 23:11:36 +08:00
yml2213 06c4348163 test(1ms): 逐条单变量排除 errorCode=12 的已知自变量 (cookie / 传输指纹 / 头保真 / collect)
做了什么
- 新增 tcaptcha/http_transport.py: curl_cffi(Chrome) 传输层壳, 对上层暴露 httpx.Client 同款
  get/post/close; TencentCaptchaClient 增加 transport="httpx"|"curl" + impersonate 参数。
- client.py 按真实抓包(har/签到成功-1.har)逐 endpoint 保真请求头:
  * prehandle/tdc.js/getcapbysig: Sec-Fetch-Dest=script/image + Mode=no-cors +
    Storage-Access=active + captcha 域 cookie, Referer 为 site-origin / gtimg;
  * getsig/verify: empty/cors + Origin gtimg, 且 **不带** cookie (SameSite=Lax 实测如此)。
  cookie 由全局头改为按调用点附加 (此前 verify 也带, 与真实浏览器不符)。
- 新增 scripts/experiment_transport_ab.py: 只改传输层的单变量 A/B。

为什么 (AGENTS §2.1 / §5.7)
- 抓包里 1ms 的真机成功样本(errorCode=0)与真机失败样本(50/50/9)证明: 12 是"请求方身份"判定,
  与答案/载荷无关 —— 需要把客户端与真机的每一处可测差异逐条单变量掉。

怎么验证 (2026-10-11 实测, 全部仍 12)
- 真实 cookie 值 (uin/skey/qcmainCSRFToken/intl) -> 12 (H3 用真值再次排除)
- curl_cffi impersonate=chrome (JA3/JA4 + HTTP/2 + 头序) + 真实 cookie -> 12 (传输层排除)
- curl_cffi + 逐 endpoint 头保真 (Sec-Fetch-*/cookie 位置) -> 12 (头保真排除)
- 我们的 collect 明文与 1ms 真实成功样本逐字节同构 (同 1353B/同字段序/同取值) -> 12 (内容排除)
- 反向: 用纯 Python 提取的密钥成功解密 1ms 真实成功样本的 collect -> 密钥链路对真机样本成立
- .venv/bin/python -m tcaptcha.selftest 全绿

账本 har/1ms_dianxuan/PROGRESS.md: 已知 #23~#25; 已排除表新增四行; 下一步改为 (a) 真机桥接保底 /
(b) identity_context 研究项。
2026-10-11 23:01:24 +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 32a78318ad docs(账本): 记录 XTEA 密钥形态的关键发现(ASCII 打包串 / 闭包捕获 / 轮加盐随构建变)
做了什么:
- PROGRESS.md §一 已知表新增 4 条:
  10. XTEA 密钥字 = 4 个「ASCII 打包的 4 字符串」(大端拼 32bit)
  11. 密钥既不是立即数也不是字面字符串(明文/大端字节/base64 都搜不到)
  12. 密钥数组经闭包捕获传入: @09463 CREATE_CLOSURE caps=[r7=r8], XTEA 轮以 reg7[(sum>>>11)&3] 取用
  13. 轮加盐随构建变化(本构建 723726/855311, 台账另一构建 2829068/328986), 但都是可见立即数
- PROGRESS.md §五 下一步新增第 0 条: 静态提取密钥(逐闭包符号执行)

为什么:
- 这是"去掉最后一个 Node 依赖"的关键定位, 避免重复摸索

怎么验证:
- 密钥字节解剖: 1818510928 -> 0x6c644650 "ldFP" 等 (core_js/tdc_sample.js)
- 反汇编: .venv/bin/python tools/tdc_disasm.py tcaptcha/core_js/tdc_sample.js
2026-10-11 21:51:06 +08:00
yml2213 e2485c5ea6 docs(账本): 记录 H3 单变量实验结论(captcha 域 cookie 与 errorCode=12 无关)
做了什么:
- ANALYSIS.md §7.1 新增 E8: captcha 域"带/不带"腾讯云登录 cookie, 单变量 A/B 均为 12
- ANALYSIS.md §7.4 假设表更新: H3 标注为已排除, 只剩 H1/H2
- ANALYSIS.md §4.3 更正: extract_eks 改通用匹配后实测 248 字符, 与真实浏览器一致
- PROGRESS.md §三 已排除表新增 H3 一行

为什么:
- 彻底关闭"换 cookie/cookie 同源"这条猜测路线, 避免重复投入

怎么验证:
- 单变量 A/B: scripts/experiment_h3_cookie_ab.py --both  -> A=12, B=12
  (同一次运行, 题目 2404 求解 posterior=1.000 全对, collect_len=1856)
2026-10-11 21:47:27 +08:00
yml2213 47ce74b160 refactor(纯算): 移除全部补环境后端,收敛为纯算单一路径 + 修复两处红灯
做了什么:
- 删除 generate_telemetry_node/browser/probe 三个伪造环境后端, 以及 apply_real_environment、
  4 段拼接旧结构、human_dt_ms、generate_collect_pure (crypto.py 955 -> 389 行)
- 遥测只剩纯算: python_keyed = Node 仅提取 XTEA 密钥字 + Python 模板回填 + 教科书 XTEA
- 修复 crypto.py 重复的 xtea_encrypt_block (内置自测从失败到通过), 并并入 selftest
- 修复 glyph_solver 强校验: 改用集成得分矩阵行 argmax 自洽校验, 回归 5/8 -> 8/8
- 移除注释里无证据的 errorCode=12 归因; 移除 12 自动换 IP 重试
- extract_eks 不再写死变量名; UA 常量统一为 Chrome/155
- 文档对齐: ANALYSIS §8 修订 / PROGRESS 重写 / TOOLCHAIN_REPORT §三 更正

为什么:
- 本项目证据账本已证: 12 与遥测后端/出口 IP/静默时长均无关, 补环境无收益且引入脆弱面
- 强校验用单引擎 ocr_text 会误杀正确解 (实测 簿->垂 / 笆->芭 / 筹->努)

怎么验证:
- .venv/bin/python -m tcaptcha.selftest 全绿 (crypto_core + glyph 8/8 + xtea_key)
- 端到端纯算: python / python_keyed 均可解回 '{"cd":[' 开头明文, probe 模式被拒绝
2026-10-11 21:30:26 +08:00
yml2213 1ef819b48d feat(纯算): 以成功样本明文为模板的纯 Python 出包管线 + 证据账本 v2
做了什么
- 从「签到成功-1.har」提取该次会话的 tdc.js, 提取密钥解密出
  errorCode=0 那次 collect 的完整明文 (1360 字节), 存为
  tcaptcha/models/success_collect_template.b64
- 新增 generate_collect_success(): 以成功样本为结构模板, Python 只回填
  轨迹/当前时间戳/随机盐, 其余环境字段 (canvas/WebGL/屏幕/语言/sd.ft) 原样保留
- 新增 _success_style_trajectory(): 按成功样本实测统计生成轨迹
  (61 点, dt 均值 30.6ms, 50% 在 0~16ms, 途经 3 个目标, 驻留抖动)
  —— 修正了统计正则不允许负增量的 bug (真实 61 点, 此前误数成 18)
- 发现成功样本用 \x00 填充 (不是空格), sd 多一个 ft 字段
- telemetry_mode=python_keyed 默认走该路径; Node 仅提取每构建 XTEA 密钥
- ANALYSIS.md 第七节重写为 v2 证据账本, 显式撤回 4 个无证据结论

验证
- 纯 Python 密文 == 真实 tdc.js 密文 (逐字节)
- 回归: test_xtea_key 11 项、test_glyph_solver 8/8
- 端到端 1ms 实测: 仍 errorCode=12 (诚实记录, 未解决)

结论(见 ANALYSIS.md 7.5-7.7)
- 12 不是"检测到机器人": 同 IP 有成功样本、参数逐字段一致、
  真 Chrome 网络栈/真人点击/CDP 均排除
- 剩余假设: H1 需已登录业务会话(未用 auth_token 调 captcha/params 复测)
            H3 需有效 qcloud.com cookie(过期 skey 复测不能定论)
- 下一步: 用登录态 auth_token 调 /api/v1/captcha/params,
  对比返回是否多出鉴权字段 —— 这是唯一未做且成本低的关键实验
2026-10-11 19:12:23 +08:00
yml2213 be7a6ee97b chore(git): har/ 下只忽略抓包产物,放行分析文档入库
做了什么
- .gitignore 的 `har/` 改为 `har/**` + `!har/**/` + `!har/**/*.md`
  —— 仍忽略 har/ 下的 jpg/js/json 抓包产物,但 *.md 分析文档正常入库
- 首次纳入两份文本证据资产:
  har/1ms_dianxuan/ANALYSIS.md  (330+ 行协议分析报告)
  har/1ms_dianxuan/PROGRESS.md  (已知/未知/已排除假设/下一步)

为什么
- 原规则是整目录忽略,把「证据文档」和「大文件抓包」一起挡在版本控制外;
  按 AGENTS.md §1.3,进度与证据必须落盘可追溯,文档应随代码版本化

怎么验证的
- git check-ignore 逐项确认: PROGRESS.md/ANALYSIS.md 入库,
  bg_3.jpg/tdc.js/live/session.json 仍忽略
- git add 预演仅 3 个文本文件, 无二进制混入
- 已检查文档内无 API key 等凭据 (仅几个已过期住宅代理 IP)
2026-10-11 18:10:15 +08:00