- 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
87 lines
4.2 KiB
Markdown
87 lines
4.2 KiB
Markdown
# 虎牙 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(下一步)。
|