From d1a2b6aa8b3443ed2df141694a2236fbc3f75fc6 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 25 Aug 2026 19:14:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(huya):=20=E4=B8=80=E9=94=AE=E5=85=A8?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=B5=8B=E8=AF=95=E8=84=9A=E6=9C=AC(?= =?UTF-8?q?=E5=AF=86=E7=A0=81->cookie)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/full_auto_test.py | 98 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100755 tools/full_auto_test.py diff --git a/tools/full_auto_test.py b/tools/full_auto_test.py new file mode 100755 index 0000000..18534f1 --- /dev/null +++ b/tools/full_auto_test.py @@ -0,0 +1,98 @@ +#!/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())