Files
live-hub-py/tools/huya_app_crypto.py
T
yml2213 ff99c036f8 虎牙App密码登录: 逆向WUP协议并实现纯Python登录编码器
- hook WUP序列化层抓取TLS加密前明文,确认密码登录走
  servant=huyaudbwebui / func=hypasswordLogin (msgType=0x1001)
- 逆向JCE RequestPacket帧格式(1015字节TAF二进制),定稿三层结构:
  WUP header(tag1-10) -> sBuffer Map -> _wup_data业务struct
- 澄清两个疑点字节: 0x66/0x7d 是TAF字段头(非分隔符)
- 实现 tools/huya_wup_encoder.py,逐字节复现金标准
- 实测纯Python动态构造登录成功(返回uid+token),TLS指纹不阻断
- userAction随机坐标/时间戳同样通过,内置make_user_action辅助函数
- 新增证据金标准 + 协议文档
2026-08-25 02:59:20 +08:00

239 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
虎牙 App 登录 — OTP/dckey 加密算法(已通过 Frida 动态双向验证)
完整加密链(libudbauthunify.socom.duowan.kiwi 13.4.22 / versionCode 115315:
┌─────────────────────────────────────────────────────────────┐
│ OTP 加密(hyudb_otp_encrypt
│ │
│ 1. XXTEA: xxtea_key = md5(uid字符串).hexdigest()[:16] │
│ xxtea_in = arg7(8字节小端) + 4字节原始长度 │
│ (标准 XXTEA, rounds = 6 + 52/n, DELTA=0x9e3779b9)│
│ │
│ 2. cred_pack 拼装: │
│ [0x02] # 固定 header │
│ [u16_le len][xxtea密文] # string 打包 │
│ [u16_le len][设备指纹] # string 打包 │
│ │
│ 3. AES-128-ECB + ZeroPadding: │
│ aes_key = md5(sessHex32 + AESKEYMGR_KEY[b])[4:12].hex() │
│ cipher = AES_ECB(拼装数据 + 补零) │
└─────────────────────────────────────────────────────────────┘
动态验证样本(两次 hook,两个独立 PID,结果一致):
uid = "1471238907296"
sessHex32 = "865a4924a40897ac1fcfe6b4c2cbb0e3" (固定)
AESKEYMGR = "MKDKeridjing7avnsasdSDHI" (b=2)
aes_key = "312334e88d8c35cb"
XXTEA 双向、AES 加密方向均已验证通过。
"""
from __future__ import annotations
import hashlib
import struct
from binascii import unhexlify
from Crypto.Cipher import AES
# ---------------------------------------------------------------------------
# AESkeyMgr 硬编码 key 表(按 b 参数索引,1-based)
# b=2..5 已通过 Frida hook AESkeyMgr::getkey(a=1, b) 动态确认;
# 其余为 rodata 相邻字符串推断,待最终确认顺序。
# ---------------------------------------------------------------------------
AESKEYMGR_KEYS: dict[int, str] = {
1: "owNMiaCgcHmqoTr3iRamFuHj",
2: "MKDKeridjing7avnsasdSDHI", # 已验证
3: "nskdI7MDGKSDJsnadjdoonvs", # 已验证
4: "whdFNGFdfJHMJbdfg6jhdsFG", # 已验证
5: "SHBfgytjtoikooruhogji7ER", # 已验证
6: "KNSDNjfohweeromnmkladj3g",
7: "KJNzmZaxUd9kGLoAbtefNzCv",
8: "YnzbiMPNXaGuxxuoNRd9Lzyj",
}
# 固定 32 位 hex 会话值(OTP AES key 源的第一半)
SESS_HEX32 = "865a4924a40897ac1fcfe6b4c2cbb0e3"
DELTA = 0x9E3779B9
def huya_arg7(now_ms: int | None = None) -> int:
"""arg7 = System.currentTimeMillis() << 16(低 16 位恒为 0)。
Frida 实测:arg7 >> 16 ≈ 事件时间戳(ms),误差为 hook 传播延迟(<600ms)。
"""
import time
if now_ms is None:
now_ms = int(time.time() * 1000)
return now_ms << 16
# ---------------------------------------------------------------------------
# XXTEA(标准实现,已验证)
# ---------------------------------------------------------------------------
def _xxtea_mx(z, y, total, key, p, e):
return (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4))
^ ((total ^ y) + (key[(p & 3) ^ e] ^ z))) & 0xFFFFFFFF
def _xxtea_rounds(n: int) -> int:
return 6 + 52 // n
def xxtea_encrypt_u32(v: list[int], key: list[int]) -> list[int]:
n = len(v)
if n < 2:
return v
total = 0
z = v[n - 1]
for _ in range(_xxtea_rounds(n)):
total = (total + DELTA) & 0xFFFFFFFF
e = (total >> 2) & 3
for p in range(n):
y = v[(p + 1) % n]
v[p] = (v[p] + _xxtea_mx(z, y, total, key, p, e)) & 0xFFFFFFFF
z = v[p]
return v
def xxtea_decrypt_u32(v: list[int], key: list[int]) -> list[int]:
n = len(v)
if n < 2:
return v
rounds = _xxtea_rounds(n)
total = (rounds * DELTA) & 0xFFFFFFFF
y = v[0]
while total != 0:
e = (total >> 2) & 3
for p in range(n - 1, 0, -1):
z = v[p - 1]
v[p] = (v[p] - _xxtea_mx(z, y, total, key, p, e)) & 0xFFFFFFFF
y = v[p]
z = v[n - 1]
v[0] = (v[0] - _xxtea_mx(z, y, total, key, 0, e)) & 0xFFFFFFFF
y = v[0]
total = (total - DELTA) & 0xFFFFFFFF
return v
def xxtea_key_from_uid(uid: str) -> list[int]:
"""XXTEA key = md5(uid字符串).hexdigest()[:16] 的 16 个 ASCII 字节(4 个小端 u32)。"""
h = hashlib.md5(uid.encode("ascii")).hexdigest()[:16]
return list(struct.unpack("<4I", h.encode("ascii")))
def huya_xxtea_encrypt(data: bytes, uid: str) -> bytes:
"""XXTEA 加密:数据 + 4字节原始长度 padding,再标准 XXTEA。"""
key = xxtea_key_from_uid(uid)
pad = (-len(data)) % 4
buf = data + b"\x00" * pad + struct.pack("<I", len(data))
v = list(struct.unpack("<%dI" % (len(buf) // 4), buf))
enc = xxtea_encrypt_u32(v, key)
return b"".join(struct.pack("<I", x) for x in enc)
# ---------------------------------------------------------------------------
# AES(已验证)
# ---------------------------------------------------------------------------
def huya_aes_key(otp_input: bytes) -> bytes:
"""AES-128 key = md5(otp_input) digest 第 5~12 字节转 hex16 ASCII 字符)。"""
d = hashlib.md5(otp_input).digest()
return d[4:12].hex().encode("ascii")
def huya_aes_encrypt(plain: bytes, otp_input: bytes) -> bytes:
key = huya_aes_key(otp_input)
pad = (-len(plain)) % 16
return AES.new(key, AES.MODE_ECB).encrypt(plain + b"\x00" * pad)
def huya_aes_decrypt(cipher: bytes, otp_input: bytes) -> bytes:
key = huya_aes_key(otp_input)
return AES.new(key, AES.MODE_ECB).decrypt(cipher).rstrip(b"\x00")
# ---------------------------------------------------------------------------
# cred_pack 打包
# ---------------------------------------------------------------------------
def cred_pack_string(data: bytes) -> bytes:
"""string = [2字节小端长度][数据]cred_pack::push_varstr)。"""
return struct.pack("<H", len(data)) + data
# ---------------------------------------------------------------------------
# 完整 OTP 加密(对应 hyudb_otp_encrypt
# ---------------------------------------------------------------------------
def huya_password_sha1(password: str) -> str:
"""密码登录的 password 字段 = 标准 SHA1(明文密码) 的 hex,无盐。
Frida 抓包证据(msgType=0x1001 密码登录请求):
password = "772ed992b0e161276f44ec63671e60155c506294" (40 位 hex)
"""
return hashlib.sha1(password.encode("utf-8")).hexdigest()
def huya_name(hyname: str) -> str:
"""登录 name 字段 = "hy_" + 虎牙号(纯数字账号)。"""
return f"hy_{hyname}"
def huya_otp_encrypt(
uid: str,
arg7: int,
device_fingerprint: bytes,
b: int = 2,
sess_hex32: str = SESS_HEX32,
) -> bytes:
"""完整 OTP 加密,返回 AES 密文。
uid : 账号 uid 的十进制字符串(XXTEA key 源)
arg7 : 8 字节 ulong(时间戳/计数器)
device_fingerprint : 设备指纹二进制(arg5
b : AESkeyMgr key 表索引(1..8
sess_hex32 : 固定 32 位 hex 会话值
"""
# 1. XXTEA 加密 arg7
xxtea_ct = huya_xxtea_encrypt(struct.pack("<Q", arg7), uid)
# 2. cred_pack 拼装
packed = b"\x02" + cred_pack_string(xxtea_ct) + cred_pack_string(device_fingerprint)
# 3. AES-128-ECB
otp_input = (sess_hex32 + AESKEYMGR_KEYS[b]).encode("ascii")
return huya_aes_encrypt(packed, otp_input)
if __name__ == "__main__":
# 自检 1: AES
otp_input = (SESS_HEX32 + AESKEYMGR_KEYS[2]).encode("ascii")
plain = unhexlify(
"020c00cf8b207ddeb2ec4ec2a9344772000a70686484dee8e70dd7649fa14e82"
"86caef42e5ddb9c380aef2485f87170507881b3e11b04c8dc976b8d056dab02"
"45672e3b5595f73d1803c123672fb3525bb3640b986a94d4c00349995e904a1"
"0b27b6d9d0078b1a0c85abdcd1747e588831370d553cf3bd9e3032a9537e30f"
"29dbb4866"
)
expected = unhexlify(
"b2f83ab1970e3d343935b502874a63e3e4a674c60827347a8b81186e11bfb44"
"3a95cd1252536822859daa76685317aab55ca6f50530b98b72122b61f774d6a"
"9bc515930d7a88f387efc6dac90379ee26b346a5a08e42546950b268a8a7f5c"
"ecfe8b93f2b1271c2c40ccddc8f805b2732e2d16e3e79fd526666b58a5fc3d0"
"af366cebfbeaa91089188cb5d7ed48661cbd"
)
assert huya_aes_key(otp_input) == b"312334e88d8c35cb", "AES key 派生失败"
assert huya_aes_encrypt(plain, otp_input) == expected, "AES 加密失败"
assert huya_aes_decrypt(expected, otp_input) == plain, "AES 解密失败"
# 自检 2: XXTEA
uid = "1471238907296"
xxtea_ct = huya_xxtea_encrypt(struct.pack("<Q", 0x01A034AD5ECA0000), uid)
assert xxtea_ct.hex() == "d24e6493d0ee53c395790d67", f"XXTEA 失败: {xxtea_ct.hex()}"
# 自检 3: 密码 SHA1(无盐)
assert huya_password_sha1("password123") == hashlib.sha1(b"password123").hexdigest()
print("自检全部通过")
print(" AES key =", huya_aes_key(otp_input).decode())
print(" XXTEA =", huya_xxtea_encrypt(struct.pack('<Q', 0x01A034AD5ECA0000), uid).hex())