Files
live-hub-py/docs/虎牙App登录-加密算法动态验证.md
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

6.7 KiB
Raw Blame History

虎牙 App 登录 — 加密算法动态验证(Frida 最终结论)

状态:算法链已 100% 破解并双向验证;arg7/appKey/XXTEA key 源已确认 实现:tools/huya_app_crypto.py(自检通过) 方法:Frida spawn + libmsaoaidsec 绕过 + hook libudbauthunify.so 导出符号 样本:com.duowan.kiwi 13.4.22versionCode 115315


一、最终算法链(一句话)

OTP 加密 = AES-128-ECB( cred_pack([0x02][xxtea密文][设备指纹]) , key )
  其中 key = md5("865a4924a40897ac1fcfe6b4c2cbb0e3" + AESKEYMGR_KEYS[b])[4:12].hex()
        xxtea密文 = XXTEA( arg7(8字节) + 4字节长度 , key=md5(uid)[:16]hex )

两层加密,均已用 hook 到的「明文↔密文」样本双向验证通过。


二、AES 层(已验证)

模式 AES-128-ECB + ZeroPadding
key 派生 md5(otp_input).digest()[4:12].hex() → 16 个 ASCII hex 字符
otp_input sessHex32 + AESKEYMGR_KEYS[b]

验证样本:

otp_input = "865a4924a40897ac1fcfe6b4c2cbb0e3" + "MKDKeridjing7avnsasdSDHI"
aes_key   = "312334e88d8c35cb"
明文 131 字节 → 补 13 字节零 → 144 字节密文,加密/解密双向一致。

关键坑:md5_char16 内部是 HuyaMd5::toString16(),输出的是 digest 第 5~12 字节(跳过前 4 字节)转 hex,不是前 16 字符,也不是 16 字节二进制。


三、XXTEA 层(已验证)

算法 标准 XXTEADELTA=0x9e3779b9rounds=6+52/n
key md5(uid字符串).hexdigest()[:16] 的 16 个 ASCII 字符(4 个小端 u32
输入 arg7(8字节小端) + struct.pack("<I", len(arg7))(追加原始长度)

验证样本:

uid    = "1471238907296"
xxtea_key = "4d20d5c7222d5858"   (md5(uid) 前 16 hex 字符)
输入   = 0000ca5ead34a001 + 08000000   (arg7=0x01a034ad5eca0000, 长度8)
输出   = d24e6493d0ee53c395790d67

关键坑:XXTEA key 是 md5 hex 字符串前 16 个 ASCII 字符,不是 16 字节 digest。反汇编证据:ldr q0 只读 16 字节,无 hex 解码。 padding:不是简单补 0,而是「补 0 到 4 对齐 + 追加 4 字节原始长度」。


四、AESkeyMgr 硬编码 key 表(rodata

AESkeyMgr::getkey(a=1, b) 返回的 keyb=2..5 已 hook 动态确认:

b key 确认方式
1 owNMiaCgcHmqoTr3iRamFuHj rodata 推断
2 MKDKeridjing7avnsasdSDHI hook
3 nskdI7MDGKSDJsnadjdoonvs hook
4 whdFNGFdfJHMJbdfg6jhdsFG hook
5 SHBfgytjtoikooruhogji7ER hook
6 KNSDNjfohweeromnmkladj3g rodata 推断
7 KJNzmZaxUd9kGLoAbtefNzCv rodata 推断
8 YnzbiMPNXaGuxxuoNRd9Lzyj rodata 推断

sessHex32 = "865a4924a40897ac1fcfe6b4c2cbb0e3" 固定(多次 hook 一致,非 so 内字符串,运行时生成)。


五、OTP 数据结构(cred_pack

cred_pack 打包格式(反汇编 push_varstr/cred_packlsE* 确认):

类型 编码
uchar 1 字节
ulong 8 字节(小端)
string [u16_le 长度][数据]

hyudb_otp_encrypt 输出的 AES 明文结构:

[0x02]                          # 固定 headerarg1=2
[u16_le len][xxtea密文]          # arg7 加密结果(8字节→12字节)
[u16_le len][设备指纹]           # arg5114字节左右)

六、函数参数映射(hook 实测)

hyudb_otp_encrypt(x0..x7, 栈上out)

参数 含义 实测值
x0 (string) XXTEA key 源 = uid 字符串 "1471238907296"
x1 (uchar) 固定 header 2
x2 (uchar) AESkeyMgr b 索引 2/3/4/5
x3 (string) ""
x4 (string) sessHex32 "865a4924a40897ac1fcfe6b4c2cbb0e3"
x5 (string) 设备指纹(二进制) 0a80...
x6 (uchar) 固定 4
x7 (ulong) XXTEA 输入值 0x01a034ad5eca0000

调用方:BusinessCfg::getOtp(uid, ...)hyudb_otp_encrypt


七、Hook 落点(已验证可用)

函数 符号 needle 地址偏移
OTP 加密 hyudb_otp_encrypt 0x32fa24
XXTEA 包装 crypt_util13xxtea_encrypt 0x32e83c
XXTEA 核心 hyudbxxt13xxtea_encryptEPjj 0x252700
MD5 派生 md5_char16 0x32e71c
AES 加密 UdbAESUtil7encrypt 0x250038
AES key 管理 AESkeyMgr6getkey 0x26871c
OTP 入口 BusinessCfg6getOtp 0x26916c

Hook 脚本:/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/scripts/hook_huya_crypto.js Runner/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/scripts/run_huya_crypto.py


八、sessHex32 = appKey(已确认)

sessHex32 = "865a4924a40897ac1fcfe6b4c2cbb0e3" 就是 appId=5008 的 appKey

验证(appSign 算法 md5(f"{appId}{versionCode}{appKey}")[:8]):

md5("5008" + "115315" + "865a4924a40897ac1fcfe6b4c2cbb0e3")[:8] = "60576904"  ✓

与已知 appSign(5008) 完全一致。该值不在 so/dex 硬编码,是运行时由上层传入(App 密钥)。


九、arg7 = 时间戳 << 16(已确认)

arg7 是 XXTEA 的输入(8 字节 ulong),规律:

arg7 = System.currentTimeMillis() << 16     # 低 16 位恒为 0

Frida 实测 5 个样本,arg7 >> 16 与事件时间戳差值 <600ms(hook 传播延迟)。


十、XXTEA key 源 = uid 字符串(已确认)

hyudb_otp_encrypt 的 x0XXTEA key 源)= uid 十进制字符串

  • 登录后:"1471238907296"(用户 uid)→ md5(uid)[:16]hex 作 XXTEA key
  • 匿名(未登录):空字符串 → md5("")[:16]hex

十一、设备指纹(arg5)= 数美 SDK 黑盒(需缓存/设备获取)

设备指纹是数美(TrustKernel)SDK 生成的黑盒二进制(非标准 JCE),同一设备跨调用稳定:

bOTP 类型) 指纹开头 长度 说明
2, 4 0a80ee5642... 114B 缓存同值(设备指纹 A
3 0 无设备指纹
5 0a005e3748... 114B 另一指纹类型(设备指纹 B

无法在纯 Python 复刻(依赖设备硬件信息 + 数美算法)。两种可行方案:

  1. 缓存:对同一设备,hook 一次拿到指纹后持久化复用;
  2. 设备获取:在设备上通过 hook createWupDeviceInfo / 数美 SDK 实时生成。

十二、待确认项(剩余)

  1. b=1,6,7,8 的 key 映射rodata 推断,需 hook 对应场景确认(影响较小,登录主流程走 b=2)。
  2. 设备指纹的稳定周期:同一设备指纹是否长期不变,还是含时间戳会刷新(跨调用已稳定,跨天待验证)。
  3. App 登录主流程走哪个 b:观察到的 b=2/3/4/5 是启动期匿名 getOtp 的不同类型,真正的密码登录提交需在登录动作时确认 b 值。