#!/usr/bin/env python3 """一键: 账号密码 -> 完整网页Cookie全套 (纯协议, nonce本地重算). 2026-08-26 升级: nonce 生成机制已破解(tools/nonce_forge.py), 不再需要复用设备信封的原nonce —— 任意账号用 目标uid+k1 本地重算即过bind! 依赖: k1 设备常量(evidence/nonce_k1.json 或默认值, 不随账号变)。 多账号设备隔离 (2026-08-26 实测): 每账号生成独立设备画像 (tools/huya_device_profile.get_profile) —— fingerprint/device_id/机型随机, hdid+app_version 为设备注册硬锚共用一个金样本。首次换画像触发 safe_auth 滑块自动过验。证书 P1 的 fingerprint 用该账号画像的指纹, 保持全链一致。 用法: .venv/bin/python tools/full_web_cookie.py <账号> <密码> .venv/bin/python tools/full_web_cookie.py <账号> <密码> --new-device 产出: 打印全部cookie并写 evidence/web_cookies_full.txt """ import sys, json, re, hashlib, time, base64 ACCT, PWD = sys.argv[1], sys.argv[2] FORCE_NEW_DEV = "--new-device" in sys.argv sys.argv = [sys.argv[0]] from urllib.parse import quote sys.path.insert(0, 'scripts'); sys.path.insert(0, 'tools') from cert_forge import build_p1, decrypt_cert, forge_cert, parse_p1 from envelope_forge import Envelope from nonce_forge import K1_DEFAULT, gen_nonce from probe_huya_qr_bind import QrRole, web_behavior from core.huya.device_fingerprint import get_huya_sdid from app_login_flow import QrAuthRequiredError, login_cred from huya_device_profile import get_profile def real_uid(acct: str) -> int: """登录响应解析真实uid(与信封t3一致的12位uid)。""" import struct from app_login_flow import wup_password_login_raw resp = wup_password_login_raw(acct, PWD) s = resp.find(b"\x0a\x0a", 0x40) e = resp.find(b"_wup_header") uid = struct.unpack_from(">Q", resp[s:e], 235)[0] assert 1_100_000_000_000 < uid < 1_300_000_000_000, f"uid异常 {uid}" return uid print(f"== 账号 {ACCT} ==") # 0) 每账号独立设备画像 (首次生成并持久化; 换机身份触发滑块自动过验) DEV = get_profile(ACCT, force_new=FORCE_NEW_DEV) print(f"设备画像: {DEV['vendor']} {DEV['model']} fp={DEV['fingerprint'][:12]}...") # 1) 登录取 cred (safe_auth滑块自动过) try: cred = login_cred(ACCT, PWD, device_info=DEV) except QrAuthRequiredError as e: print('❌', e); sys.exit(2) except Exception as e: print(f'❌ 登录未返回cred: {e}'); sys.exit(1) print('新鲜cred:', len(cred), cred[:8].hex()) # 2) 取真实uid + 本地重算nonce 铸证 (P1指纹用本账号画像, 全链设备一致) uid = real_uid(ACCT) env = Envelope.load() orig = base64.b64decode(env.cert_b64) f = parse_p1(decrypt_cert(orig)) st = int(time.time() * 1000) rnd = gen_nonce(uid, K1_DEFAULT, service_time_ms=st, counter=0) print(f"本地nonce: uid={uid} st={st} rnd={rnd.hex()}") P1 = build_p1(f["app_id"], DEV["fingerprint"].encode(), cred, rnd=rnd) cert = base64.b64encode(forge_cert(P1, key_idx=orig[1])).decode() assert len(cert) == env.cert_len, (len(cert), env.cert_len) # 3) 信封: 换cert + (uid不同则patch) raw = bytearray(env.raw) raw[env.cert_off:env.cert_off + env.cert_len] = cert.encode() if env.uid != uid: import struct print(f"patch uid {env.uid} -> {uid}") struct.pack_into(">Q", raw, env.uid_off, uid) wup = base64.b64encode(bytes(raw)).decode() # 4) 扫码绑定四步流 sdid = get_huya_sdid(allow_fallback=False).sdid pc = QrRole(pc=True, sdid=sdid); ph = QrRole(pc=False, sdid=sdid) beh, page = web_behavior() resp = pc.call("/qrLgn/getQrId", "70001", {"behavior": beh, "type": "", "domainList": "", "page": page}) qrid = resp["data"]["qrId"] cp = f"https://aq.huya.com/r/confirm.html?k={qrid}&id=5002" ph.call("/qrLgn/scanQrPicNotify", "70005", {"qrId": qrid, "wupData": wup, "behavior": quote("[]", safe=""), "page": quote(cp, safe="")}) r2 = ph.call("/qrLgn/bindQrLoginUser", "70007", {"qrId": qrid, "wupData": wup, "behavior": quote("[]", safe=""), "page": quote(cp, safe="")}) print("bind rc", r2.get("returnCode"), r2.get("message") or "") biztoken = None for i in range(10): rt = pc.call("/qrLgn/tryQrLogin", "70003", {"qrId": qrid, "remember": "1", "domainList": "", "behavior": beh, "page": page}) dt = rt.get("data") or {} if dt.get("stage") == 2: biztoken = dt.get("biztoken") print(f"biztoken OK: {len(biztoken)}B") break time.sleep(2) if not biztoken: print("❌ 未取到biztoken"); sys.exit(1) # 5) cookie verify 兑换 sess = getattr(pc, 'session', None) or getattr(pc, 's', None) or getattr(pc, '_sess', None) r = sess.post("https://udblgn.huya.com/web/cookie/verify", json={"appId": 5002}, timeout=15) print('verify HTTP', r.status_code) jar = [] for c in sess.cookies: print(' ', c.name, '=', c.value[:50]) jar.append(f'{c.name}={c.value}') out = __import__('pathlib').Path(__file__).resolve().parent.parent / 'evidence/web_cookies_full.txt' out.write_text(f'# account: {ACCT}\n' + '\n'.join(jar) + '\n') print('已写', out)