# -*- coding: utf-8 -*- """把「同会话 collect 明文」与「同会话 tdc.js 的语义名」按位置精确对齐。 这是"不手工配平"的正解,依据都是已确证的链路: * 槽位序 == 清单模块的 require 序(账本 #34/#35,运行时反证过) * 语义名 == tdc_modules.analyze(同一份 tdc.js)(账本 #44,37/37 唯一) * collect 4 段加密(账本 #41),解密后按空格 pad 切回 cd 输入(全部来自**同一次会话**,无需任何人工假设): /tmp/1ms_bridge/session_build.js 运行时 tdc.js(Debugger 取的) /tmp/1ms_bridge/session_collect.json 同会话 TDC.getData(true) + getInfo 用法: .venv/bin/python scripts/verify/align_session.py """ 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")) from tcaptcha.telemetry import modules as tm # noqa: E402 from tcaptcha.telemetry.crypto import split_collect_segments, xtea_decrypt_bytes # noqa: E402 from tcaptcha.telemetry.key import extract_xtea_key_pure # noqa: E402 BUILD = "/tmp/1ms_bridge/session_build.js" SESS = "/tmp/1ms_bridge/session_collect.json" js = open(BUILD, encoding="utf-8", errors="replace").read() print("运行时 tdc.js: %d 字节" % len(js)) key = extract_xtea_key_pure(js)["key"] print("本会话密钥 :", key) print() sess = json.load(open(SESS, encoding="utf-8")) if "collect" not in sess and isinstance(sess.get("data"), dict): sess = sess["data"] # cdp_collect.js 落盘的形状是 {target, ok, data:{...}} collect = unquote(sess["collect"]) # 浏览器侧返回的是 URL-encoded print("collect: %d 字符" % len(collect)) # 解密(4 段拼接:整体解一次即可,因为各段独立 XTEA-ECB) raw = base64.b64decode(collect) dec = xtea_decrypt_bytes(raw, key).decode("latin1") plain = dec.rstrip("\x00") print("解密 %d 字节" % len(dec)) runs = [m.span() for m in re.finditer(r" {2,}", plain)] print("空格 pad 串:", runs) print() if len(runs) == 3: c1, traj, c2, sd = split_collect_segments(plain) print("段: chunk1=%d traj=%d chunk2=%d sd=%d" % (len(c1), len(traj), len(c2), len(sd))) else: c1 = traj = c2 = sd = None print() logical = re.sub(r" {2,}", "", plain) obj = json.loads(logical) cd = obj["cd"] print("cd 槽数:", len(cd)) print() rows = tm.analyze(BUILD) names = [r["name"] for r in rows] mods = [r["module"] for r in rows] print("清单项 %d, 语义名 %d" % (len(mods), len(names))) print() # itoken 占 2 槽(^\\d+:\\d+$),其余 1 槽 multi = [i for i, n in enumerate(names) if n.startswith("itoken")] slots = 0 print("%-2s %-6s %-34s %s" % ("#", "模块", "语义名", "值")) print("-" * 110) out = {} k = 0 for i, (m, nm) in enumerate(zip(mods, names)): n = 2 if i in multi else 1 if k + n > len(cd): break vals = cd[k:k + n] k += n val = vals[0] if n == 1 else vals out[nm] = val print("%-2d %-6d %-34s %s" % (i, m, nm, json.dumps(val, ensure_ascii=False)[:60])) print() print("覆盖槽数 %d / cd %d" % (k, len(cd))) print() Path("/tmp/1ms_bridge/name_values.json").write_text( json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8") print(">>> 语义名 -> 真值 已存 /tmp/1ms_bridge/name_values.json")