- probe_huya_login_scene: 各 appId/UA 组合的风控策略探测 - probe_huya_proxy: 走代理源对比直连的风控策略 - probe_huya_qr_bind: 扫码登录闭环(getQrId->scanQrPicNotify-> bindQrLoginUser->tryQrLogin), 实测出有效web会话; 含wupData 结构地图与证书定位实验结论(见 docs/虎牙扫码登录闭环逆向.md)
106 lines
3.8 KiB
Python
106 lines
3.8 KiB
Python
"""探测虎牙密码登录在不同 appId/UA 组合下的风控策略。
|
|
|
|
只走到「触发风控」这一步,打印风控策略(strategy 值 + data URL 路径),
|
|
不真正提交滑块/短信/扫码验证,也不写库。
|
|
|
|
用法:
|
|
.venv/bin/python scripts/probe_huya_login_scene.py <账号> <明文密码> [--app-id 5002|5008] [--ua desktop|android]
|
|
|
|
会依次测试多组组合,对比风控返回的是:
|
|
qr_auth -> 扫码验证(无法自动化)
|
|
dx_auth -> 短信验证(无法自动化)
|
|
pt_auth / get3 / slider -> 滑块/点选(可自动化)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
from core.huya.login import (
|
|
APP_ID,
|
|
APP_ID_MOBILE,
|
|
APP_UA_MOBILE,
|
|
DEFAULT_UA,
|
|
HuyaPasswordLogin,
|
|
)
|
|
|
|
|
|
def classify_strategy(payload: dict) -> str:
|
|
"""从登录响应里识别风控策略类型。"""
|
|
data = payload.get("data") or {}
|
|
strategies = data.get("strategys") or []
|
|
if not strategies:
|
|
return "unknown"
|
|
urls = [str(s.get("data") or "") for s in strategies]
|
|
joined = " ".join(urls).lower()
|
|
strategy_values = [str(s.get("strategy")) for s in strategies]
|
|
if "qr_auth" in joined or "64" in strategy_values:
|
|
return "qr_auth(扫码)"
|
|
if "dx_auth" in joined:
|
|
return "dx_auth(短信)"
|
|
if "pt_auth" in joined or "get3" in joined or "slider" in joined:
|
|
return "pt_auth(滑块)"
|
|
return f"其他(strategy={','.join(strategy_values)})"
|
|
|
|
|
|
def probe(username: str, password: str, app_id: str, ua: str | None = None) -> dict:
|
|
login = HuyaPasswordLogin(username=username, password=password, app_id=app_id, ua=ua)
|
|
ua_label = login.ua[:40]
|
|
print(f"\n=== app_id={app_id} | app_sign={login.app_sign[:8]} | ua={ua_label}... ===")
|
|
login.prepare_device()
|
|
login.init_udb_middle()
|
|
payload = login._password_login_once()
|
|
|
|
return_code = int(payload.get("returnCode") or 0)
|
|
print(f"returnCode={return_code}")
|
|
if return_code == 0:
|
|
print(" → 登录成功(无需验证)")
|
|
return payload
|
|
if return_code in (10030, 10039):
|
|
kind = classify_strategy(payload)
|
|
print(f" → 风控策略: {kind}")
|
|
data = payload.get("data") or {}
|
|
for s in data.get("strategys") or []:
|
|
print(f" strategy={s.get('strategy')} dataType={s.get('dataType')}")
|
|
url = str(s.get("data") or "")
|
|
print(f" data_url={url.split('?')[0]}")
|
|
return payload
|
|
print(f" message={payload.get('message')} desc={payload.get('description')}")
|
|
return payload
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("username")
|
|
parser.add_argument("password")
|
|
parser.add_argument("--app-id", default=None, help="只测单个 appId")
|
|
parser.add_argument("--ua", default=None, choices=["desktop", "android"], help="只测单种 UA")
|
|
args = parser.parse_args()
|
|
|
|
combos = []
|
|
app_ids = [args.app_id] if args.app_id else [APP_ID, APP_ID_MOBILE]
|
|
for app_id in app_ids:
|
|
if args.ua == "desktop":
|
|
combos.append((app_id, DEFAULT_UA))
|
|
elif args.ua == "android":
|
|
combos.append((app_id, APP_UA_MOBILE))
|
|
else:
|
|
# 默认每组都测;5002 也测 Android UA(验证设备画像自洽假设)
|
|
if app_id == APP_ID_MOBILE:
|
|
combos.append((app_id, None)) # None -> 自动选 Android UA
|
|
else:
|
|
combos.append((app_id, None)) # None -> 桌面 UA
|
|
combos.append((app_id, APP_UA_MOBILE)) # 强制 Android UA
|
|
|
|
for app_id, ua in combos:
|
|
try:
|
|
probe(args.username, args.password, app_id, ua)
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f" !! 探测异常: {type(exc).__name__}: {exc}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|