- 新增 fingerprint/: node仿真Android WebView运行官方hydevice.js(含WASM加密), 产出与真机同构的高信任sdid - 新增 device_fingerprint.py: py编排node runner获取sdid, 失败自动降级旧无指纹流程 - login.py: prepare_device优先高信任指纹; 增加App场景常量(appId=5008, appSign=md5(appId+verCode+appKey)[:8], 逆向自APK UdbNetHelper) - solver: 支持appId/page_url/use_touch_events参数化(App模式); get3使用csid会话id; 识别qr_auth扫码(冷却)与dx_auth短信策略并抛专用异常
132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
"""虎牙设备指纹:通过 node 运行 hydevice.js 生成高信任 sdid。
|
||
|
||
流程与真机 App WebView 一致:
|
||
df/token -> hydevice 采集+WASM 加密 -> df/collect -> sdid
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import shutil
|
||
import subprocess
|
||
import tempfile
|
||
from collections.abc import Mapping
|
||
from dataclasses import dataclass
|
||
from pathlib import Path
|
||
|
||
import requests
|
||
from loguru import logger
|
||
|
||
FINGERPRINT_DIR = Path(__file__).parent / "fingerprint"
|
||
RUNNER_JS = FINGERPRINT_DIR / "runner.js"
|
||
SDID_PREFIX = "__SDID__"
|
||
|
||
DEFAULT_TIMEOUT = (8, 40)
|
||
|
||
|
||
class HuyaFingerprintError(RuntimeError):
|
||
"""虎牙设备指纹生成失败。"""
|
||
|
||
|
||
@dataclass
|
||
class HuyaSdidResult:
|
||
"""sdid 获取结果。"""
|
||
|
||
sdid: str = ""
|
||
source: str = ""
|
||
message: str = ""
|
||
|
||
|
||
def _run_node_runner(state_dir: Path, app_id: str, timeout: tuple[float, float]) -> str:
|
||
"""调用 node runner,返回 sdid。"""
|
||
node_bin = shutil.which("node")
|
||
if not node_bin:
|
||
raise HuyaFingerprintError("未找到 node 可执行文件(hydevice.js 需要 JS 引擎)")
|
||
|
||
connect_timeout, read_timeout = timeout
|
||
total_timeout = int(connect_timeout + read_timeout + 10)
|
||
try:
|
||
proc = subprocess.run(
|
||
[node_bin, str(RUNNER_JS), str(state_dir), app_id],
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=total_timeout,
|
||
cwd=str(FINGERPRINT_DIR),
|
||
)
|
||
except subprocess.TimeoutExpired as exc:
|
||
raise HuyaFingerprintError(f"hydevice runner 超时({total_timeout}s)") from exc
|
||
|
||
for line in (proc.stdout or "").splitlines():
|
||
line = line.strip()
|
||
if line.startswith(SDID_PREFIX) and len(line) > len(SDID_PREFIX) + 20:
|
||
return line[len(SDID_PREFIX):]
|
||
stderr_tail = (proc.stderr or "").strip().splitlines()
|
||
detail = stderr_tail[-1][:120] if stderr_tail else f"exit={proc.returncode}"
|
||
raise HuyaFingerprintError(f"hydevice runner 未返回 sdid: {detail}")
|
||
|
||
|
||
def _fallback_sdid(session: requests.Session, timeout) -> str:
|
||
"""旧版降级:仅 token+collect,无指纹(低信任)。"""
|
||
token_url = "https://df.huya.com/web/df/token"
|
||
collect_url = "https://df.huya.com/web/df/collect"
|
||
token_res = session.post(
|
||
token_url,
|
||
json={"encryptVersion": "1.0.1", "fingerprintVersion": "1.2.45"},
|
||
timeout=timeout,
|
||
)
|
||
token_res.raise_for_status()
|
||
token = token_res.json().get("data", {}).get("token", "")
|
||
if not token:
|
||
raise HuyaFingerprintError(f"获取虎牙 df token 失败: {token_res.text[:120]}")
|
||
collect_res = session.post(collect_url, json={"token": token}, timeout=timeout)
|
||
collect_res.raise_for_status()
|
||
return collect_res.json().get("data", {}).get("sdid", "")
|
||
|
||
|
||
def get_huya_sdid(
|
||
app_id: str = "5008",
|
||
state_dir: str | Path | None = None,
|
||
session: requests.Session | None = None,
|
||
proxies: Mapping | None = None,
|
||
timeout: tuple[float, float] = DEFAULT_TIMEOUT,
|
||
allow_fallback: bool = True,
|
||
) -> HuyaSdidResult:
|
||
"""获取虎牙高信任 sdid;node 链路失败时可选降级为低信任指纹。
|
||
|
||
Args:
|
||
app_id: 虎牙应用 id,App 场景 5008。
|
||
state_dir: localStorage 持久化目录(保持设备一致性),None 用临时目录。
|
||
allow_fallback: node 失败时是否降级为旧版无指纹方式。
|
||
Returns:
|
||
HuyaSdidResult(sdid, source, message),source 为 fingerprint/fallback/none。
|
||
"""
|
||
del session, proxies # node runner 自行发请求;保留参数便于未来代理透传
|
||
if state_dir is None:
|
||
state_dir = Path(tempfile.gettempdir()) / "huya_fp_state"
|
||
state_dir = Path(state_dir)
|
||
try:
|
||
sdid = _run_node_runner(state_dir, app_id, timeout)
|
||
if sdid:
|
||
logger.debug("虎牙设备指纹成功(node): sdid={}...", sdid[:24])
|
||
return HuyaSdidResult(sdid=sdid, source="fingerprint")
|
||
except HuyaFingerprintError as exc:
|
||
logger.warning("虎牙 hydevice 指纹失败: {}", exc)
|
||
if not allow_fallback:
|
||
raise
|
||
|
||
if allow_fallback:
|
||
try:
|
||
with requests.Session() as s:
|
||
s.trust_env = False
|
||
sdid = _fallback_sdid(s, timeout)
|
||
if sdid:
|
||
logger.debug("虎牙设备指纹降级(无指纹): sdid={}...", sdid[:24])
|
||
return HuyaSdidResult(
|
||
sdid=sdid,
|
||
source="fallback",
|
||
message="低信任指纹(降级)",
|
||
)
|
||
except Exception as exc: # noqa: BLE001
|
||
logger.warning("虎牙降级指纹也失败: {}", exc)
|
||
|
||
return HuyaSdidResult(source="none", message="未能获取 sdid")
|