# 虎牙 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(" 关键坑: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 复刻**(依赖设备硬件信息 + 数美算法)。两种可行方案: 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 值。