feat(huya): 证书算法完全闭环 - 差分法证明标准AES-128-ECB并复现铸证
- NativeFunction直调差分: FIPS-197 KAT逐位一致, UdbAESUtil无魔改 - 证书 = b64([0x0C][key_idx][AES128-ECB(key16, zeropad(P1_187B))]) - P1结构: [010400][5008][u16+20B随机][u16+40B指纹hex][u16+114B hyCred] - 密钥: rodata默认三条(AESkeyMgr id=3/4/5) + 服务端下发会话钥(key_idx区分) - tools/cert_forge.py 重铸证书与活体密文 FULL MATCH
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""虎牙 udb 登录证书(biz_token)解析/铸造工具 —— 算法已完全闭环(2026-08-25)。
|
||||
|
||||
== 已证实的完整算法链 ==
|
||||
|
||||
证书 = base64( [0x0c][key_idx][AES-128-ECB(key16, zeropad(P1))] )
|
||||
|
||||
P1 (187B) 结构(两份活体样本逐字段对齐验证):
|
||||
[0x01][0x04][0x00] 固定头
|
||||
["5008"] appId
|
||||
[u16_le=20][20B 随机数] 每次生成时随机
|
||||
[u16_le=40][40B 指纹hex] 设备指纹ASCII("02df39879743..."), 设备绑定、跨会话不变
|
||||
[u16_le=114][114B cred] hyCred 二进制 (=udb_cred base64解码)
|
||||
|
||||
key16 来源: AESkeyMgr 密钥表。rodata 内置默认三条(0x1bc810, 注册id=3/4/5):
|
||||
4VYcPdvKKqjBHZtCmbroRXHk <- 本次活体实测在用(取前16字节)
|
||||
xXEDWqiKLGwEZ6HubEiswCqK
|
||||
3FMHubdKosFrhmXNLHTNHZwe
|
||||
服务端可下发会话密钥(NEWKEY/key_id机制), 证书第二字节即 key_idx:
|
||||
今日 getQUrlData 样本 idx=0x20 <-> "4VYcPdvKKqjBHZtC";
|
||||
更早样本 idx=0x50 用默认钥解不开 => 属服务端下发钥。
|
||||
UdbAESUtil 本体为标准 AES-128-ECB(FIPS-197 KAT 逐位一致), 无魔改。
|
||||
|
||||
调用链: HandlerGetH5InfoEx::onHandler -> BusinessCfg::getOtp -> ... ->
|
||||
gen_biz_token(BIZTOKEN,P1部件...,out) @0x3307e4
|
||||
-> enpack_header/enpack_body/cred_packls/xxtea_encrypt
|
||||
-> encode_aes(P1, key24, out) @0x330218 # C1 只读 key 前16字节
|
||||
-> UdbAESUtil::encrypt @0x250038 # 补零到16倍数逐块
|
||||
-> CBase64::Encode
|
||||
|
||||
验证脚本: scripts/hook_aes_diff.py(差分) / scripts/hook_cert_keycap.py(抓钥)
|
||||
证据: evidence/aes_diff.json / evidence/cert_keycap.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from tools.udb_aes import udb_decrypt, udb_encrypt # noqa: E402
|
||||
|
||||
# so rodata 0x1bc810 起连排的三条24字符默认密钥(AESkeyMgr 注册 id=3/4/5)
|
||||
KEY_TABLE_RAW = [
|
||||
"4VYcPdvKKqjBHZtCmbroRXHk",
|
||||
"xXEDWqiKLGwEZ6HubEiswCqK",
|
||||
"3FMHubdKosFrhmXNLHTNHZwe",
|
||||
]
|
||||
DEFAULT_KEY16 = KEY_TABLE_RAW[0][:16].encode() # 本次活体验证在用
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- P1 组装/解析
|
||||
def build_p1(app_id: bytes, fingerprint: bytes, cred: bytes,
|
||||
rnd: bytes | None = None) -> bytes:
|
||||
"""按活体样本结构组装 P1 明文。
|
||||
|
||||
fingerprint: 40字节 ASCII hex 形如 b'02df39879743...'(设备绑定)
|
||||
cred: 114 字节 hyCred 二进制(base64(udb_cred) 解码后)
|
||||
rnd: 缺省用 os.urandom(20)
|
||||
"""
|
||||
assert len(fingerprint) == 40, f"指纹应40B, 得到{len(fingerprint)}"
|
||||
assert len(cred) == 114, f"cred应114B, 得到{len(cred)}"
|
||||
r = os.urandom(20) if rnd is None else rnd
|
||||
p1 = b"\x01\x04\x00" + app_id
|
||||
p1 += struct.pack("<H", len(r)) + r
|
||||
p1 += struct.pack("<H", len(fingerprint)) + fingerprint
|
||||
p1 += struct.pack("<H", len(cred)) + cred
|
||||
assert len(p1) == 187, len(p1)
|
||||
return p1
|
||||
|
||||
|
||||
def parse_p1(data: bytes) -> dict:
|
||||
o = 0
|
||||
|
||||
def tk(n: int) -> bytes:
|
||||
nonlocal o
|
||||
b = data[o:o + n]
|
||||
o += n
|
||||
return b
|
||||
|
||||
out = {
|
||||
"head": tk(3).hex(),
|
||||
"app_id": tk(4),
|
||||
}
|
||||
n = struct.unpack("<H", tk(2))[0]
|
||||
out["rnd"] = tk(n)
|
||||
n = struct.unpack("<H", tk(2))[0]
|
||||
out["fingerprint"] = tk(n)
|
||||
n = struct.unpack("<H", tk(2))[0]
|
||||
out["cred"] = tk(n)
|
||||
out["trailing"] = data[o:]
|
||||
return out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 证书层
|
||||
def forge_cert(p1: bytes, key16: bytes = DEFAULT_KEY16,
|
||||
key_idx: int = 0x20, type_byte: int = 0x0C) -> bytes:
|
||||
"""P1 -> 二进制证书 [type][key_idx][192B AES 密文]。"""
|
||||
ct = udb_encrypt(key16, p1)
|
||||
assert len(ct) == 192, len(ct)
|
||||
return bytes([type_byte, key_idx]) + ct
|
||||
|
||||
|
||||
def cert_b64(cert: bytes) -> str:
|
||||
return base64.b64encode(cert).decode()
|
||||
|
||||
|
||||
def parse_cert(blob: bytes) -> tuple[int, int, bytes]:
|
||||
"""[0x0c][idx][192B] -> (type, idx, ct)。"""
|
||||
assert blob[0] == 0x0C and len(blob) >= 194, "非证书结构"
|
||||
return blob[0], blob[1], blob[2:194]
|
||||
|
||||
|
||||
def decrypt_cert(blob: bytes, key16: bytes = DEFAULT_KEY16) -> bytes:
|
||||
_, _, ct = parse_cert(blob)
|
||||
return udb_decrypt(key16, ct)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 兼容旧接口
|
||||
def try_decrypt_all(ct192: bytes) -> list[tuple[str, bytes]]:
|
||||
return [(k[:16], udb_decrypt(k[:16].encode(), ct192)) for k in KEY_TABLE_RAW]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
ev = json.load(open(Path(__file__).resolve().parent.parent /
|
||||
"evidence/cert_keycap.json"))
|
||||
enc_in = next(e for e in ev if e.get("type") == "enc_in")
|
||||
P1 = bytes.fromhex(enc_in["x1s"]["v"])
|
||||
fields = parse_p1(P1)
|
||||
print("P1 解析:", {k: (v.hex()[:32] if isinstance(v, bytes) else v)
|
||||
for k, v in fields.items()})
|
||||
# 用捕获的 P1 重铸并与活体密文比对
|
||||
qurl = next(e["data"] for e in ev if e.get("type") == "qurl_done" and e.get("data"))
|
||||
import re
|
||||
raw = base64.b64decode(qurl)
|
||||
inner = []
|
||||
for x in re.findall(rb"[A-Za-z0-9+/]{100,}={0,2}", raw):
|
||||
try:
|
||||
inner.append(base64.b64decode(x + b"=" * ((-len(x)) % 4)))
|
||||
except Exception:
|
||||
pass
|
||||
blob = max(inner, key=len)
|
||||
mine = forge_cert(P1)
|
||||
print("活体证书 :", blob.hex()[:64])
|
||||
print("重铸证书 :", mine.hex()[:64])
|
||||
print("FULL MATCH:", mine == blob)
|
||||
Reference in New Issue
Block a user