# -*- coding: utf-8 -*- """audit_names.py —— 把「语义名 / 模块结构 / 明文槽值」放在一张表里逐个核对。 为什么需要(2026-10-12 实测): 对齐是**按位置**做的(require 序 == 槽位序),所以一旦某个语义名识别错了, 它对应的**真值就会被记到错误的名字下**。tdc_profile 之前就把「轨迹」的 空数组记成了 trajectory,而真正的轨迹(含 mousemove/touchmove 的大模块) 却在另一个名字下。名字错 = 生成器错,必须逐个核对。 本脚本给出 4 条独立证据并排显示: 1. 指纹命中的字符串(tdc_modules 的规则) 2. 结构签名(entry/get 指令数、closure 种类) 3. 该模块**自身代码里**是否出现鼠标/触摸/时钟等关键串 4. 同会话明文里该槽的**真值** 用法: .venv/bin/python scripts/verify/audit_names.py \ --build /tmp/1ms_bridge/session_build.js \ --collect /tmp/1ms_bridge/session_collect.json """ import argparse import base64 import json import re import sys from pathlib import Path from urllib.parse import unquote ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "tools")) import tdc_modules as tm # noqa: E402 import tdc_program as tp # noqa: E402 from tcaptcha.crypto import xtea_decrypt_bytes # noqa: E402 from tcaptcha.tdc_key import extract_xtea_key_pure # noqa: E402 # 用于「这个模块到底像在采什么」的旁证关键词 HINTS = { "鼠标/触摸轨迹": ["mousemove", "mouseMoveEvent", "touchmove", "touchStartEvent", "mousedown", "pointermove", "clientX", "pageX"], "时钟": ["getTime", "Date", "performance"], "屏幕": ["screen", "colorDepth", "availHeight"], "WebGL": ["UNMASKED", "getExtension", "getParameter"], "Canvas": ["toDataURL", "getImageData", "fillText"], "UA/平台": ["userAgent", "platform"], "时区/语言": ["getTimezoneOffset", "languages"], "自动化检测": ["webdriver", "$cdc_", "__webdriver_script_fn"], "存储": ["cookie", "localStorage", "sessionStorage"], "iframe/DOM": ["contentWindow", "createElement", "appendChild"], } def parse_cd(plain: str): """把明文切成 (cd 元素列表带跨度, 顶层对象)。pad(>=2 空格)先移除。""" logical = re.sub(r" {2,}", "", plain) dec = json.JSONDecoder() i = logical.index("[", logical.index('"cd"')) spans, j = [], i + 1 while True: while logical[j] in " \t\r\n,": j += 1 if logical[j] == "]": break val, end = dec.raw_decode(logical, j) spans.append((val, j, end)) j = end return spans, json.loads(logical) def slot_names(rows): """把 37 个清单项展开成 38 个槽的语义名(itoken 占 2 槽)。""" out = [] for i, r in enumerate(rows): n = 2 if r["name"].startswith("itoken") else 1 out.extend([r["name"]] * n) return out def main() -> int: ap = argparse.ArgumentParser(description="语义名 / 结构 / 槽值 联合审计") ap.add_argument("--build", default="/tmp/1ms_bridge/session_build.js") ap.add_argument("--collect", default="/tmp/1ms_bridge/session_collect.json") ap.add_argument("--json", default="/tmp/1ms_bridge/audit.json") args = ap.parse_args() js = open(args.build, encoding="utf-8", errors="replace").read() key = extract_xtea_key_pure(js)["key"] sess = json.load(open(args.collect, encoding="utf-8")) if "collect" not in sess and isinstance(sess.get("data"), dict): sess = sess["data"] plain = xtea_decrypt_bytes(base64.b64decode(unquote(sess["collect"])), key) \ .decode("latin1").rstrip("\x00") spans, _obj = parse_cd(plain) pads = [m.span() for m in re.finditer(r" {2,}", plain)] p = tp.load(args.build) rows = tm.analyze_prog(p) names = slot_names(rows) print("构建 %s (%d 字节) 明文 %d 字节 cd %d 槽 清单 %d 项" % (Path(args.build).name, len(js), len(plain), len(spans), len(rows))) print("pad 段边界:", [(a, b, b - a) for a, b in pads]) pad_ends = {a for a, _b in pads} print() out = [] print("%-3s %-5s %-36s %-9s %-22s %s" % ("项", "模块", "语义名", "结构", "槽值", "旁证")) print("-" * 132) for i, r in enumerate(rows): n = 2 if r["name"].startswith("itoken") else 1 k = sum(2 if x["name"].startswith("itoken") else 1 for x in rows[:i]) vals = [spans[k + t][0] for t in range(n)] val = vals[0] if n == 1 else vals st = r["struct"] tag = "e%s/g%s%s" % (st["entry_instr"], st["get_instr"], "_call" if st["call_func"] else "") s = set(r["strings"]) hints = [k2 for k2, ks in HINTS.items() if any(x in s for x in ks)] # 该槽的结束位置是否正好是 pad 边界? end = spans[k + n - 1][2] mark = " <<< 段边界" if end in pad_ends else "" print("%-3d %-5d %-36s %-9s %-22s %s%s" % (r["item"], r["module"], r["name"], tag, json.dumps(val, ensure_ascii=False)[:20], ",".join(hints), mark)) out.append({ "item": r["item"], "module": r["module"], "name": r["name"], "struct": st, "tag": tag, "delegate": r["delegate"], "strings": r["strings"], "value": val, "span": [spans[k][1], end], "segment_boundary": end in pad_ends, }) Path(args.json).write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8") print() print(">>> %s" % args.json) return 0 if __name__ == "__main__": raise SystemExit(main())