Files
hello_js_reverse_skill/tools/tdc_profile.py
T
yml2213 51dff870cf feat(collect): 纯离线生成打通 1ms 签到文字点选 —— errorCode=0 收口
做了什么:
- 新增 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
2026-10-12 03:38:18 +08:00

203 lines
8.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 -*-
"""
tdc_profile.py —— 设备画像(fingerprint profile)的**提取**与**定义**。
背景(账本 §五 第 0 项,`har/1ms_dianxuan/PROGRESS.md`):
「纯离线生成」的唯一阻塞点,是需要为每个**语义名**提供取值。
多数语义名是静态环境量(UA / 屏幕 / 时区 / 语言 / 核数 / charset /
WebGL / Canvas),可以从一份真机明文里**按值的内容反查**出来。
为什么不靠"明文属于哪份构建":
真机明文的槽顺序 = **它自己那次会话构建**的 require 顺序,而那份 tdc.js
通常没保存(本项目实测:自洽性打分全为负,即明文不属于手上任何构建)。
但**值本身携带强语义**:`"MacIntel"` 只可能是 platform,`"UTF-8"` 只可能是
charset,`"ANGLE (Apple, …)"` 只可能是 webgl_renderer。所以按内容反查
比按位置配对更稳,也不需要那份丢失的构建。
镜像自洽(案例 §6.3): 一套画像内的值必须互相一致 ——
screen_fingerprint `"1440-2560-2530-24-…"` 里的 1440/2560/24 应当分别等于
screen_width / screen_height / screen_color_depth(字段序 = 宽-高-可用高-色深)。
本模块会做这个交叉校验。
标准画像请以 `tcaptcha/tdc_values.py::DEVICE_PROFILE` 为准(那里是 37 个语义名
全部对齐后的产物);本文件只是**从明文按值反查**的开发期工具。
用法:
# 从一份已解密的真机明文提取画像基线
.venv/bin/python tools/tdc_profile.py from-sample <明文文件> [--json 输出]
# 打印内置画像
.venv/bin/python tools/tdc_profile.py show
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
ROOT = Path(__file__).resolve().parents[1]
# --------------------------------------------------------------------------- #
# 内容反查:值 -> 语义名(可能多个候选,按置信度排序) #
# --------------------------------------------------------------------------- #
def guess_names(v: Any) -> List[str]:
"""按**值的内容**反推它可能出自哪个语义名(不依赖槽位置)。"""
out: List[str] = []
if isinstance(v, str):
if "Mozilla" in v and "AppleWebKit" in v:
out.append("user_agent")
if v == "MacIntel":
out.append("platform")
if re.fullmatch(r"[+-]\d{2}(:\d{2})?", v):
out.append("timezone_offset")
if v.upper() in ("UTF-8", "UTF8"):
out.append("charset")
if "ANGLE" in v or "Metal Renderer" in v or "OpenGL" in v:
out.append("webgl_renderer")
if "Inc." in v or ("Google" in v and "Apple" in v):
out.append("webgl_vendor")
if v.startswith("GgoA") and len(v) > 40:
out.append("canvas_fingerprint")
if "captcha" in v and "rand=" in v:
out.append("template_url")
if v == "98k":
out.append("constant_98k")
if v == "iframe":
out.append("iframe_detect")
if "-" in v and "*" in v and re.fullmatch(r"[\d\-*|]+", v):
out.append("screen_fingerprint")
if v.startswith("http") and "1ms.run" in v:
out.append("entry_url")
elif v.startswith("http"):
out.append("location_href")
elif isinstance(v, list):
if v and all(isinstance(x, list) and len(x) == 3 for x in v):
out.append("trajectory")
if v and all(isinstance(x, str) for x in v) and 1 <= len(v) <= 4:
out.append("languages")
elif isinstance(v, int) and not isinstance(v, bool):
if 1_600_000_000 <= v <= 2_100_000_000:
out.append("timestamp")
return out
# --------------------------------------------------------------------------- #
# 内置画像(MacBook 画像基线;取自真机样本,可按需替换/随机化) #
# --------------------------------------------------------------------------- #
DEFAULT_PROFILE: Dict[str, Any] = {
"name": "Mac / Chrome",
# --- 明文里能直接看到的静态量 ---
"user_agent": ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36"),
"platform": "MacIntel",
"languages": ["zh-CN", "zh"],
"timezone_offset": "+08",
"charset": "UTF-8",
"hardware_concurrency": 10,
# ⚠️ 方向别弄反:真机 screen.width == 1440、screen.height == 2560
# (2026-10-12 浏览器实测;1440x2560 竖屏,availHeight 2530)
"screen_width": 1440,
"screen_height": 2560,
"screen_color_depth": 24,
"screen_fingerprint": "1440-2560-2530-24-*-*-|-*",
"webgl_renderer": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)",
"webgl_vendor": "Google Inc. (Apple)",
"canvas_fingerprint": ("GgoAAAANSUhEUgAAASwAAACWCAYAAATf3v8bh+ryWcfSBSLkmN3N1"
"PQinPL7Bi3TG0qE2CogAAAABJRU5ErkJggg=="),
"constant_98k": "98k",
"cookie_enabled": 1,
"touch_detect": 2, # 真机实测(该机 maxTouchPoints 为 0,值仍是 2)
"viewport_size": [360, 360],
}
def cross_check(profile: Dict[str, Any]) -> List[str]:
"""做镜像自洽性交叉校验(案例 §6.3)。返回不一致项列表。"""
problems: List[str] = []
fp = profile.get("screen_fingerprint") or ""
parts = fp.split("-")
if len(parts) >= 4:
try:
# 字段序 = 宽-高-可用高-色深(2026-10-12 与浏览器真值 1440/2560/2530/24 对齐;
# 旧代码把它当成 高-宽-?-色深,方向是反的)
w, h, _avail, depth = int(parts[0]), int(parts[1]), int(parts[2]), int(parts[3])
if h != profile.get("screen_height"):
problems.append("screen_fingerprint 的高度 %d != screen_height %s"
% (h, profile.get("screen_height")))
if w != profile.get("screen_width"):
problems.append("screen_fingerprint 的宽度 %d != screen_width %s"
% (w, profile.get("screen_width")))
if depth != profile.get("screen_color_depth"):
problems.append("screen_fingerprint 的色深 %d != screen_color_depth %s"
% (depth, profile.get("screen_color_depth")))
except ValueError:
problems.append("screen_fingerprint 格式无法解析: %r" % fp)
ua = profile.get("user_agent") or ""
if "Chrome/" in ua and "Macintosh" in ua and profile.get("platform") != "MacIntel":
problems.append("UA 说是 Mac,但 platform=%r" % profile.get("platform"))
return problems
def from_sample(path: str) -> Tuple[Dict[str, Any], List[Dict]]:
"""从一份真机明文,按内容反查提取画像基线。
返回 (识别到的状态量, 未识别的槽列表)。
"""
raw = open(path, encoding="latin1").read().rstrip("\x00")
logical = re.sub(r" {2,}", "", raw)
cd = json.loads(logical)["cd"]
found: Dict[str, Any] = {}
unidentified: List[Dict] = []
for i, v in enumerate(cd):
cands = guess_names(v)
if len(cands) == 1:
found[cands[0]] = v
elif cands:
found.setdefault(cands[0], v)
else:
unidentified.append({"slot": i, "value": v})
return found, unidentified
def main() -> int:
ap = argparse.ArgumentParser(description="TDC 设备画像提取/查看")
sub = ap.add_subparsers(dest="cmd", required=True)
p1 = sub.add_parser("from-sample", help="从真机明文提取画像基线")
p1.add_argument("plain")
p1.add_argument("--json", default=None)
sub.add_parser("show", help="打印内置画像")
args = ap.parse_args()
if args.cmd == "show":
print(json.dumps(DEFAULT_PROFILE, ensure_ascii=False, indent=1))
probs = cross_check(DEFAULT_PROFILE)
print()
print("交叉校验:", "全部自洽" if not probs else probs)
return 0
found, unk = from_sample(args.plain)
print("从 %s 反查到的状态量:" % Path(args.plain).name)
for k, v in found.items():
print(" %-22s %s" % (k, json.dumps(v, ensure_ascii=False)[:70]))
print()
print("未识别的槽: %d 个" % len(unk))
for u in unk:
print(" 槽%-3d %s" % (u["slot"], json.dumps(u["value"], ensure_ascii=False)[:60]))
print()
probs = cross_check(found)
print("交叉校验:", "全部自洽 ✓" if not probs else probs)
if args.json:
Path(args.json).write_text(json.dumps(found, ensure_ascii=False, indent=1),
encoding="utf-8")
print("画像 -> %s" % args.json)
return 0
if __name__ == "__main__":
raise SystemExit(main())