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

187 lines
6.7 KiB
Markdown
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 登录 — 加密算法动态验证(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 值。