feat(huya): 证书算法完全闭环 - 差分法证明标准AES-128-ECB并复现铸证

- NativeFunction直调差分: FIPS-197 KAT逐位一致, UdbAESUtil无魔改
- 证书 = b64([0x0C][key_idx][AES128-ECB(key16, zeropad(P1_187B))])
- P1结构: [010400][5008][u16+20B随机][u16+40B指纹hex][u16+114B hyCred]
- 密钥: rodata默认三条(AESkeyMgr id=3/4/5) + 服务端下发会话钥(key_idx区分)
- tools/cert_forge.py 重铸证书与活体密文 FULL MATCH
This commit is contained in:
yml2213
2026-08-25 15:21:11 +08:00
parent 2076510df9
commit a0ba2fe020
7 changed files with 771 additions and 0 deletions
@@ -0,0 +1,86 @@
# 虎牙 udb 登录证书(biz_token)算法 — 差分法完全闭环(2026-08-25
> 状态:**加密算法 100% 复原,Python 重铸与活体密文逐位一致**
> 方法:静态反汇编(objdump) + Frida NativeFunction 直调差分 + C1 构造器抓钥
> 工具:`tools/udb_aes.py`(参考实现) `tools/cert_forge.py`(解析/铸造) —— 自检均通过
> 证据:`evidence/aes_diff.json` `evidence/cert_keycap.json`
---
## 一、结论一句话
```
证书 = base64( [0x0C][key_idx][ AES-128-ECB(key16, zeropad(P1_187B)) ] )
```
- **UdbAESUtil 是教科书级标准 AES-128-ECB**,无任何魔改:
- FIPS-197 KAT 直调逐位一致:`Cipher(00112233..ff, key000102..0f) = 69c4e0d8...`
- KeyExpansion 标准展开,轮密钥按「字表转置」存储(176B @ this+0x208
- 对象布局:+0x00 vptr | +0x08 S-box拷贝 | +0x108 InvS-box拷贝 | +0x208 轮密钥
- ZeroPadding:补 `\0` 到 16 倍数,**不追加整块**(187→192)。
- 此前"标准解密对不上"的原因:**拿错了密钥**(真钥不是 HuyaUdb 主密钥字符串本身)。
## 二、P1 明文结构(187B,两份活体样本逐字段对齐)
| 字段 | 长度 | 说明 |
|---|---|---|
| `[01][04][00]` | 3 | 固定头 |
| `"5008"` | 4 | appId |
| `[u16_le=20]` + rnd | 22 | 每次生成随机(os.urandom 即可) |
| `[u16_le=40]` + fp | 42 | 设备指纹 ASCII hex `02df39879743...`,设备绑定跨会话不变 |
| `[u16_le=114]` + cred | 116 | **hyCred 二进制**=base64(udb_cred) 解码),账号绑定 |
## 三、密钥机制(AESkeyMgr
- rodata `0x1bc810` 内置三条 24 字符默认密钥(`AESkeyMgr::C2` 注册 id=3/4/5):
```
4VYcPdvKKqjBHZtCmbroRXHk ← 本次活体实测在用(encode_aes 收整条,C1 只取前16字节)
xXEDWqiKLGwEZ6HubEiswCqK
3FMHubdKosFrhmXNLHTNHZwe
```
- 证书第二字节 = **key_idx**:今日样本 `0x20` ↔ 上表第一条;更早样本 `0x50`
用三条默认钥均解不开 ⇒ 存在**服务端下发的会话密钥**(对应 dckey/check 的
NEWKEY/key_id 老线索)。服务端按 idx 选钥解密。
- ⚠️ `HuyaUdb1928374650qwertyuiop` 是本地登录态文件加密用的主密钥,
**不是**证书密钥(此前误判的根源)。
## 四、调用链(函数地址级)
```
HandlerGetH5InfoEx::onHandler @0x38d548
└ BusinessCfg::getOtp @0x2689f4
└ gen_biz_token(BIZTOKEN,...) @0x3307e4 # P1 组装(enpack_header/body,cred_packls,xxtea)
└ encode_aes(P1, key24str, out) @0x330218 # out.assign(常量); C1(key.data())
├ UdbAESUtil::C1(uchar* key16) @0x24eb08 # ★抓钥点: 每次必经
└ UdbAESUtil::encrypt @0x250038 # 补零逐块 -> _encrypt@0x24f9f0 -> Cipher@0x24f314(10轮)
└ CBase64::Encode
```
全部符号在 `.dynsym` 导出(binary stripped 但 dynsym 完整),可直接 NativeFunction。
## 五、复现步骤
```bash
# 1) 差分验证(直调 C1/Cipher, FIPS-197 KAT)
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python scripts/hook_aes_diff.py
# 2) 抓当前会话证书密钥 + 完整 wupData
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python scripts/hook_cert_keycap.py
# 3) 离线重铸自检
.venv/bin/python tools/cert_forge.py # FULL MATCH: True
# 4) 参考实现自检
.venv/bin/python tools/udb_aes.py
```
frida 注意事项:手机端 frida-server 14.2.18,必须用 RE 项目 venv(frida==14.2.18)
时序不可变:force-stop → spawn挂起 → bypass_msaoaid_maps_art_callsite +
mask_frida_maps_only 双脚本 → resume → 10s → patch_guard → 业务脚本。
直调 libc++ string 时**禁止让 push_back 触发 realloc**(会对 frida 缓冲区
operator delete 导致 abort——第一次闪退即此因,非反调试)。
## 六、剩余开放项
1. **key_idx↔密钥映射的服务端语义**:0x20/0x50 的生成规则、下发通道
dckey/check? 登录态 JSON?)、轮换周期。工程上可绕过:直接 hook C1 取当期钥。
2. **任意账号 hyCred 获取**(硬骨头②):APP WUP 密码登录已通,
待从登录态/HA.getCred 提取目标账号 114B cred 即可离线铸其证书。
3. 铸造证书的活体验证:新随机数重建证书 → bindQrLoginUser(下一步)。