虎牙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
+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
```