Files
hello_js_reverse_skill/scripts/verify/verify_40_9_disamb.py
T
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

65 lines
2.0 KiB
Python
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.
# -*- coding: utf-8 -*-
"""验证 struct_40_9 的消歧特征是否跨构建稳定。
判别:
A 组: 闭包体最后写入 **空串** (PUSH_EMPTY_STR; SET_PROP)
B 组: 闭包体最后写入 **随机 int32** (PUSH_IMM <big>; SET_PROP)
"""
import glob
import sys
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "tools"))
import tdc_modules as tm # noqa: E402
import tdc_program as tp # noqa: E402
BATCH = sorted(glob.glob(str(ROOT / "har" / "online" / "*" / "tdc_*.js")))
def closure_body(p, entry):
"""取模块唯一闭包的 target 体。"""
cls = [c for c in p.closures if p.owner(c.pc) == entry]
if not cls:
return None
return p.function_code(cls[0].target)
def disamb(p, entry):
"""返回 'empty' | 'rand' | None"""
body = closure_body(p, entry)
if body is None:
return None
imms = [x["operands"][0] for x in body
if x["name"] == "PUSH_IMM" and x["operands"]]
big = [v for v in imms if v > 1_000_000]
if big:
return "rand"
return "empty"
stats = defaultdict(lambda: defaultdict(list))
for f in BATCH:
p = tp.load(f)
reg = dict(p.module_registry(0))
rows = tm.analyze(f)
for r in rows:
if r["name"] != "struct_40_9":
continue
d = disamb(p, reg[r["module"]])
stats[f.split("/")[-1]][d or "?"].append((r["item"], r["module"]))
print("%-24s %-8s %s" % ("构建", "判别", "项(模块)"))
print("-" * 70)
ok = True
for f, groups in stats.items():
for d, members in sorted(groups.items()):
print("%-24s %-8s %s" % (f, d, members))
# 每份构建必须 empty 1 个 + rand 1 个
if len(groups.get("empty", [])) != 1 or len(groups.get("rand", [])) != 1:
ok = False
print()
print(">>> 每份构建都是 empty×1 + rand×1 :", ok)
print(">>> 结论:", "可静态消歧(写入空串 vs 写入随机 int32)" if ok else "仍不可分")