test(huya): 风控场景/代理/扫码闭环探针与逆向文档
- probe_huya_login_scene: 各 appId/UA 组合的风控策略探测 - probe_huya_proxy: 走代理源对比直连的风控策略 - probe_huya_qr_bind: 扫码登录闭环(getQrId->scanQrPicNotify-> bindQrLoginUser->tryQrLogin), 实测出有效web会话; 含wupData 结构地图与证书定位实验结论(见 docs/虎牙扫码登录闭环逆向.md)
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
"""探测虎牙密码登录在不同 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())
|
||||
Reference in New Issue
Block a user