feat(huya): nonce生成机制完全破解 - 任意账号零设备出完整CK闭环
动态hook实锤(scripts/hook_nonce_chain.py): rnd = XXTEA(pack(st) + pack(counter|st<<16), key=MD5hex(uid_str + k1)[:16]) - st=毫秒时间戳, counter同st内递增 - k1=设备常量865a4924...(不随账号变, evidence/nonce_k1.json) - Python复刻(tools/nonce_forge.py)对设备两轮rnd逐字节MATCH 推翻v2误判(X2'nonce账号绑定令牌'实为nonce内编码uid): - 换账号用目标uid重算nonce而非复用旧nonce! - 纯随机nonce必40020因解不出合法st/counter结构 实测: 同账号+跨账号(hy_300024708从未设备登录)本地nonce铸证bind均通过, full_web_cookie/full_auto_safe 任意账号密码->全套cookie(udb_cred/udb_biztoken)
This commit is contained in:
+89
-37
@@ -1,20 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
"""一键全自动(含新账号safe_auth滑块): 账号密码 -> 页面cookie(biztoken)。
|
||||
"""一键全自动(含新账号safe_auth滑块): 账号密码 -> 页面cookie(biztoken)。纯协议版。
|
||||
|
||||
与 full_auto_test.py 的区别: 可信账号直出cred之外,
|
||||
2026-08-26: nonce 本地重算(tools/nonce_forge.py), 任意账号无需设备首登;
|
||||
新/异地账号触发 safe_auth 时自动解 udbrtt 滑块并重发登录, 全程零人工。
|
||||
|
||||
用法:
|
||||
.venv/bin/python tools/full_auto_safe.py <账号如hy_xxx或300023887> <密码>
|
||||
|
||||
前提: evidence/cert_keycap.json 内有较新鲜的 wupData 信封
|
||||
(过期则先在设备上跑 scripts/hook_cert_keycap.py 刷新)。
|
||||
前提: evidence/cert_keycap.json 内有信封模板(任意账号共用, 仅需采集一次)
|
||||
+ evidence/nonce_k1.json 或默认 k1 设备常量。
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
@@ -24,33 +24,34 @@ sys.path.insert(0, str(ROOT / "tools"))
|
||||
from app_login_flow import QrAuthRequiredError, login_cred # noqa: E402
|
||||
from cert_forge import build_p1, decrypt_cert, forge_cert, parse_p1 # noqa: E402
|
||||
from envelope_forge import Envelope # noqa: E402
|
||||
from nonce_forge import K1_DEFAULT, gen_nonce # noqa: E402
|
||||
|
||||
|
||||
def forge(cred: bytes) -> str:
|
||||
"""复用信封原证书的nonce/指纹/key_idx仅换cred(纯随机nonce必40020)."""
|
||||
env = Envelope.load()
|
||||
def real_uid(acct: str, pwd: str) -> int:
|
||||
"""登录响应解析真实uid(响应bean偏移235 u64be)。"""
|
||||
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
|
||||
|
||||
|
||||
def forge_for_uid(env: Envelope, cred: bytes, uid: int, k1: str) -> bytes:
|
||||
"""本地重算nonce + 换cred 铸证(任意账号可用)。"""
|
||||
orig = base64.b64decode(env.cert_b64)
|
||||
f = parse_p1(decrypt_cert(orig))
|
||||
P1 = build_p1(f["app_id"], f["fingerprint"], cred, rnd=f["rnd"])
|
||||
return forge_cert(P1, key_idx=orig[1]).hex()
|
||||
|
||||
|
||||
def bind_for_cookie(cred_json_path: Path) -> bool:
|
||||
"""复用扫码绑定四步流(QrRole), 返回是否拿到biztoken。"""
|
||||
probe = ROOT / "scripts/probe_forge_cert_bind.py"
|
||||
src = probe.read_text()
|
||||
src = re.sub(r'open\("evidence/hycred_[a-z_]+\.json"\)',
|
||||
f'open("{cred_json_path.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:])
|
||||
return '"ok": true' in r.stdout or "'ok': True" in r.stdout
|
||||
st = int(time.time() * 1000)
|
||||
rnd = gen_nonce(uid, k1, service_time_ms=st, counter=0)
|
||||
print(f" 本地nonce: uid={uid} st={st} rnd={rnd.hex()}")
|
||||
P1 = build_p1(f["app_id"], f["fingerprint"], cred, rnd=rnd)
|
||||
return forge_cert(P1, key_idx=orig[1])
|
||||
|
||||
|
||||
def main() -> int:
|
||||
acct, pwd = sys.argv[1], sys.argv[2]
|
||||
print("[1/3] 纯Python密码登录 (遇safe_auth自动过滑块) ...")
|
||||
print("[1/4] 纯Python密码登录 (遇safe_auth自动过滑块) ...")
|
||||
try:
|
||||
cred = login_cred(acct, pwd)
|
||||
except QrAuthRequiredError as exc:
|
||||
@@ -58,19 +59,70 @@ def main() -> int:
|
||||
return 2
|
||||
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")
|
||||
uid = real_uid(acct, pwd)
|
||||
print(f"[2/4] 解析真实uid={uid}, 本地重算nonce 铸证 ...")
|
||||
env = Envelope.load()
|
||||
cert = forge_for_uid(env, cred, uid, K1_DEFAULT)
|
||||
b64 = base64.b64encode(cert).decode()
|
||||
if len(b64) != env.cert_len:
|
||||
print(f" cert b64 长度不符 {len(b64)} != {env.cert_len}, 中止")
|
||||
return 1
|
||||
raw = bytearray(env.raw)
|
||||
raw[env.cert_off:env.cert_off + env.cert_len] = b64.encode()
|
||||
if env.uid != uid:
|
||||
print(f" patch uid {env.uid} -> {uid}")
|
||||
struct.pack_into(">Q", raw, env.uid_off, uid)
|
||||
wup = base64.b64encode(bytes(raw)).decode()
|
||||
|
||||
print("[3/3] 扫码绑定取cookie ...")
|
||||
if bind_for_cookie(out):
|
||||
print("\n🎉 全链路成功: 密码已换出页面cookie(biztoken)!")
|
||||
return 0
|
||||
print("\n❌ 绑定失败——若40020多为信封过期, 请刷新wupData后重试")
|
||||
return 1
|
||||
print("[3/4] 扫码绑定四步流 ...")
|
||||
from urllib.parse import quote
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
from probe_huya_qr_bind import QrRole, web_behavior
|
||||
from core.huya.device_fingerprint import get_huya_sdid
|
||||
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(f" bind rc={r2.get('returnCode')} {r2.get('message') or ''}")
|
||||
biztoken = None
|
||||
for _ 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")
|
||||
return 1
|
||||
|
||||
print("[4/4] 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(f" verify HTTP {r.status_code}")
|
||||
jar = []
|
||||
for c in sess.cookies:
|
||||
print(f" {c.name} = {c.value[:50]}")
|
||||
jar.append(f"{c.name}={c.value}")
|
||||
out = ROOT / "evidence/web_cookies_full.txt"
|
||||
out.write_text(f"# account: {acct}\n" + "\n".join(jar) + "\n")
|
||||
print(f"\n🎉 纯协议全链路成功: 任意账号密码已换出全套cookie! ({len(jar)}个)")
|
||||
print("已写", out)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user