- 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辅助函数 - 新增证据金标准 + 协议文档
13 KiB
虎牙 App 密码登录 — WUP 明文抓包实证(最终)
状态:密码登录完整 WUP 请求已抓到(TLS 加密前明文) 证据:
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/derived/huya-wup-full3.jsonl方法:hookcreateWupRequestData<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)+STRING10x66= 字段6(sFuncName)+STRING10x7d= 字段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(成功标记) |
这证明:
- ✅ 请求格式 100% 正确(服务端无 BAD_REQUEST)
- ✅ 设备指纹(safedeviceid/hdid)可复用(重放旧值仍通过)
- ✅ userAction 可复用(旧轨迹未被风控拒绝)
- ✅ TLS 指纹不阻断
注:此验证是「重放」,即复用已抓到的完整请求。要批量登录新账号,还需编码器动态构造新请求(替换 name/password/session/traceId/userAction)。
四·六、编码器 + 动态构造验证(✅ 完整成功)
编码器已实现,逐字节复现金标准,且能动态构造全新请求。
tools/huya_wup_encoder.py— 自检通过:用已知字段构造,输出与金标准逐字节一致(1015 字节)- 动态构造测试 — 用新 session(3251799)、新 traceId 构造全新请求(1009 字节),发送后返回:
| 字段 | 值 |
|---|---|
| uid | 1199666914671 |
| name | hy_300023887 |
| 手机号掩码 | 001******7524 |
| token | 全新 token(XAQBRJ3H...) |
这证明 纯 Python 可以完整复刻虎牙密码登录:
- ✅ 帧格式(JCE/WUP)逆向定稿
- ✅ 编码器逐字节复现
- ✅ session/traceId 可自由生成(服务端接受新值)
- ✅ 设备指纹可复用
- ✅ 动态构造请求返回有效登录响应(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 发密码登录请求,需要:
- ✅ 密码:
sha1(明文)—— 已破解,纯 Python 可做 - ✅ name:
hy_虎牙号—— 已知 - ✅ servant/func:
huyaudbwebui/hypasswordLogin—— 已知 - ✅ WUP 帧格式:标准 JCE RequestPacket,已 100% 定稿(见第二节)
- ✅ Content-Type:
application/x-wup,body = 原始 TAF 二进制 - ✅ 重放验证:服务端返回 uid + token(纯 Python 可行)
- ✅ TAF/JCE 编码器:
tools/huya_wup_encoder.py(自检通过,逐字节复现) - ✅ _wup_data 元数据:
session/traceId可自由生成(服务端接受新值) - ✅ 设备指纹(safedeviceid/hdid):可复用旧值(缓存一次即可)
- ✅ 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,不提交。
七、结论与下一步
已完成并验证:
- ✅ TLS 指纹:实测不阻断(Python urllib 正常收到业务响应)
- ✅ TAF 编码器:
tools/huya_wup_encoder.py,逐字节复现金标准 - ✅ 纯 Python 登录:重放 + 动态构造均返回 uid + token
- ✅ userAction 随机化:随机坐标/时间戳通过
待实测(唯一未定论项):
- 批量高频登录的限流/风控:需用编码器连续发请求,观察账号/设备/IP 维度是否触发限制(错误码、频率阈值)
- 集成进
core/huya/login.py:把 APP 密码登录(/open/hy/passwordLogin)作为 Web 登录被风控阻断时的替代路径