虎牙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:
@@ -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 中层:sBuffer(961 字节)是一个 MAP
|
||||
|
||||
```
|
||||
MAP (2条目):
|
||||
"_wup_data" → 915 字节 (内层登录请求 struct)
|
||||
"wupudbrequest_v0" → 5 字节 = 02 00 31 9d f3 (版本2 + session INT32)
|
||||
```
|
||||
|
||||
### 2.3 内层:_wup_data(915 字节)登录请求 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 = 定制 BoringSSL(hyssl),符号已 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. **动态构造测试** — 用新 session(3251799)、新 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 字节 base64(hook 返回值)
|
||||
- `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 登录被风控阻断时的替代路径
|
||||
Reference in New Issue
Block a user