Files
hello_js_reverse_skill/scripts/verify/verify_fp_consistency.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

68 lines
2.7 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 -*-
"""验证字符串+结构指纹方案的**跨构建一致性** —— 只基于 har/online/ 全新样本。
案例 §5.3 的验收标准: "3 个 tdc.js 版本、37 个模块、全部正确识别,无一 unknown"。
本脚本再进一步:不仅要求"无 unknown",还要求
① 每份构建内部 37 项都有名字
② 各构建之间的**语义名集合**一致(说明识别的是同一批采集器)
③ 语义名 -> 每项在 cd 里的**相对位置**可交叉核对
"""
import glob
import sys
from collections import Counter
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "tools"))
import tdc_modules as tm # noqa: E402
BATCH = sorted(glob.glob(str(ROOT / "har" / "online" / "*" / "tdc_*.js")))
print("全新在线样本 %d 份(只使用这批数据)" % len(BATCH))
print()
results = {}
for f in BATCH:
rows = tm.analyze(f)
names = [r["name"] for r in rows]
results[f] = rows
unk = sum(1 for n in names if n == "unknown")
via_struct = sum(1 for r in rows if r.get("via_struct"))
via_deleg = sum(1 for r in rows if r.get("via_delegate"))
print(" %-24s 项%2d unknown %d | 字符串直判 %2d 委派链 %d 结构兜底 %d"
% (f.split("/")[-1], len(rows), unk,
len(rows) - via_struct - via_deleg, via_deleg, via_struct))
print()
keys = list(results)
base = Counter(r["name"] for r in results[keys[0]])
print("以 %s 为基准:%d 个语义名(去重 %d)"
% (keys[0].split("/")[-1], len(base), len(set(base))))
print()
for f in keys[1:]:
c = Counter(r["name"] for r in results[f])
same = sum((base & c).values())
only_base = sum((base - c).values())
only_it = sum((c - base).values())
print(" vs %-22s 相同 %2d | 仅基准有 %2d | 仅有它有 %2d"
% (f.split("/")[-1], same, only_base, only_it))
print()
common = set(base)
for f in keys[1:]:
common &= set(Counter(r["name"] for r in results[f]))
allnames = set()
for f in keys:
allnames |= set(r["name"] for r in results[f])
print(">>> 所有构建**共同**识别出的语义名: %d 个" % len(common))
print(">>> 出现过的语义名总数 : %d 个" % len(allnames))
print(">>> 差异名:", sorted(allnames - common) if allnames - common else "无(完全一致)")
print()
# 唯一性检查:单构建内是否有重名(重名 = 锚点有歧义)
print("单构建内重名检查(重名说明该锚点有歧义):")
for f in keys:
c = Counter(r["name"] for r in results[f])
dup = {k: v for k, v in c.items() if v > 1}
print(" %-24s 重名 %d 组 %s"
% (f.split("/")[-1], len(dup), dict(sorted(dup.items()))))