#!/usr/bin/env python3 """一键全自动测试: 账号密码 -> 页面cookie(biztoken)。 用法: python tools/full_auto_test.py <账号如hy_xxx或300023887> <密码> 前提: evidence/cert_keycap.json 内有较新鲜的 wupData 信封 (过期则先在设备上跑 scripts/hook_cert_keycap.py 刷新)。 """ import sys, re, json, hashlib, base64, time, struct from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "tools")) import requests from huya_wup_encoder import build_password_login_wup from cert_forge import build_p1, forge_cert def password_login(account: str, password: str) -> bytes: """纯Python护照登录, 返回新鲜cred(114B)。""" uid = account[3:] if account.startswith("hy_") else account raw = (ROOT / "evidence/wup_passwordlogin_taf.bin").read_bytes() mj = json.loads(re.search(rb'(\{"associationId.*?\})', raw).group(1)) ua = re.search(rb'(\{"curl".*?"user_action":\[.*?\]\})', raw).group().decode() sd = re.search(rb'PQwemAN9[A-Za-z0-9+/=]+', raw).group().decode() dev = {"app_version": "13.4.22", "sdk_version": "1.0.80138", "vendor": "xiaomi", "model": "M2102J2SC", "os": "android", "ip": "127.0.0.1", "fingerprint": "02df398797432eadefcc12767119ad5e80999389", "screen": "M2102J2SC,30,11", "width": "1080", "height": "2120", "device_id": "7c5387e0539c023c31c4ff0e807e7256117385ee"} pkt = build_password_login_wup( uid, hashlib.sha1(password.encode()).hexdigest(), sd, "ed0db8334cadd236c00cadf7e11ab5a5", mj["session"], mj["traceId"], ua, dev) r = requests.post( "https://wup.huya.com", data=pkt, headers={"Content-Type": "application/multipart-formdata; charset=UTF-8", "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 11)", "Accept-Encoding": "gzip"}, timeout=15) assert r.status_code == 200, f"登录HTTP {r.status_code}" b = r.content s = b.find(b"\x0a\x0a", 0x40) e = b.find(b"_wup_header") d = b[s:e - 6] m = re.search(rb"\x3d\x00([\x00-\x03])(.)", d) # tag3 SIMPLE_LIST, INT8 len assert m, "响应中未找到cred字段" ln = m.group(2)[0] st = m.start() + 4 cred = d[st:st + ln] assert len(cred) == 114 and cred[:1] == b"\x0a", f"cred异常 {len(cred)}" return cred def forge(cred: bytes) -> str: t = int(time.time() * 1000) & ((1 << 48) - 1) rnd = (t.to_bytes(6, "big") + b"\x00\x00" + ((t << 16) & ((1 << 64) - 1)).to_bytes(8, "big")[2:] + bytes(8))[:20] fp = b"02df398797432eadefcc12767119ad5e80999389" P1 = build_p1(b"5008", fp, cred, rnd=rnd) return forge_cert(P1, key_idx=0x20).hex() def main(): acct, pwd = sys.argv[1], sys.argv[2] print("[1/3] 纯Python密码登录 ...") cred = password_login(acct, pwd) print(f" cred OK: {len(cred)}B {cred[:8].hex()}") print("[2/3] 铸造证书 ...") cert_hex = forge(cred) out = ROOT / "evidence/auto_test_cred.json" out.write_text(json.dumps({"p1": "", "cert_hex": cert_hex, "cred_hex": cred.hex()})) print(f" 证书 OK: {len(cert_hex)//2}B") print("[3/3] 扫码绑定取cookie ...") import subprocess probe = ROOT / "scripts/probe_forge_cert_bind.py" src = probe.read_text() src = re.sub(r'open\("evidence/hycred_[a-z_]+\.json"\)', f'open("{out.as_posix()}")', src) tmp = ROOT / "scripts/_probe_auto.py" tmp.write_text(src) r = subprocess.run([sys.executable, str(tmp)], capture_output=True, text=True) print(r.stdout[-800:]) if "ok\": true" in r.stdout: print("\n🎉 全链路成功: 密码已换出页面cookie(biztoken)!") return 0 print("\n❌ 绑定失败——若40020多为信封过期, 请刷新wupData后重试") return 1 if __name__ == "__main__": raise SystemExit(main())