做了什么:
- 新增 tcaptcha/tdc_values.py: 37 个语义名的取值生成器(静态画像 / 会话量 /
每次采集量三层)+ build_plain 四段组装 + generate_collect 纯离线出包
- crypto: 新增 generate_telemetry(mode="python_values");轨迹生成统一到
trajectory_points()(首点绝对坐标 + 增量 + 固定收尾 [1,1,12])
- crypto.split_collect_segments 重写: pad 只有 1 个空格时旧 ` {2,}` 判据会漏段,
且 UA 串里的普通空格会假阳性 —— 改为「模 24 算术 + 不落在 JSON 字符串内部 + 结构校验」
- tools/tdc_modules: trajectory 指纹原先指向只做 JSON.stringify 的模块,改为
mouseMoveEvent/touchStartEvent(真轨迹模块),37/37 不变
- client 默认 telemetry_mode 改为 python_values
- 新增 scripts/verify/{align_session,audit_names,verify_values,verify_offline,send_test}.py
与 tools/{cdp_collect,cdp_dump_scripts,tdc_profile}.py
为什么:
- 账本 #40「谁在发 HTTP 是 12 的剩余自变量」被推翻。同 aid/同客户端/同求解器、
答案都对,只换 collect 构造方式: 本次会话构建生成 -> 0 (4/4),
旧模板回填 -> 12 (2/2)。真因是 collect 必须与本次会话构建同源。
怎么验证:
- .venv/bin/python -m tcaptcha.selftest 全绿(含新增四段切分与纯离线闭环回归)
- scripts/verify/verify_values.py 4 份真机明文 152/152 逐槽全等
- scripts/verify/verify_offline.py 逐槽全等 + 分段几何一致
- scripts/verify/send_test.py --mode python_values 1ms 签到页文字点选 errorCode=0 x4
76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""全量配平:37 个语义名 <-> 真机明文的 38 个槽。
|
||
|
||
约束(这些是硬证据,不是猜):
|
||
1. 值内容唯一确定语义名(MacIntel/UTF-8/ANGLE/GgoA.../98k/iframe...)
|
||
2. screen_fingerprint `"1440-2560-2530-24-*-*-|-*"` 反解出
|
||
screen_height=1440 / screen_width=2560 / screen_color_depth=24
|
||
3. itoken 有 `^\\d+:\\d+$` 特征 -> 占 **2 槽**(`641547489,1791732001`)
|
||
4. 37 项 -> 38 槽(仅 itoken 多占 1 槽),可与明文槽数对账
|
||
|
||
输出: 每个语义名的取值 + 置信度,供人工核对。
|
||
"""
|
||
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
|
||
|
||
RAW = "/tmp/1ms_bridge/real_collect_plain.txt"
|
||
raw = open(RAW, encoding="latin1").read().rstrip("\x00")
|
||
cd = json.loads(re.sub(r" {2,}", "", raw))["cd"]
|
||
print("明文 38 槽:")
|
||
for i, v in enumerate(cd):
|
||
print(" %2d %s" % (i, json.dumps(v, ensure_ascii=False)[:78]))
|
||
print()
|
||
|
||
rows = tm.analyze("har/1ms_dianxuan/tdc.js")
|
||
names = [r["name"] for r in rows]
|
||
print("语义名 %d 个" % len(names))
|
||
print()
|
||
|
||
# ---- 按内容给每个槽打标签(不依赖位置)----
|
||
def tags(v):
|
||
t = []
|
||
if isinstance(v, str):
|
||
if "Mozilla" in v: t.append("user_agent")
|
||
if v == "MacIntel": t.append("platform")
|
||
if re.fullmatch(r"[+-]\d\d", v): t.append("timezone_offset")
|
||
if v.upper() == "UTF-8": t.append("charset")
|
||
if "ANGLE" in v: t.append("webgl_renderer")
|
||
if "Inc." in v: t.append("webgl_vendor")
|
||
if v.startswith("GgoA"): t.append("canvas_fingerprint")
|
||
if "rand=" in v and "captcha" in v: t.append("template_url")
|
||
if v == "98k": t.append("constant_98k")
|
||
if v == "iframe": t.append("iframe_detect")
|
||
if "*" in v and "-" in v: t.append("screen_fingerprint")
|
||
if v.startswith("http") and "1ms.run" in v: t.append("location_href")
|
||
elif isinstance(v, list):
|
||
if v and all(isinstance(x, list) and len(x) == 3 for x in v): t.append("trajectory")
|
||
if v and all(isinstance(x, str) for x in v): t.append("languages")
|
||
elif isinstance(v, int) and not isinstance(v, bool):
|
||
if 1_600_000_000 <= v <= 2_100_000_000: t.append("timestamp?")
|
||
elif v > 1_000_000: t.append("bigint?")
|
||
else: t.append("smallint?")
|
||
elif isinstance(v, str) and v == "":
|
||
t.append("emptystr?")
|
||
return t
|
||
|
||
print("逐槽标签:")
|
||
for i, v in enumerate(cd):
|
||
print(" %2d %-22s %s" % (i, ",".join(tags(v)), json.dumps(v, ensure_ascii=False)[:50]))
|
||
print()
|
||
|
||
# ---- 反解 screen_fingerprint 的约束 ----
|
||
fp = next((v for v in cd if isinstance(v, str) and "*" in v and "-" in v), None)
|
||
if fp:
|
||
p = fp.split("-")
|
||
print("screen_fingerprint = %r -> 高%s 宽%s 色深%s" % (fp, p[0], p[1], p[3]))
|
||
for target, want in (("screen_height", int(p[0])), ("screen_width", int(p[1])),
|
||
("screen_color_depth", int(p[3]))):
|
||
idxs = [i for i, v in enumerate(cd) if v == want]
|
||
print(" %-20s 期望 %s -> 明文里出现在槽 %s" % (target, want, idxs))
|