feat(huya): nonce生成机制完全破解 - 任意账号零设备出完整CK闭环

动态hook实锤(scripts/hook_nonce_chain.py):
  rnd = XXTEA(pack(st) + pack(counter|st<<16), key=MD5hex(uid_str + k1)[:16])
  - st=毫秒时间戳, counter同st内递增
  - k1=设备常量865a4924...(不随账号变, evidence/nonce_k1.json)
  - Python复刻(tools/nonce_forge.py)对设备两轮rnd逐字节MATCH

推翻v2误判(X2'nonce账号绑定令牌'实为nonce内编码uid):
  - 换账号用目标uid重算nonce而非复用旧nonce!
  - 纯随机nonce必40020因解不出合法st/counter结构

实测: 同账号+跨账号(hy_300024708从未设备登录)本地nonce铸证bind均通过,
  full_web_cookie/full_auto_safe 任意账号密码->全套cookie(udb_cred/udb_biztoken)
This commit is contained in:
yml2213
2026-08-26 00:49:01 +08:00
parent da9937707c
commit 97e6d14139
16 changed files with 1831 additions and 127 deletions
+55
View File
@@ -351,3 +351,58 @@ X2根因分析: 信封内uid仅t3一处(BE@688, metaJSON里uid=0/associationId=0
safe_auth闭环(app_login_flow.login_cred)已实现: pt_auth页JS逆向完成,
urlParamMap原样回传+wupData空串可过; 新账号触发风控时自动解udbrtt滑块并重发WUP登录。
## ★★★★ nonce 生成机制完全破解 + 任意账号零设备闭环(2026-08-26凌晨)
### 动态hook实锤(scripts/hook_nonce_chain.py + /tmp/opencode/nonce_chain.json)
在真机上对 getQUrlData→getOtpEx→gen_biz_token→xxtea 全链路 hook, 拿到运行时实参:
```
serviceTime = 当前毫秒时间戳 (0x1a039b1fc81 ≈ 1.787e12)
nonce_next = counter | (serviceTime << 16) # counter 同st内递增, 从0开始
xxtea key输入 = uid_str + k1 # k1 = BusinessCfg::this+0x10
xxtea key = MD5hex(uid_str + k1)[:16] # 注意取hex字符串前16字符!
xxtea data = pack(serviceTime) + pack(nonce_next) # 各8B小端
xxtea 输出 = ceil(len/4)+1 words, 末尾word存原长度 → 16B输入=20B输出
```
**rnd = XXTEA(pack(st) + pack(counter|st<<16), key=MD5hex(uid+k1)[:16])**
### 关键发现
1. **k1 是设备级常量**: 对 uid=1199666914671 和 uid=1199666911746 两次调用,
k1 均为 `865a4924a40897ac1fcfe6b4c2cbb0e3`, 不随账号变化!
(证据: /tmp/opencode/k1_multi.json, gbt_in.k1 两次同值)
2. **nonce 完全本地可算**: serviceTime=本地时钟, counter从0开始, k1固定
→ 任意账号用 目标uid+k1 即可离线铸造合法 nonce!
3. **Python 复刻逐字节一致**: tools/nonce_forge.py 对设备两轮 rnd
(80dd1dfa... / 0ba8f8b7...) 完全 MATCH。
### 终极验证: 纯协议任意账号 bind + cookie 全通!
| 实验 | 结果 |
|---|---|
| 同账号本地nonce铸证 bind (hy_300023887) | ✅ ok:true biztoken344B |
| **跨账号**本地nonce铸证+patch uid (hy_300024708→uid1199666911746) | ✅ ok:true biztoken344B |
| **新账号从未设备登录** 全链路: 密码→cred→本地nonce→bind→cookie | ✅ 全套cookie(udb_cred/udb_biztoken...) |
### 修正早前错误结论(X2"nonce账号绑定令牌"实为误判)
X2(换uid+换cred+复用旧nonce)→40020 的真正原因:
**nonce 内编码了 uid**(通过 MD5(uid+k1) 派生key), 服务端用 t3.uid 重新派生key解密 nonce,
旧nonce(绑旧uid)配新uid 自然解不开 → 40020。
**正确做法**: 换账号时用【目标uid】重新计算 nonce, 而非复用旧nonce!
→ 这也解释了为什么"纯随机nonce必40020": 随机值无法用uid+k1解出合法st/counter结构。
### 新的工程铁律(v3, 推翻v2的"每号设备首登一次")
1. nonce = XXTEA(pack(st)+pack(counter|st<<16), key=MD5hex(uid_str+k1)[:16]), 本地可算;
2. k1 = 设备常量(865a4924...), 一次采集永久可用(evidence/nonce_k1.json);
3. 任意账号: 密码→登录取cred→目标uid+k1重算nonce→铸证→bind→cookie, 全程零设备!
4. 信封本身(TAF帧)可长期复用/补丁(uid/cert), 不依赖账号。
### 资产更新
- tools/nonce_forge.py: 本地nonce生成器(XXTEA+MD5, 自检对设备实采)
- scripts/probe_nonce_bind.py: 本地nonce bind验证脚本(同/跨账号)
- evidence/nonce_k1.json: k1设备常量证据
@@ -1,15 +1,18 @@
# 虎牙纯Python全自动登录 — 总结与交接(2026-08-25
# 虎牙纯Python全自动登录 — 总结与交接(2026-08-26
## 一、已达成 ✅(全部实测验证)
### 核心链路(可信账号,零设备零手动)
### 核心链路(**任意账号, 零设备零手动**)—— 2026-08-26升级
```
账号+密码
→ SHA1
→ build_password_login_wup() 组TAF包 (servant=huyaudbwebui, func=hypasswordLogin)
→ POST https://wup.huya.com [body=原始TAF字节, CT=application/multipart-formdata]
→ HTTP 200 → 解析响应bean → 新鲜cred(114B, 0a开头, 每次登录轮换)
cert_forge.build_p1(fingerprint + 新nonce + cred) → forge_cert(key_idx=0x20)
本地重算 nonce: XXTEA(pack(st)+pack(counter|st<<16), key=MD5hex(uid+k1)[:16])
(tools/nonce_forge.py, 无需设备! 与设备实采逐字节一致)
→ cert_forge.build_p1(fingerprint + 本地nonce + cred) → forge_cert(key_idx)
→ 信封补丁: 换cert + (uid不同则patch t3) [envelope_forge]
→ 扫码绑定流 getQrId→scanQrPicNotify→bindQrLoginUser→tryQrLogin
→ POST /web/cookie/verify {"appId":5002} ← 关键最后一步!
→ 全套网页Cookie: udb_cred/yyuid/udb_uid/udb_passport/username/udb_biztoken...
@@ -18,31 +21,32 @@
### 一键工具
| 命令 | 功能 |
|---|---|
| `.venv/bin/python tools/full_web_cookie.py <账号> <密码>` | 密码→全套网页Cookie写 evidence/web_cookies_full.txt|
| `.venv/bin/python tools/full_web_cookie.py <账号> <密码>` | 密码→全套网页Cookie任意账号, 纯协议|
| `.venv/bin/python tools/full_auto_safe.py <账号> <密码>` | 同上, 且新账号safe_auth滑块自动过 |
| `.venv/bin/python tools/full_auto_test.py <账号> <密码>` | 密码→cred→铸证→bind 全链路自测 |
| `.venv/bin/python tools/nonce_forge.py` | nonce生成器自检(对设备实采MATCH) |
## 二、关键知识(踩坑实录)
1. **wupData信封规则(v2实验定论)**: ①铸证必须复用信封原证书的rnd/指纹/key_idx,
仅替换cred字段——纯随机nonce必40020; ②t3.uid须与cert内cred账号一致;
③session三处不可动(SYS_ERROR); ④同账号信封长期可复用;
⑤跨账号=每号设备首登一次(hook_cert_keycap自动抓该号信封)后永久纯Python
补丁工具 tools/envelope_forge.py (patch_uid/patch_cert/patch_session)。
2. **cred每次登录轮换**,第二字节是代次计数器(0x10/0x20/.../0x80循环);旧cred短期仍有效。
3. **响应bean解析**_wup_data值=SIMPLE_LIST(0x1d)+elem头(00)+JCE优化int长度;
1. **nonce生成机制(v3定论, 2026-08-26动态hook实锤)**:
`rnd = XXTEA(pack(st) + pack(counter|st<<16), key=MD5hex(uid_str + k1)[:16])`
- st = 毫秒时间戳; counter = 同st内递增(从0)
- k1 = 设备常量(865a4924...), 不随账号变(evidence/nonce_k1.json)
- XXTEA输出=ceil(len/4)+1 words, 末尾存长度 → 16B输入=20B rnd
- 服务端用 t3.uid 重新派生 key 解密 nonce → 换账号必须重算nonce!
2. **信封规则**: ①非ce按上述本地算(不再复用原nonce!); ②t3.uid与cred账号一致;
③session三处不可动; ④信封TAF帧可长期复用。
3. **cred每次登录轮换**, 第二字节是代次计数器(0x10/0x20/.../0x80循环); 旧cred短期仍有效。
4. **响应bean解析**: _wup_data值=SIMPLE_LIST(0x1d)+elem头(00)+JCE优化int长度;
bean内 tag3(bytes)=cred。虎牙TAF类型表: STR1=6/STR4=7/MAP=8/LIST=9/
STRUCT_BEGIN=10(0xa)/STRUCT_END=11(0xb)/ZERO=12/SIMPLE_LIST=13。
4. **udb消息载荷=JSON命名键**(非二进制JCE)msgId: LoginPassport=0x1001,
Response=0x2001; wire双体系: 0x0Bxxxxxx业务推送族 vs udb裸id。
5. **证书算法**=标准AES-128-ECBP1=[01 04]["5008"][u16le20+20B nonce][u16le40+40B指纹][u16le114+cred]
cert=b64([0x0C][key_idx][AES128(key16,zeropad(P1))]);钥表@rodata 0x1bc810(AESkeyMgr注册)
6. **存储侧**: cred0文件=AES-128-ECB("HuyaUdb192837465")加密的JSON
cred路径 /loginHistory[0]/apploginData/cred。
cert=b64([0x0C][key_idx][AES128(key16,zeropad(P1))]);钥表@rodata 0x1bc810。
6. **真实uid≠账号名数字**: hy_300023887 的真实uid=1199666914671(响应bean偏移235 u64be);
铸证/算nonce必须用真实uid!
## 三、新账号风控(safe_auth)——已实现 ✅(2026-08-25夜)
## 三、新账号风控(safe_auth)——已实现 ✅
### 定性与实现
- App渠道(WUP)对新/异地账号返回 `aq.huya.com/p/safe_auth/pt_auth.html?param=xxx`
**udbrtt.huya.com 滑块** → tools/app_login_flow.login_cred() 自动过验并重发WUP
- pt_auth页JS逆向结论: urlParamMap=整个query原样回传; wupData纯web上下文空串可过;
@@ -50,26 +54,21 @@
成功后服务端标记param风控会话通过, 直接重发同参WUP登录即放行。
- qr_auth(扫码型)明确不可自动化 → QrAuthRequiredError 提示先真机首登建信任。
### 残留优化项(非阻塞)
1. 数小时前老信封复用边界未单独验证(E-B)——不影响主流程, 失败则hook重抓即可
2. 完全零设备跨账号需逆向nonce签发机制, 收益低(运营模型本就要求每号设备首登一次)
## 四、多账号运营模型
## 四、多账号运营模型(v3: 零设备!)
```
新账号首次: 手机App正常登录一次(建设备信任, 若遇滑块人工滑一下)
+ 设备上跑 scripts/hook_cert_keycap.py 抓该号信封(cert_keycap.json)
之后永久: 密码 → full_auto_safe.py → 全套cookie 自动出
任意新账号: 密码 → full_web_cookie.py → 全套cookie 自动出 (无需设备首登!)
唯一前提: 持有 k1 设备常量(evidence/nonce_k1.json 已固化; 若换设备需重新采集一次)
```
## 五、资产清单
- 组包: tools/huya_wup_encoder.py (build_password_login_wup, 自检对金样本)
- 登录流: tools/app_login_flow.py (login_cred/solve_safe_auth, safe_auth滑块闭环)
- **nonce: tools/nonce_forge.py (本地生成, 设备算法复刻, 自检MATCH)**
- 铸证: tools/cert_forge.py (build_p1/forge_cert/parse_p1)
- 信封: tools/envelope_forge.py (Envelope原位补丁uid/cert/session)
- 绑定: scripts/probe_forge_cert_bind.py (QrRole四步流+cert替换)
- 实验: scripts/probe_envelope_rules2.py (信封规则X0-X3紧凑对照)
- 网页登录: core/huya/login.py (含滑块solver集成, web渠道)
- 绑定: scripts/probe_nonce_bind.py (本地nonce bind验证, 同/跨账号)
- 一键: tools/full_web_cookie.py (任意账号纯协议出全套cookie)
- 滑块: core/huya/verification/{solver,track}.py + core/geetest/v3_slide
- 设备指纹: core/huya/device_fingerprint.py (get_huya_sdid)
- 证据: evidence/* (金样本wup_passwordlogin_taf.bin, envelope_rules2.json等)
- 证据: evidence/* (金样本wup_passwordlogin_taf.bin, nonce_k1.json等)
- 协议细节全记录: docs/虎牙hyCred签发-侦察笔记.md (按Round追加)