虎牙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辅助函数
- 新增证据金标准 + 协议文档
This commit is contained in:
yml2213
2026-08-25 02:59:20 +08:00
parent 06a0a1e7d3
commit ff99c036f8
14 changed files with 2185 additions and 0 deletions
+238
View File
@@ -0,0 +1,238 @@
"""
虎牙 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())