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:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""虎牙证书 nonce(rnd) 生成器 —— 纯本地可算, 无需设备!
|
||||
|
||||
2026-08-26 动态hook实锤(scripts/hook_nonce_chain.py, /tmp/opencode/nonce_chain.json):
|
||||
|
||||
rnd = XXTEA(pack(serviceTime) + pack(nonce_next), key=MD5hex(uid_str + k1)[:16])
|
||||
其中:
|
||||
serviceTime = 当前毫秒时间戳 (int(time.time()*1000) & 0x7FFFFFFFFFFF)
|
||||
nonce_next = counter | (serviceTime << 16)
|
||||
(counter 为同 serviceTime 内递增计数, 从0开始)
|
||||
k1 = 设备级常量 32位hex (865a4924a40897ac1fcfe6b4c2cbb0e3, 不随账号变)
|
||||
key = hashlib.md5((uid_str + k1).encode()).hexdigest()[:16].encode()
|
||||
pack(x) = 8字节小端 (cred_packlsEm: append 8B LE)
|
||||
|
||||
XXTEA 输出 = ceil(len/4)+1 words, 末尾 word 存原始长度 -> 16B输入 => 20B输出 = P1的rnd。
|
||||
|
||||
验证: 对设备实采两轮 rnd (80dd1dfa.../0ba8f8b7...) 逐字节复现一致。
|
||||
k1 跨账号不变(uid=1199666914671 vs 1199666911746 均同值) => 任意账号可本地铸 nonce!
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import struct
|
||||
import time
|
||||
|
||||
# 设备级常量: BusinessCfg::this+0x10 (动态hook自真机, 不随账号变化)
|
||||
# TODO: 待确认其生成源(疑为设备安装时生成并持久化), 当前直接从真机提取
|
||||
K1_DEFAULT = "865a4924a40897ac1fcfe6b4c2cbb0e3"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- XXTEA
|
||||
def _xxtea_encrypt_words(v: list[int], k: list[int]) -> list[int]:
|
||||
"""标准 XXTEA (delta=0x9e3779b9, rounds=6+52/n), in-place 返回。"""
|
||||
n = len(v)
|
||||
if n < 2:
|
||||
return v
|
||||
z = v[n - 1]
|
||||
y = v[0]
|
||||
s = 0
|
||||
delta = 0x9E3779B9
|
||||
q = 6 + 52 // n
|
||||
while q > 0:
|
||||
s = (s + delta) & 0xFFFFFFFF
|
||||
e = (s >> 2) & 3
|
||||
p = 0
|
||||
while p < n - 1:
|
||||
y = v[p + 1]
|
||||
z = (v[p] + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
|
||||
^ ((s ^ y) + (k[(p & 3) ^ e] ^ z)))) & 0xFFFFFFFF
|
||||
v[p] = z
|
||||
p += 1
|
||||
y = v[0]
|
||||
z = (v[n - 1] + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
|
||||
^ ((s ^ y) + (k[((n - 1) & 3) ^ e] ^ z)))) & 0xFFFFFFFF
|
||||
v[n - 1] = z
|
||||
q -= 1
|
||||
return v
|
||||
|
||||
|
||||
def xxtea_encrypt(data: bytes, key16: bytes) -> bytes:
|
||||
"""虎牙专用 XXTEA: 输出 words = ceil(len/4)+1, 末尾 word 存原始长度。
|
||||
|
||||
data: 任意长度(实测16B); key16: 16字节。
|
||||
返回: 20B (16B输入时)。
|
||||
"""
|
||||
n = len(data)
|
||||
nwords = (n // 4) + 1
|
||||
v = [0] * nwords
|
||||
for i in range(n // 4):
|
||||
v[i] = struct.unpack("<I", data[i * 4:i * 4 + 4])[0]
|
||||
v[nwords - 1] = n # 原始长度
|
||||
k = [struct.unpack("<I", key16[i * 4:i * 4 + 4])[0] for i in range(4)]
|
||||
_xxtea_encrypt_words(v, k)
|
||||
return b"".join(struct.pack("<I", w & 0xFFFFFFFF) for w in v)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- nonce
|
||||
def pack_u64(v: int) -> bytes:
|
||||
"""cred_packlsEm: 8字节小端裸值。"""
|
||||
return struct.pack("<Q", v & 0xFFFFFFFFFFFFFFFF)
|
||||
|
||||
|
||||
def gen_nonce(uid: int, k1: str = K1_DEFAULT,
|
||||
service_time_ms: int | None = None,
|
||||
counter: int = 0) -> bytes:
|
||||
"""按设备算法生成 20B nonce(rnd)。
|
||||
|
||||
uid: 虎牙uid(纯数字, 如1199666914671)
|
||||
k1: 设备常量32hex
|
||||
service_time_ms: 缺省=当前毫秒时间戳
|
||||
counter: 同serviceTime内的递增计数(通常0即可)
|
||||
"""
|
||||
st = service_time_ms if service_time_ms is not None else int(time.time() * 1000)
|
||||
st &= 0x7FFFFFFFFFFF
|
||||
nonce_val = (counter & 0xFFFF) | (st << 16)
|
||||
data = pack_u64(st) + pack_u64(nonce_val)
|
||||
key = hashlib.md5((str(uid) + k1).encode()).hexdigest()[:16].encode()
|
||||
return xxtea_encrypt(data, key)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 自检
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
# 用设备实采数据验证 (成对遍历 xxtea_in/xxtea_out)
|
||||
ev = json.load(open("/tmp/opencode/nonce_chain.json"))
|
||||
ins = [e for e in ev if e.get("type") == "xxtea_in"]
|
||||
outs = [e for e in ev if e.get("type") == "xxtea_out"]
|
||||
for idx, e in enumerate(ins):
|
||||
data = bytes.fromhex(e["data"]["v"])
|
||||
key_str = bytes.fromhex(e["key"]["v"]).decode()
|
||||
uid = key_str[:-32]
|
||||
k1 = key_str[-32:]
|
||||
st = struct.unpack("<Q", data[:8])[0]
|
||||
nonce_val = struct.unpack("<Q", data[8:])[0]
|
||||
counter = nonce_val & 0xFFFF
|
||||
mine = gen_nonce(int(uid), k1, service_time_ms=st, counter=counter)
|
||||
real = outs[idx]["out"]["v"]
|
||||
print(f"uid={uid} st={st} counter={counter}")
|
||||
print(f" 设备: {real}")
|
||||
print(f" 复刻: {mine.hex()}")
|
||||
print(" MATCH!" if mine.hex() == real else " MISMATCH!!")
|
||||
Reference in New Issue
Block a user