# -*- coding: utf-8 -*- """把真机明文的值,与「语义名」精确配对。 难点:真机明文的槽顺序 = **它自己那次构建**的 require 顺序, 而我手上只有 har/online/ 等若干构建;不知道明文属于哪一份。 解法(自洽性打分):把明文的值**按每个候选构建的语义名顺序**标注一遍, 再看标注结果是否语义自洽(platform 的位置该是 "MacIntel"、user_agent 该含 "Mozilla"、charset 该是 "UTF-8" …)。自洽度最高者即为它所属的构建; 若都不匹配,也能从"哪些语义名被正确喂到"反推出每个名字的真实取值。 用法: .venv/bin/python scripts/verify/label_plaintext.py <明文文件> <若干 tdc.js> """ import glob import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "tools")) import tdc_modules as tm # noqa: E402 # ---- 语义名 -> 期望值特征(这就是"画像"的定义雏形)---- def likes(name: str, v) -> str: """值是否与语义名相容。返回 OK / NO / ??(?? = 该名字无法从值判断)""" s = json.dumps(v, ensure_ascii=False) if name.startswith("user_agent"): return "OK" if isinstance(v, str) and "Mozilla" in v else "NO" if name == "platform": return "OK" if isinstance(v, str) and v and " " not in v else "NO" if name == "languages": return "OK" if isinstance(v, list) and v and isinstance(v[0], str) else "NO" if name == "hardware_concurrency": return "OK" if isinstance(v, int) and 2 <= v <= 64 else "NO" if name == "screen_width": return "OK" if isinstance(v, int) and v > 500 else "NO" if name == "screen_height": return "OK" if isinstance(v, int) and v > 300 else "NO" if name == "screen_color_depth": return "OK" if isinstance(v, int) and 16 <= v <= 48 else "NO" if name == "screen_fingerprint": return "OK" if isinstance(v, str) and "-" in v and "*" in v else "NO" if name == "timezone_offset": return "OK" if isinstance(v, str) and re.fullmatch(r"[+-]\d\d", v) else "NO" if name == "charset": return "OK" if isinstance(v, str) and "UTF" in v.upper() else "NO" if name == "webgl_renderer": return "OK" if isinstance(v, str) and ("ANGLE" in v or "Renderer" in v) else "NO" if name == "webgl_vendor": return "OK" if isinstance(v, str) and ("Inc." in v or "Google" in v) else "NO" if name == "canvas_fingerprint": return "OK" if isinstance(v, str) and len(v) > 40 else "NO" if name == "constant_98k": return "OK" if v == "98k" else "NO" if name == "template_url": return "OK" if isinstance(v, str) and "captcha" in v and "rand=" in v else "NO" if name in ("location_href", "referrer"): return "OK" if isinstance(v, str) and v.startswith("http") else "??" if name == "trajectory": return "OK" if (isinstance(v, list) and v and all(isinstance(x, list) and len(x) == 3 for x in v)) else "NO" return "??" def main() -> int: if len(sys.argv) < 3: print(__doc__) return 2 plain = open(sys.argv[1], encoding="latin1").read().rstrip("\x00") logical = re.sub(r" {2,}", "", plain) cd = json.loads(logical)["cd"] print("明文 cd 槽数: %d" % len(cd)) print() cands = [] for a in sys.argv[2:]: cands += sorted(glob.glob(a)) if not cands: print("没有候选构建") return 1 def assign(names, nslots): """按语义名顺序把 nslots 个槽分配给各名字(交错推进)。""" out = {} k = 0 for nm in names: if k >= nslots: break out.setdefault(nm, []).append(cd[k]) k += 1 return out best = None for f in cands: rows = tm.analyze(f) names = [r["name"] for r in rows] # 假设除 itoken(占 2 槽)外都是 1 槽 itok = [i for i, n in enumerate(names) if n.startswith("itoken")] n_extra = 1 if itok else 0 # 逐个候选:把 cd 的槽按名字顺序切(itoken 那两个槽合并看第一个) mapping = {} k = 0 for i, nm in enumerate(names): if k >= len(cd): break if i in itok: mapping[nm] = cd[k:k + 2] k += 2 else: mapping[nm] = [cd[k]] k += 1 ok = no = unk = 0 for nm, vals in mapping.items(): st = likes(nm, vals[0]) ok += st == "OK" no += st == "NO" unk += st == "??" score = ok - no print(" %-46s OK=%2d NO=%2d ??=%2d 分=%d" % (Path(f).name, ok, no, unk, score)) if best is None or score > best[0]: best = (score, f, mapping, ok, no) print() print(">>> 最自洽的构建: %s (OK=%d NO=%d)" % (Path(best[1]).name, best[3], best[4])) print() print("该假设下的 name -> 值 标注:") for nm, vals in best[2].items(): st = likes(nm, vals[0]) mark = {"OK": " ", "NO": "!", "??": "?"}[st] print(" %s %-40s %s" % (mark, nm, json.dumps(vals, ensure_ascii=False)[:70])) return 0 if __name__ == "__main__": raise SystemExit(main())