- 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辅助函数 - 新增证据金标准 + 协议文档
213 lines
9.7 KiB
Markdown
213 lines
9.7 KiB
Markdown
# 虎牙 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 Pinning,Reqable 中间人证书被拒,只留 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. getDfpConfig(wsapi,servant=huyaudbwebui)
|
||
|
||
- **请求 tReq**:`{"t0": {"t0": "5008"}}` —— 只传 appId。
|
||
- **响应 tRsp**:`{"t0": {"t0": {"5008": "<base64>"}}}` —— 按 appId 返回配置。
|
||
- **base64 配置解码后 = 80 字节**(64+16 结构,疑似密钥材料):
|
||
|
||
```
|
||
hex: 34b946b716f1481d8584980b79cf756b962e79c999d752016904c1fc07d689
|
||
694a148ef872206826745498088e61be3f062f7d8d1a0276e29c0048062ac8e2
|
||
b58fe0d1ee376c412472d8b9124d0bbf7d
|
||
```
|
||
|
||
### 2. selectOperator(wsapi,servant=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. dfpReport(wsapi,servant=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 hash(4 次一致)
|
||
}}
|
||
```
|
||
|
||
### 4. dckey/check(udbdf.huya.com,okhttp/3.14.9,application/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 内嵌)就能解密。
|
||
|
||
---
|
||
|
||
## 五、稳定字段清单(可直接复用的常量)
|
||
|
||
| 字段 | 值 | 用途 |
|
||
|---|---|---|
|
||
| appId(App 场景) | `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
|
||
```
|