虎牙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
@@ -0,0 +1,186 @@
# 虎牙 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),同一设备跨调用稳定:
| b(OTP 类型) | 指纹开头 | 长度 | 说明 |
|---|---|---|---|
| 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 值。