虎牙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,309 @@
# 虎牙 App 密码登录 — WUP 明文抓包实证(最终)
> 状态:**密码登录完整 WUP 请求已抓到(TLS 加密前明文)**
> 证据:`/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/derived/huya-wup-full3.jsonl`
> 方法:hook `createWupRequestData<AppLgnPasswordLoginReq>` onLeave 拿到序列化后的 WUP 字节
---
## 一、密码登录 WUP 请求(核心结论)
| 项 | 值 |
|---|---|
| **servant** | `huyaudbwebui` |
| **func** | `hypasswordLogin` |
| **协议** | WUP / TAF(JCE) 二进制序列化 |
| **msgType** | 4097 (0x1001) = `MsgLogin.mMsgId` |
| **请求大小** | 1356 字节 |
| **密码** | SHA1(明文) hex,无盐(`772ed992b0e161276f44ec63671e60155c506294` |
| **name** | `hy_300023887``hy_` + 虎牙号) |
---
## 二、WUP 帧完整结构(已 100% 逆向定稿)
> 金标准文件:`evidence/wup_passwordlogin_taf.bin`1015 字节 TAF 二进制)+ `evidence/wup_passwordlogin_base64.bin`1356 字节 base64,即 HTTP body
> HTTP`POST https://udblgn.huya.com/open/hy/passwordLogin``Content-Type: application/x-wup`
### 2.1 外层:WUP RequestPacket(标准 JCE/TAF 序列化)
```
[0:4] uint32 BE 总长 = 1015
JCE RequestPacket 字段(head = (tag<<4)|type:
tag1 iVersion = 3 (INT8: 10 03)
tag2 cPacketType = 0 (ZERO: 2c)
tag3 iMessageType = 0 (ZERO: 3c)
tag4 iRequestId = session(3251699) (INT32: 42 00 31 9d f3)
tag5 sServantName = "huyaudbwebui" (STR1: 56 0c + 12B)
tag6 sFuncName = "hypasswordLogin" (STR1: 66 0f + 15B)
tag7 sBuffer = MAP{...} (SIMPLE_LIST: 7d 00 01 03 c1 + 961B)
tag8 iTimeout = 0 (ZERO: 8c)
tag9 context = {} (空map) (MAP: 98 0c)
tag10 status = {} (空map) (MAP: a8 0c)
```
> 关键澄清:之前误认为 `'f'(0x66)` 和 `'}' (0x7d)` 是分隔符,实为 JCE 字段头:
> - `0x56` = 字段5(sServantName)+STRING1
> - `0x66` = 字段6(sFuncName)+STRING1
> - `0x7d` = 字段7(sBuffer)+SIMPLE_LIST
### 2.2 中层:sBuffer961 字节)是一个 MAP
```
MAP (2条目):
"_wup_data" → 915 字节 (内层登录请求 struct)
"wupudbrequest_v0" → 5 字节 = 02 00 31 9d f3 (版本2 + session INT32)
```
### 2.3 内层:_wup_data915 字节)登录请求 struct(完整字段映射)
```
_wup_data = STRUCT {
field0 = STRUCT { # 元数据+风控
tag0 = 0
tag1 = "1.0" # 协议版本
tag2 = 207B JSON (associationId/funcName/group/id/session/step/stillLogin/traceId/type/uid/userContext)
tag3 = "5008" # bizAppid
tag4 = 3 (INT8) # = type
tag5 = 180B safedeviceid # 设备指纹(base64)
tag6 = ""
tag7 = ""
tag8 = 210B userAction JSON # 风控行为轨迹
tag9 = ""
}
field1 = STRUCT { # 设备基础信息
tag0 = "ed0db8334cadd236c00cadf7e11ab5a5" # hdid (32 hex)
tag1 = "13.4.22" # App版本
tag2 = "1.0.80138" # SDK版本
tag3 = ""
tag4 = "127.0.0.1" # 占位
tag5 = "xiaomi" # 厂商
tag6 = ""
}
field2 = STRUCT { # 设备详情
tag0 = 1 (INT8)
tag1 = "M2102J2SC" # 型号 thyme
tag2 = "02df398797432eadefcc12767119ad5e80999389" # 设备唯一ID(40 hex)
tag3 = "android"
tag4 = "M2102J2SC,30,11" # 型号,SDK,版本
tag6 = "1080" # 屏宽 (tag5缺失)
tag7 = "2120" # 屏高
tag8 = "7c5387e0539c023c31c4ff0e807e7256117385ee" # 另一设备ID(40 hex)
}
field3 = STRUCT { # 登录凭证
tag3 = "hy_300023887" # name (hy_虎牙号)
tag4 = "772ed992b0e161276f44ec63671e60155c506294" # password = SHA1(明文)
tag5 = LIST["5008"] # appid列表
tag6 = 1 (INT8)
tag7 = {} (空map)
tag8 = [] (空simplelist)
}
}
```
### 2.4 原始可读字符串(完整)
TAF 序列化,可读字符串全量提取如下(关键字段用换行标出):
```
AAAD9xADLDxCADGd81YMaHV5YXVkYndlYnVpZg9oeXBhc3N3b3JkTG9naW59... ← WUP 头
└─ "huyaudbwebui" ← servant
└─ "hypasswordLogin" ← func
_wup_data 字段(关联元数据 JSON:
{"associationId":8193,
"funcName":"hypasswordLogin",
"group":1,
"id":4097, ← msgType
"session":3251699,
"step":1,
"stillLogin":false,
"traceId":"0b8f098ff64a5bdc-23473-94783833787595172451",
"type":3,
"uid":0,
"userContext":""}
业务数据:
5008 ← bizAppid (appId)
PQwemAN9NHkZKoMqVT... ← safedeviceid (设备指纹, base64)
userAction JSON:
{"curl":"登录页",
"furl":"我的",
"latitude":"-1.0",
"longitude":"-1.0",
"ssid":"",
"user_action":[
{"id":"24","time":"1787595171950","x":"277","y":"1057"},
{"id":"11","time":"1787595172444","x":"296","y":"952"}]}
设备信息字段:
ed0db8334cadd236c00cadf7e11ab5a5 ← hdid (硬件ID)
13.4.22 ← App 版本
1.0.801386 ← 某版本号
127.0.0.1 ← 回环地址(占位)
xiaomi ← 厂商
M2102J2SC ← 设备型号 (thyme)
M2102J2SC,30,11 ← 型号,SDK版本,版本
android ← 系统
1080 / 2120 ← 分辨率
7c5387e0539c023c31c4ff0e807e7256117385ee ← 另一个设备ID
登录凭证:
hy_300023887 ← name
772ed992b0e161276f44ec63671e60155c506294 ← password (SHA1)
结尾:
wupudbrequest_v0 ← WUP 请求类型标记
AAFAgAxnfOMmAyoDA== ← base64 尾巴(签名/校验)
```
---
## 三、WUP 请求的三层结构
```
┌──────────────────────────────────────────────────────┐
│ 第1层: WUP/TAF 外层 │
│ servant = "huyaudbwebui" │
│ func = "hypasswordLogin" │
│ _wup_data = { associationId, funcName, group, id, │
│ session, step, stillLogin, traceId, │
│ type, uid, userContext } │
├──────────────────────────────────────────────────────┤
│ 第2层: 业务数据 (AppLgnPasswordLoginReq) │
│ bizAppid = 5008 │
│ name = "hy_300023887" │
│ password = SHA1(明文) │
│ userAction = 风控行为 JSON │
│ safedeviceid / hdid / 设备信息 │
├──────────────────────────────────────────────────────┤
│ 第3层: 网络传输 │
│ TLS 1.3 → 116.162.209.x:443 (udblgn.huya.com) │
│ (自定义 BoringSSL / hyssl) │
└──────────────────────────────────────────────────────┘
```
---
## 四、网络层(实证)
- **密码登录走 TLS 443**(不是之前看到的 80 端口图片 CDN)
- 目标 IP`116.162.209.83-86``udblgn.huya.com`kunluncan CDN
- TLS 层:libhycrypto.so = 定制 BoringSSLhyssl),符号已 strip
- 连接池:App 启动时预建立,登录时复用
- **HTTP body = 原始 TAF 二进制(1015字节),NOT base64**base64 是 hook 层返回值的表示形式,发送前已解码)
---
## 四·五、纯 Python 重放验证(✅ 成功)
**关键结论:纯 Python 可以复刻密码登录请求,服务端返回完整登录响应。**
```
POST https://udblgn.huya.com/open/hy/passwordLogin
Content-Type: application/x-wup
Body: 原始 TAF 二进制 (1015字节, 非base64)
```
重放金标准请求(`wup_passwordlogin_taf.bin`)后,服务端返回 **1514 字节登录响应**
| 字段 | 值 |
|---|---|
| **uid** | `1199666914671` |
| **name** | `hy_300023887` |
| **手机号掩码** | `001******7524` |
| **token** | 346 字节 base64(已存 `evidence/huya_token.txt` |
| **uc_states** | `1`(成功标记) |
这证明:
1. ✅ 请求格式 100% 正确(服务端无 BAD_REQUEST
2. ✅ 设备指纹(safedeviceid/hdid)可复用(重放旧值仍通过)
3. ✅ userAction 可复用(旧轨迹未被风控拒绝)
4. ✅ TLS 指纹不阻断
> 注:此验证是「重放」,即复用已抓到的完整请求。要批量登录新账号,还需编码器动态构造新请求(替换 name/password/session/traceId/userAction)。
---
## 四·六、编码器 + 动态构造验证(✅ 完整成功)
**编码器已实现,逐字节复现金标准,且能动态构造全新请求。**
1. **`tools/huya_wup_encoder.py`** — 自检通过:用已知字段构造,输出与金标准逐字节一致(1015 字节)
2. **动态构造测试** — 用新 session3251799)、新 traceId 构造全新请求(1009 字节),发送后返回:
| 字段 | 值 |
|---|---|
| **uid** | `1199666914671` |
| **name** | `hy_300023887` |
| **手机号掩码** | `001******7524` |
| **token** | 全新 token`XAQBRJ3H...` |
这证明 **纯 Python 可以完整复刻虎牙密码登录**
1. ✅ 帧格式(JCE/WUP)逆向定稿
2. ✅ 编码器逐字节复现
3. ✅ session/traceId 可自由生成(服务端接受新值)
4. ✅ 设备指纹可复用
5. ✅ 动态构造请求返回有效登录响应(uid + token)
### userAction 随机化(✅ 已实测)
`userAction` 只是请求里的一个普通 JSON 字符串字段,编码器已暴露 `user_action_json` 参数。实测**随机坐标 + 随机时间戳**同样能通过登录(返回 uid + token),服务端对该字段不做严格校验。
编码器提供辅助函数:
- `make_user_action(now_ms=None)` — 生成随机 userAction JSON(坐标/时间戳/点击间隔随机)
- `make_trace_id(pid=0)` — 生成 traceId
> 未实测定论:批量高频登录时,服务端是否按**账号/设备/IP 维度**限流或风控。这需要实测才能确认,与 userAction 无关。
---
## 五、对「纯 Python 复刻」的启示(已实证更新)
**TLS 指纹已实测:不阻断。** 普通 Python `urllib` 直连 `udblgn.huya.com` 能正常收到业务响应。
要脱离 App 用 Python 发密码登录请求,需要:
1.**密码**`sha1(明文)` —— 已破解,纯 Python 可做
2.**name**`hy_虎牙号` —— 已知
3.**servant/func**`huyaudbwebui` / `hypasswordLogin` —— 已知
4.**WUP 帧格式**:标准 JCE RequestPacket,已 100% 定稿(见第二节)
5.**Content-Type**`application/x-wup`body = 原始 TAF 二进制
6.**重放验证**:服务端返回 uid + token(纯 Python 可行)
7.**TAF/JCE 编码器**`tools/huya_wup_encoder.py`(自检通过,逐字节复现)
8.**_wup_data 元数据**`session`/`traceId` 可自由生成(服务端接受新值)
9.**设备指纹(safedeviceid/hdid**:可复用旧值(缓存一次即可)
10.**userAction**:随机坐标/时间戳即可通过(编码器内置 `make_user_action()`
> 唯一待实测项:批量高频登录时的账号/设备/IP 维度限流(与 userAction 无关)。
---
## 六、关联证据文件
- `huya-wup-plain.jsonl` — 第一次明文抓包(sendMsg JSON 层,305 事件)
- `huya-wup-full3.jsonl` — 完整 WUP 序列化(含本次密码登录 1356 字节)
- `wup_passwordlogin_taf.bin` — 金标准:1015 字节 TAF 二进制(= HTTP body,编码器自检依赖)
- `wup_passwordlogin_base64.bin` — 金标准:1356 字节 base64hook 返回值)
- `wup_response_badreq.bin` — 服务端响应帧(383 字节,returnCode=104
- `tools/huya_wup_encoder.py` — WUP 帧编码器(自检通过,逐字节复现金标准,含 `make_user_action`/`make_trace_id`
- hook 脚本:`hook_huya_wup_full.js`
- runner`run_huya_netcapture.py`
> 含有效登录 token 的响应文件(`wup_response_login.bin`、`wup_response_new_session.bin`、`huya_token.txt`)已加入 `.gitignore`,不提交。
---
## 七、结论与下一步
已完成并验证:
1.**TLS 指纹**:实测不阻断(Python urllib 正常收到业务响应)
2.**TAF 编码器**`tools/huya_wup_encoder.py`,逐字节复现金标准
3.**纯 Python 登录**:重放 + 动态构造均返回 uid + token
4.**userAction 随机化**:随机坐标/时间戳通过
待实测(唯一未定论项):
1. **批量高频登录的限流/风控**:需用编码器连续发请求,观察账号/设备/IP 维度是否触发限制(错误码、频率阈值)
2. **集成进 `core/huya/login.py`**:把 APP 密码登录(`/open/hy/passwordLogin`)作为 Web 登录被风控阻断时的替代路径
@@ -0,0 +1,131 @@
# 虎牙 App 密码登录协议 — Frida 抓包实证
> 状态:**密码登录完整链路已抓包实证**
> 方法:spawn + libmsaoaidsec 绕过 + hook sendMsg/jbyteArray2str/str2jbyteArrayTLS 加密前的明文层)
> 证据文件:`/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/derived/huya-wup-plain.jsonl`
> Java 反编译:`/tmp/hy_jadx5/sources/com/huyaudbunify/`
---
## 一、密码登录请求(msgType = 0x1001 = 4097
`MsgLogin.mMsgId = 4097 (0x1001)`Java 层 `HuyaAuth.login()``sendMessage(4097, json)` → native `sendMsg(JNIEnv, jclass, jlong, jbyteArray)`
**请求 JSON**TLS 加密前明文,抓包实证):
```json
{
"bizAppids": [],
"isAuthLogin": false,
"lgnExtParam": {},
"name": "hy_300023887",
"password": "772ed992b0e161276f44ec63671e60155c506294",
"userAction": "{\"curl\":\"登录页\",\"furl\":\"我的\",\"latitude\":\"-1.0\",\"longitude\":\"-1.0\",\"ssid\":\"\",\"user_action\":[{\"id\":\"24\",\"time\":\"1787593636012\",\"x\":\"253\",\"y\":\"1573\"},...]}"
}
```
| 字段 | 值/规则 | 说明 |
|---|---|---|
| `name` | `"hy_" + 虎牙号` | 虎牙号 = 纯数字账号 |
| `password` | **标准 SHA1(明文密码) hex,无盐** | 40 字符小写 hex |
| `isAuthLogin` | false | 普通密码登录 |
| `bizAppids` | [] | 目标业务 appId 列表(空=主 app |
| `lgnExtParam` | {} | 扩展参数 |
| `userAction` | 风控行为 JSON | 点击轨迹、时间戳、坐标、curl/furl、经纬度 |
**密码哈希**(用户确认 + 抓包实证):
```
password = sha1(明文密码).hexdigest()
例如 sha1("某密码") = "772ed992b0e161276f44ec63671e60155c506294"
```
> 无盐、无 HMAC、无 RSA,就是裸 SHA1。
---
## 二、登录响应(msgType=0x1001 返回)
```json
{
"lastLgnTime": 1787593640035,
"token": "9QDzAAEDAAAAb2W4URcBAAABAGNI4jSgAQQANTAwOCgAMDJkZjM5ODc5NzQzMmVhZGVmY2MxMjc2NzExOWFkNWU4MDk5OTM4OQQANTAwOKIAAhKJiMHWd/m1LAKSxxpg1cjDxJEIG+ooj8BGSC5NBXb23Trpcz85MOqpZxhLmSeQ9c2MGzBk0oU4iR2PR0Ak6/t50Ej9sdzHjDLa2wcDiRofNyEZzuU1KcIQqrGDwwwfouEwuagTPI0ZrP5vAFJe8uccW5BKRVoqDDsXc09nIW7HX5Yp+9cT0nr8t/8+/3IdTXhFOZYmDN6gHaQI+ToQivXIAAAAAA==",
"tokenType": 2,
"uid": 1199666914671
}
```
| 字段 | 说明 |
|---|---|
| `token` | Base64,内含 TAF 二进制(`9QDzAAED...` 头),是后续凭据登录/接口调用的 token |
| `tokenType` | 2(密码登录产生的 token 类型) |
| `uid` | 1199666914671(该账号 uid |
| `lastLgnTime` | 上次登录时间戳(ms |
---
## 三、完整登录调用链(Java → native → 网络)
```
Java: HuyaAuth.login(name, password, isAuthLogin, bizAppids, callback)
└─ MsgLogin (mMsgId=4097=0x1001)
└─ ReqLogin.toString() → JSON(含 SHA1 密码)
└─ HuyaAuthMgr.sendMessage(4097, jsonBytes)
└─ HuyaAuthCore.sendMsg(jlong, byte[]) [native JNI]
└─ sendMsg(JNIEnv, jclass, jlong msgType, jbyteArray)
└─ jbyteArray2str → 明文 JSON
└─ UdbHandlerMgr.getPlatFormMsgHandler() 处理
└─ TAF/WUP 包装(servant + func
└─ TLS (libhycrypto 自带 BoringSSL)
└─ 目标: udblgn.huya.com / udb3lgn.huya.com (443)
```
**关键符号地址**libudbauthunify.so):
| 函数 | 地址 | 作用 |
|---|---|---|
| `sendMsg` | 0x26244c | Java→native 消息入口(msgType + jbyteArray |
| `jbyteArray2str` | 0x262b00 | jbyteArray→std::string(请求明文) |
| `str2jbyteArray` | 0x262be8 | std::string→jbyteArray(响应明文) |
| `hyudb_otp_encrypt` | 0x32fa24 | OTP/dckey 加密(XXTEA+AES |
---
## 四、msgType 观察(本次抓包 305 事件)
| msgType | 次数 | 含义 |
|---|---|---|
| `0xb000003` | 92 | getLoginData(读取登录态/token),高频轮询 |
| `0xb000021` | 4 | setDeviceInfohdid + safedeviceid |
| **`0x1001` (4097)** | 1 | **密码登录**MsgLogin |
| `0xb000001` | 1 | 登录后 getLoginData |
| `0xb000024` | 1 | 登录后获取数据 |
| `0xb000013` | 1 | getConfigTAF 二进制,servant=mobileuif |
---
## 五、与 OTP 加密链的关系
密码登录时序(抓包时间线):
```
1787593639831 sendMsg msgType=0x1001 ← 密码登录 JSON 提交
1787593639988 crypto-otp b=2 ← 紧接着触发 OTP/dckey 加密(AESkeyMgr b=2
1787593639996 sendMsg msgType=0xb000001 ← 登录成功后的凭据流程
```
即:**密码登录本身用 SHA1 密码 + WUP 明文 JSON;登录成功后,native 层用 OTP 加密链(XXTEA+AES)生成 dckey 凭据,供后续业务接口(token/cred)使用**。
之前破解的 OTP 加密链(`tools/huya_app_crypto.py`)对应的是**登录后的凭据续期/设备绑定**,不是密码本身。
---
## 六、servant / URL
- 密码登录 CGI`/{servantName}/hypasswordLogin`
- URL`https://udblgn.huya.com/open/hy/passwordLogin`(国内版)
- servant 名运行时传入,登录类走 `huyaudb`/`huyaudbwebui`
- 设备信息:`getCgi()``/{servantName}/setDeviceInfo`hdid + safedeviceid
---
## 七、待确认项(剩余,影响较小)
1. **userAction 是否必填/可复用**:风控行为采集(点击轨迹),批量登录时能否固定/复用同一份,还是每次都要真实模拟。
2. **登录接口是否校验 OTP dckey**:密码登录请求本身是否需要先拿到 dckey(本次抓包 OTP 在密码登录之后触发,说明密码登录可能不依赖 dckey)。
3. **token 的 TAF 结构解析**:响应 token 的 Base64 内是 TAF 二进制(`9QDzAAED...`),需解析出 uid/appid/时间戳等字段。
+194
View File
@@ -0,0 +1,194 @@
# 虎牙 App 登录 — APK 静态逆向分析(libudbauthunify.so
> 目标:`apks/huya-13.4.22-115315-live.apk`158MBversionCode=115315
> 分析对象:`lib/arm64-v8a/libudbauthunify.so`4.7MBUDB 认证统一库)
> 方法:`nm -D` 导出符号 + `objdump` 反汇编 + `strings` 字符串提取(纯静态,无需 hook)
> 结论:**App 登录 = WUP 长连接 + XXTEA/AES 双层加密**,全部加密函数与 servant 已定位。
---
## 一、关键结论(一句话)
虎牙 App 登录的**设备风控加密**由 `libudbauthunify.so` 实现,算法链是:
```
明文 → cred_pack 打包 → XXTEA 加密 → cred_pack 拼装 → MD5 派生 key → AES-128-ECB 加密 → 密文
```
`dckey` = **Login Credential(登录凭据)**,通过 `MsgGetCred` / `MsgRequestLgnCred` 获取,加密后经 `udbdf.huya.com/dckey/check` 上传。
---
## 二、关键 native 库定位
| so 库 | 大小 | 作用 |
|---|---|---|
| **`libudbauthunify.so`** | 4.7MB | **UDB 认证统一库(核心)**,含全部登录/加密逻辑 |
| `libhycrypto.so` | 1.9MB | BoringSSLAES/RSA 标准实现) |
| `libhydeviceid.so` | 4.1MB | 设备 ID 生成 |
| `libtscsdk.so` | 1.5MB | 数美(TrustKernel)设备指纹 SDK(对应 zztfly 请求) |
| `libtea.so` | 409KB | XXTEA 算法 |
| `libmtpencrypt.so` | 809KB | MTP 加密 |
---
## 三、加密体系(已 100% 破解,来自反汇编)
### 3.1 加密函数清单
| 函数 | 地址 | 说明 |
|---|---|---|
| `hyudb_crypt_util::xxtea_encrypt` | 0x32e83c | **XXTEA 加密**(第一层) |
| `hyudb_crypt_util::xxtea_decrypt` | 0x32ece8 | XXTEA 解密 |
| `hyudb_crypt_util::md5_char16` | 0x32e71c | **MD5 派生 16 字节 key** |
| `hyudb_crypt_util::md5_char32` | 0x32e7ac | MD5 派生 32 字节 |
| `UdbAESUtil::encrypt` | 0x250038 | **AES-128-ECB 加密**(第二层) |
| `UdbAESUtil::decrypt` | 0x2501a4 | AES 解密 |
| `AESkeyMgr::getkey` | 0x26871c | **AES key 缓存管理**map<int,map<int,string>> |
| `hyudb_packet_util::cred_pack` | 0x45xxxx | **cred(dckey) 二进制打包** |
| `hyudb_packet_util::cred_unpack` | — | cred 解包 |
| `encode_xxtea` / `decode_xxtea` | 0x330058/0x3306cc | XXTEA 编解码封装 |
### 3.2 加密模式确认(反汇编证据)
**AES 是 ECB 模式 + ZeroPadding**,证据来自 `UdbAESUtil::encrypt`0x250038)反汇编:
```asm
; 计算长度 & 0xf,非 16 倍数则 push_back(0) 补齐(ZeroPadding
and w11, w11, #0xf
...
bl push_back(0) ; 补零
; 每 16 字节独立调用 _encrypt(单块),无 IV 参与 → ECB 模式
loop:
bl UdbAESUtil::_encrypt(ptr16, out) ; 逐块 AES
add x24, x24, #0x10 ; +16
```
### 3.3 完整加密调用链(`hyudb_otp_encrypt` 0x32fa24 还原)
```
hyudb_otp_encrypt(x0=key, w1=2, w2=len, x3..x5=字段, w6=4, x7=buf, out)
├─ cred_pack(sp+0xb0, x7) # 打包第一个字段
├─ xxtea_encrypt(sp+0xc8, sp+0xe0, x24) # XXTEAkey = x0 参数
├─ cred_pack(sp+0x98, <header字段>) # 打包 header
├─ cred_pack(sp+0x98, sp+0xc8) # 打包 XXTEA 密文
├─ cred_pack(sp+0x98, x23) # 打包第5参数字段
├─ AESkeyMgr::getkey(i, i) # 取 AES key 字符串
├─ string::insert(...) # key 拼接
├─ md5_char16(key_str) # MD5 派生 16 字节 key
├─ UdbAESUtil::C1(md5key) # 用 MD5 结果构造 AES
└─ UdbAESUtil::encrypt(packed, md5key) # AES-128-ECB 加密
```
调用方:`BusinessCfg::getOtp(uid, otp, ...)`0x268bbc~0x268ca0)→ `hyudb_otp_encrypt(...)`
---
## 四、硬编码 key 候选(rodata 段)
| 字符串 | 长度 | 备注 |
|---|---|---|
| **`HuyaUdb1928374650qwertyuiop`** | 27B | **最强候选**"HuyaUdb" + 数字 + 字母键盘序) |
| `275d0ff676c0d65114acdefbd2ad87a3` | 16B | 32 hex,正好 AES-128 key 长度 |
| `0123456789ABCDEF` / `0123456789abcdef` | 16B | 常见默认 key |
> XXTEA key 与 AES key 源需 hook 最终确认(hook `xxtea_encrypt` 第3参、`md5_char16` 输入)。
---
## 五、dckey = Login Credential
符号证据链:
```
MsgGetCred / MsgRequestLgnCred / MsgResponseLgnCred
HandlerGetCred / HandlerRequestLoginCred / HandlerResponseLoginCred
AutoCredLogin(自动凭据登录)
BusinessCfg::getCred / getCredEx / setSafeDeviceId
AUTO_CRED_LOGIN_TIMER
```
即:**dckey 就是「登录凭据(credential)」**,App 启动后用设备指纹换取,登录时作为风控凭证提交。
---
## 六、完整登录 servant 清单(TAF/WUP 结构名)
从 so 的 rodata 提取全部 `wup.*` 结构名:
### 登录类(核心)
| servant/结构 | 用途 |
|---|---|
| **`AppLgnPasswordLoginReq/Resp`** | **App 密码登录** |
| **`AppLgnOtpLoginReq/Resp`** | **App 一键(OTP)登录** |
| **`AppLgnCredentialLoginReq/Resp`** | **dckey 凭据登录** |
| **`AppLgnSmscodeLoginReq/Resp`** | 短信验证码登录 |
| `AppLgnMobileSendSmsReq/Resp` | 手机号发短信 |
| `AppLgnSessionSendSmsReq/Resp` | 会话发短信 |
| `AppLgnSecondAuthLoginReq/Resp` | 二次认证 |
| `AppLgnAntiViolentLoginReq/Resp` | 反暴力登录 |
| `AppLgnRefreshPicReq/Resp` | 刷新验证码图 |
| `AppLgnThirdLoginReq/Resp` | 第三方登录 |
### 密码/注册类
```
AppPasswordLoginReq/Resp # 密码登录(另一组)
AppCredentialLoginReq # 凭据登录
AppAnonymousLoginReq/Resp # 匿名登录
AppMobileRegisterReq # 手机注册
AppRegSendSms / AppRegCheckMobile / AppRegCheckUser
AppRegSmscodeRegister / AppRegSmscodeModifyPassword / AppRegSmscodeResetPassword
AppRegTokenRegister / AppRegTokenModifyPassword / AppRegTokenResetPassword
AppRegUidVerifySmscode
AppBindLoginMobile / AppBindNewMobile(绑定手机)
AppBypassRouteReq/Resp # 旁路路由
```
### servant 名(TAF 服务名)
```
huyaudb # UDB 主服务
huyaudbwebui # 抓包里 wsapi.huya.com 的 servant
huyaudbnimoui # NIM UI 服务
passport # 护照服务
```
---
## 七、其他关键 token/字段
```
violentToken / sessionToken # 反暴力 token / 会话 token
enpack_header / depack_header # header 打包/解包(含 NEWKEY
key_version[%d] / key_id[%d] # key 版本/ID 管理
lgnExtParam / lgnProtect # 登录扩展参数 / 登录保护
loginAppId / loginAppName / loginDeviceName / loginStrategy / loginType
```
`enpack_header` + `NEWKEY` 解释了抓包里 dckey body 的 **100 字节固定头**:那是打包的 header(key 版本、key_id、固定字段),加密只作用于变长数据体。
---
## 八、给 hook 的精确落点(静态分析直接给出)
| 目标函数 | hook 什么 | 得到什么 |
|---|---|---|
| `hyudb_crypt_util::xxtea_encrypt` | 第3参(XXTEA key) + 第2参(明文) + 返回(密文) | **XXTEA key + 明文结构** |
| `hyudb_crypt_util::md5_char16` | 输入 string + 输出 16B | **AES key 源**MD5 前明文) |
| `AESkeyMgr::getkey` | 返回值 string | **AES key 字符串** |
| `UdbAESUtil::encrypt` | 入参(明文) + 返回(密文) | 验证 AES 模式/填充 |
| `hyudb_packet_util::cred_pack` | 各字段 | **dckey 二进制结构** |
| `BusinessCfg::getOtp` | 入参 | OTP 生成逻辑 |
| `MsgRequestLgnCred` 序列化前 | WUP 结构 | **登录凭据请求明文** |
| OkHttp `CertificatePinner.check` | — | SSL unpin,解 cdn.wup 长连接 |
---
## 九、抓包 ↔ so 对应关系(验证闭环)
| 抓包现象 | so 证据 |
|---|---|
| dckey body 前 100B 固定 | `enpack_header`/`cred_pack` 打包的固定 header |
| dckey body 后段变长 | XXTEA+AES 加密的数据体 |
| dfpReport t2 魔数 `571882cf664bb39401ee` | cred_pack 的固定标识 |
| selectOperator.t5 / dfpReport.t2 同前缀 13B | 同一固定 header(非 RSA,是打包结构头) |
| 登录走 cdn.wup.huya.com 抓不到 | App 登录 servant 走 WUP 长连接 + SSL Pinning |
@@ -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 值。
+212
View File
@@ -0,0 +1,212 @@
# 虎牙 App 登录逆向笔记
> 抓包来源:`/Users/yml/Desktop/抓包/hy/8.23/`(登录-1.har / 登录-5.har
> 分析工具:`tools/taf_decode.py`TAF/JCE 纯字节流解码)、`tools/hy_har_parse.py`HAR 批量解析)
> 状态:App 登录**前置风控链路**已完整解明;**登录提交(最后一跳)走 cdn.wup.huya.com 长连接 + SSL Pinning,尚未解出明文**。
---
## 一、结论速览
1. **App 登录 ≠ Web 登录**,是两套完全不同的协议栈:
- **Web 登录(旧,appId=5002**`udblgn.huya.com/web/v2/passwordLogin` + `udbrtt.huya.com/auth/pt/*` 滑块 + `df.huya.com/web/df/token|collect`。已在 `core/huya/login.py``e语言/huya/登录/虎牙操作.txt` 实现。
- **App 登录(新,appId=5008**`wsapi.huya.com`servant=`huyaudbwebui`)走 `getDfpConfig → selectOperator → dfpReport` 设备指纹,`udbdf.huya.com/dckey/check` 拿 dckey,运营商取号,最后登录提交进 `cdn.wup.huya.com` 长连接。
2. **Web 登录"风控加强"的本质**:Web 纯脚本无法伪造 App 侧的**设备态**dckey + dfp 指纹 + appId=5008),被风控拦下。登录接口本身没变,是**前置设备校验变严**。
3. **抓包抓不到登录的原因**(两层锁):
- TLS 层:`cdn.wup.huya.com` 与裸 IP`211.91.243.102` 等)有 SSL PinningReqable 中间人证书被拒,只留 CONNECT 隧道。
- 应用层:`statwup.huya.com` 的部分 body 是 `877de5c4...` 纯二进制(应用层加密),即使破 TLS 也还要再解一层。
---
## 二、App 登录完整链路(已解明的普通 HTTP 部分)
按时间顺序(登录-5.har):
```
① 数美安全 SDK 初始化(App 启动)
cfgc.zztfly.com/v6/gcf · cdn-api-auth.zztfly.com/api/initSecCdn · upc.flysleep.cn/v6/gcl
② 设备指纹配置
wsapi.huya.com huyaudbwebui.getDfpConfig ← 只传 appId=5008,返回 80 字节加密配置
③ 选运营商(一键登录前置)
wsapi.huya.com huyaudbwebui.selectOperator ← 传设备信息 + RSA 加密段,返回运营商索引
④ 设备指纹上报 ×4(循环)
wsapi.huya.com huyaudbwebui.dfpReport ← 上报加密设备指纹,返回 32 位指纹 + action
udbdf.huya.com/dckey/check ← 加密 body,返回 dckey
⑤ 运营商取号(联通)
auth.wosms.cn/dro/netm/v1.0/qc
nisportal.10010.com:9001/api?appid=2074469246116
⑥ 登录提交(❌ 未抓取)
→ 进入 cdn.wup.huya.com / 裸 IP 长连接(被 SSL Pinning 挡住)
```
`wup.huya.com` 上看到的 `mobileui.getDynamicConfigHotFixUpdateInfo` / `reportMobileUpdateResult` 只是 **App 热更配置**,与登录无关。`statwup.huya.com``metric.reportDetailV2` 是埋点。
---
## 三、各接口字段级结构
### 1. getDfpConfigwsapiservant=huyaudbwebui
- **请求 tReq**`{"t0": {"t0": "5008"}}` —— 只传 appId。
- **响应 tRsp**`{"t0": {"t0": {"5008": "<base64>"}}}` —— 按 appId 返回配置。
- **base64 配置解码后 = 80 字节**(64+16 结构,疑似密钥材料):
```
hex: 34b946b716f1481d8584980b79cf756b962e79c999d752016904c1fc07d689
694a148ef872206826745498088e61be3f062f7d8d1a0276e29c0048062ac8e2
b58fe0d1ee376c412472d8b9124d0bbf7d
```
### 2. selectOperatorwsapiservant=huyaudbwebui
请求 key 是 `_wup_data`(注意不是 tReq),三层嵌套结构:
```
_wup_data.t0.t0 = { # 会话上下文
t0: 0,
t1: "1.0", # 协议版本
t2: "hyudb_1787583730402", # 会话 id = "hyudb_" + 毫秒时间戳
t3: "5008", # appId
t4: 0,
t5: "PQwemAN9NHkZKoMqV...", # RSA 公钥加密段(与 dfpReport 响应 t2 同前缀)
t6:"", t7:"", t8:"", t9:""
}
_wup_data.t1 = { # SDK 信息
t0:"", t1:"13.4.22"(App版本), t2:"2.0.8"(SDK版本), t3:"",t4:"",t5:"",t6:""
}
_wup_data.t2 = { # 设备信息
t0: 1,
t1: "M2102J2SC", # 机型
t2: "02df398797432eadefcc12767119ad5e80999389", # 40 hex = SHA1 设备指纹
t3: "android",
t4: "M2102J2SC,30,11", # 机型,Android版本,SDK版本
t6:"", t7:""
}
_wup_data.t3 = [0, 1, 3] # 运营商列表
```
- **响应**`_wup_data.t0.t1 = 1`(选中运营商索引 1)。
### 3. dfpReportwsapiservant=huyaudbwebui
- **请求 tReq**
```
{t0: {t0:"", t1:"android", t2:"<加密体>", t3:"", t4:12}}
```
- `t2` 加密体结构:**10 字节固定魔数 + 变长加密数据**
- 魔数(跨 6 次请求完全一致):`571882cf664bb39401ee`
- 加密数据长度 3300~4100 字节(每次递增,说明是**累积式采集上报**,每次追加新采集数据)
- **响应 tRsp**
```
{t0: {
t0: "10",
t1: "865a4924a40897ac1fcfe6b4c2cbb045", # 32 hex = 稳定设备指纹(4 次响应一致)
t2: "PQwemAN9NHkZKoMqV...", # base64 = RSA 公钥加密的 action(每次变)
t3: 43200, # 过期时间(秒)=12h
t4: "action",
t5: "7c5387e0539c023c31c4ff0e807e7256117385ee" # 16 hex = 稳定 session hash4 次一致)
}}
```
### 4. dckey/checkudbdf.huya.comokhttp/3.14.9application/octet-stream
- **请求 body 结构**:**100 字节固定头 + 变长加密体**(总长 543B 或 547B,两种长度差 4 字节)
- 100 字节固定头(跨 5 个样本、两个会话完全一致):
```
32f1990eb8f46c503c3b5db81be58ae84dd3e961b991dd9e9c593aba8c78881c
f1281c555198710a1009f1ec897c46be8b26d81e8eab98f27242de3444315cab8
d617eae189d597e296099f0e5bc817a7554456c1af99515254ab7665a2a3f861a
7d941b
```
- 变长加密体(543/547B 减 100B = 443/447B)每次变化。
- 同一长度下,末尾 32 字节可复现(同设备+同 session 重复调用时)。
- **响应**`{"dckey":"AAAAAMC1eP4iV43WYoI57ZOu0...","status":"success"}`
- dckey 固定前缀 `AAAAAMC1eP4iV43WYoI57ZOu0`(base64 前 16 字节),后续每次变。
### 5. 运营商取号(联通一键登录)
- `POST auth.wosms.cn/dro/netm/v1.0/qc`
- `POST nisportal.10010.com:9001/api?appid=2074469246116&ret_url=<base64>`
---
## 四、加密体分析汇总
| 加密体 | 位置 | 结构 | 猜测算法 |
|---|---|---|---|
| dfpReport t2 | wsapi | 10B 魔数 `571882cf664bb39401ee` + 变长加密 | 自定义格式,魔数疑似协议版本/类型标识 |
| dckey/check body | udbdf | 100B 固定头 + 443/447B 变长 | 固定头疑似 RSA/ECDH 公钥或设备态加密快照 |
| getDfpConfig 配置 | wsapi 响应 | 80B = 64B+16B | 疑似 dfpReport 加密密钥 + IV/nonce |
| selectOperator t5 / dfpReport t2(action) | wsapi | base64,同前缀 `PQwemAN9NHkZKoMqV` | **RSA 公钥加密**(同公钥 → 密文前缀相同) |
| statwup #7 | statwup | 400B 纯二进制 | 应用层加密(session-key |
**关键判断**`selectOperator.t5``dfpReport 响应 t2` 前缀相同 → **同一个 RSA 公钥**加密,明文大概率是「设备信息 + 会话 key」的协商包。拿到这个公钥(APK 内嵌)就能解密。
---
## 五、稳定字段清单(可直接复用的常量)
| 字段 | 值 | 用途 |
|---|---|---|
| appIdApp 场景) | `5008` | 替代 web 的 5002 |
| App 版本 | `13.4.22` | versionCode=115315 |
| SDK 版本 | `2.0.8` | selectOperator.t1.t2 |
| dfpReport 魔数 | `571882cf664bb39401ee` | t2 加密体前缀 |
| dckey 固定头 | 100B(见上) | dckey/check body 前缀 |
| dckey 响应前缀 | `AAAAAMC1eP4iV43WYoI57ZOu0` | dckey 前 16B |
| 设备指纹(selectOperator) | `02df398797432eadefcc12767119ad5e80999389` | 40 hex = SHA1 |
| 设备指纹(dfpReport) | `865a4924a40897ac1fcfe6b4c2cbb045` | 32 hex,服务端下发 |
| session hash(dfpReport) | `7c5387e0539c023c31c4ff0e807e7256117385ee` | t5 |
| 会话 id 格式 | `hyudb_<毫秒时间戳>` | selectOperator.t2 |
| appSign | `60576904`md5 取前 8,见 login.py 注释) | App 场景签名 |
---
## 六、与现有 Web 登录(appId=5002)的差异
| 维度 | Web(5002) | App(5008) |
|---|---|---|
| 登录入口 | udblgn.huya.com/web/v2/passwordLogin | cdn.wup.huya.com 长连接 |
| 设备指纹 | df.huya.com/web/df/token+collect | wsapi getDfpConfig+dfpReport + dckey/check |
| 验证码 | udbrtt pt/get3+verify3(滑块/点选) | 运营商取号 + (未解明) |
| 传输 | JSON over HTTP | TAF/WUP + 部分应用层加密 |
---
## 七、待破解项(按优先级)
1. **[最硬] dckey/check 的加密算法**100B 固定头 + 变长体。需 hook APK 里 dckey 生成方法。
2. **[核心] dfpReport t2 加密体**:魔数 + 累积式采集数据。需 hook dfp 采集模块。
3. **[核心] 登录提交的 WUP servant/func + 参数**:被 SSL Pinning 挡住,需 Frida unpin 或 hook 登录方法。
4. **[可解] RSA 公钥**APK 内嵌,解出 selectOperator.t5 / dfpReport action 明文。
5. **[可解] getDfpConfig 80B 配置**:确定是否 dfpReport 加密密钥。
---
## 八、Frida hook 点建议(供 hook 阶段参考)
1. **dckey 生成**hook `udbdf` 请求发出前,dump 加密前明文。搜 `com/duowan/kiwi` 下 dckey / dcKey 相关类。
2. **dfp 采集**hook dfpReport 的 t2 字节流写入点(`writeTo`/`toByteArray`)。
3. **登录提交**hook `com.duowan.kiwi.base.login.UdbNetHelper`(appSign 已逆向自此类),以及登录 servant 的 `writeStruct("tReq", ...)` 前的参数。
4. **SSL unpin**hook OkHttp `CertificatePinner.check` + conscrypt `TrustManagerImpl.verifyChain`,让 Reqable 能解 cdn.wup.huya.com。
---
## 九、工具使用
```bash
# 解码某个 HAR 里的所有 WUP(TAF) 请求/响应字段
.venv/bin/python tools/hy_har_parse.py "/Users/yml/Desktop/抓包/hy/8.23/登录-5.har"
# 底层 TAF 解码器(可单独 import)
from tools.taf_decode import Taf, decode_struct_bytes
```