# 虎牙 App 密码登录 — WUP 明文抓包实证(最终) > 状态:**密码登录完整 WUP 请求已抓到(TLS 加密前明文)** > 证据:`/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/derived/huya-wup-full3.jsonl` > 方法:hook `createWupRequestData` onLeave 拿到序列化后的 WUP 字节 --- ## 一、密码登录 WUP 请求(核心结论) | 项 | 值 | |---|---| | **servant** | `huyaudbwebui` | | **func** | `hypasswordLogin` | | **协议** | WUP / TAF(JCE) 二进制序列化 | | **msgType** | 4097 (0x1001) = `MsgLogin.mMsgId` | | **请求大小** | 1356 字节 | | **密码** | SHA1(明文) hex,无盐(`772ed992b0e161276f44ec63671e60155c506294`) | | **name** | 虎牙号为 `hy_<虎牙号>`;手机号登录时为 11 位手机号原值 | --- ## 二、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 (虎牙号示例;手机号登录时直接放手机号) 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 登录被风控阻断时的替代路径