- 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辅助函数 - 新增证据金标准 + 协议文档
6.7 KiB
虎牙 App 登录 — 加密算法动态验证(Frida 最终结论)
状态:算法链已 100% 破解并双向验证;arg7/appKey/XXTEA key 源已确认 实现:
tools/huya_app_crypto.py(自检通过) 方法:Frida spawn + libmsaoaidsec 绕过 + hook libudbauthunify.so 导出符号 样本:com.duowan.kiwi 13.4.22(versionCode 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 层(已验证)
| 项 | 值 |
|---|---|
| 算法 | 标准 XXTEA(DELTA=0x9e3779b9,rounds=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) 返回的 key,b=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] # 固定 header(arg1=2)
[u16_le len][xxtea密文] # arg7 加密结果(8字节→12字节)
[u16_le len][设备指纹] # arg5(114字节左右)
六、函数参数映射(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 的 x0(XXTEA key 源)= uid 十进制字符串:
- 登录后:
"1471238907296"(用户 uid)→md5(uid)[:16]hex作 XXTEA key - 匿名(未登录):空字符串 →
md5("")[:16]hex
十一、设备指纹(arg5)= 数美 SDK 黑盒(需缓存/设备获取)
设备指纹是数美(TrustKernel)SDK 生成的黑盒二进制(非标准 JCE),同一设备跨调用稳定:
| b(OTP 类型) | 指纹开头 | 长度 | 说明 |
|---|---|---|---|
| 2, 4 | 0a80ee5642... |
114B | 缓存同值(设备指纹 A) |
| 3 | 空 | 0 | 无设备指纹 |
| 5 | 0a005e3748... |
114B | 另一指纹类型(设备指纹 B) |
无法在纯 Python 复刻(依赖设备硬件信息 + 数美算法)。两种可行方案:
- 缓存:对同一设备,hook 一次拿到指纹后持久化复用;
- 设备获取:在设备上通过 hook
createWupDeviceInfo/ 数美 SDK 实时生成。
十二、待确认项(剩余)
- b=1,6,7,8 的 key 映射:rodata 推断,需 hook 对应场景确认(影响较小,登录主流程走 b=2)。
- 设备指纹的稳定周期:同一设备指纹是否长期不变,还是含时间戳会刷新(跨调用已稳定,跨天待验证)。
- App 登录主流程走哪个 b:观察到的 b=2/3/4/5 是启动期匿名 getOtp 的不同类型,真正的密码登录提交需在登录动作时确认 b 值。