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:
+93
-60
@@ -1,73 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""一键: 账号密码 -> 完整网页Cookie全套。
|
||||
用法: python tools/full_web_cookie.py <账号> <密码>
|
||||
"""一键: 账号密码 -> 完整网页Cookie全套 (纯协议, nonce本地重算).
|
||||
|
||||
2026-08-26 升级: nonce 生成机制已破解(tools/nonce_forge.py),
|
||||
不再需要复用设备信封的原nonce —— 任意账号用 目标uid+k1 本地重算即过bind!
|
||||
依赖: k1 设备常量(evidence/nonce_k1.json 或默认值, 不随账号变)。
|
||||
|
||||
用法: .venv/bin/python tools/full_web_cookie.py <账号> <密码>
|
||||
产出: 打印全部cookie并写 evidence/web_cookies_full.txt
|
||||
"""
|
||||
import sys, json, re, hashlib
|
||||
import sys, json, re, hashlib, time, base64
|
||||
ACCT, PWD = sys.argv[1], sys.argv[2]
|
||||
sys.argv = [sys.argv[0]]
|
||||
import sys,json,time,base64
|
||||
from urllib.parse import quote
|
||||
sys.path.insert(0,'scripts'); sys.path.insert(0,'tools')
|
||||
from probe_forge_cert_bind import load_capture, parse_p1, build_p1, forge_cert
|
||||
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
|
||||
qurl,P1_old=load_capture()
|
||||
fields=parse_p1(P1_old)
|
||||
import time,base64
|
||||
from urllib.parse import quote
|
||||
# 登录取cred: 触发safe_auth时自动解udbrtt滑块并重发(app_login_flow闭环)
|
||||
import app_login_flow as _flow
|
||||
try:
|
||||
cred = _flow.login_cred(ACCT, PWD)
|
||||
except _flow.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())
|
||||
P1n=build_p1(b"5008",fields["fingerprint"],cred,rnd=fields["rnd"])
|
||||
cert=base64.b64encode(forge_cert(P1n,key_idx=fields and 0x20)).decode()
|
||||
raw=base64.b64decode(qurl)
|
||||
i=raw.find(base64.b64encode(forge_cert(build_p1(b"5008",fields["fingerprint"],b"\x0a"*114,rnd=fields["rnd"]),key_idx=0x20)))
|
||||
# 直接替换原证书段: 找原cert b64 span——用原P1重建
|
||||
old_b64=None
|
||||
from probe_forge_cert_bind import find_cert_span as _fcs; j=_fcs(raw)
|
||||
if True:
|
||||
# 退路: 用find_cert_span
|
||||
from probe_forge_cert_bind import find_cert_span
|
||||
s0,s1,_=find_cert_span(raw); raw=raw[:s0]+cert.encode()+raw[s1:]
|
||||
else:
|
||||
raw=raw[:j]+cert.encode()+raw[j+len(old_b64):]
|
||||
wup=base64.b64encode(raw).decode()
|
||||
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"))
|
||||
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:
|
||||
print(json.dumps(dt,ensure_ascii=False,indent=1)[:1800]); break
|
||||
time.sleep(2)
|
||||
from app_login_flow import QrAuthRequiredError, login_cred
|
||||
|
||||
# 追加: 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)
|
||||
for h in r.headers.get('Set-Cookie','').split('\n') or [r.headers.get('Set-Cookie','')]:
|
||||
if h: print('SET:',h[:110])
|
||||
print('cookie jar:')
|
||||
jar=[]
|
||||
|
||||
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} ==")
|
||||
# 1) 登录取 cred (safe_auth滑块自动过)
|
||||
try:
|
||||
cred = login_cred(ACCT, PWD)
|
||||
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 铸证
|
||||
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"], f["fingerprint"], 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])
|
||||
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 = __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)
|
||||
|
||||
Reference in New Issue
Block a user