feat(huya): 零设备 dfpReport 注册链迁入 core/huya 并接入生产登录

- 新增 core/huya/dfp_register: 随机 4146B cw 生成 + 注册响应解析 (t1/t2/t5),
  修复 JSON 模板 %s 数不匹配的 TypeError bug
- app_login 每次 WUP 登录前执行注册链取新 safedeviceid/device_id,
  不再读取画像旧固定值; 注册失败抛 HuyaAppLoginError 终止, 不静默回退旧链
- device_profile 移除固定 SAFEDEVICEID_DEFAULT, 画像只承载 soft 字段
- tools/huya_device_register 同步零设备生成链路 (--gen/--gen-login)
- 新增 tests/test_huya_dfp_register (9 项), 扩展 test_huya_app_login 注册接线 (4 项)
This commit is contained in:
yml2213
2026-08-27 17:47:53 +08:00
parent 36325cf639
commit 0c86e182b0
6 changed files with 520 additions and 37 deletions
+82 -14
View File
@@ -2,27 +2,28 @@
链路 (servant=huyaudbwebui, 均为 TAF/WUP 信封 POST):
getDfpConfig -> selectOperator -> dfpReport
-> 响应下发 t1(32hex 设备指纹) / t2(base64 action=safedeviceid) / t5(40hex session hash)
-> 响应下发 t1(32hex appkey回声) / t2(base64 action=safedeviceid) / t5(40hex device_id)
实测结论 (2026-08-26):
- 三接口均可纯 Python 直连重放 (HTTP 200)。
实测结论 (2026-08-26/27):
- 三接口均可纯 Python 直连 (HTTP 200)。
- dfpReport 响应 t5 == WUP 登录帧的 device_id; 响应 t2 == 登录帧的 safedeviceid ——
注册链响应就是登录帧设备字段的签发源。用注册链新签发的 t2/t5 登录 -> cred ✅。
- t1(32hex) 不是登录帧 hdid (实测作 hdid 登录 -> APP_SIGN_NOT_MATCH)
- 设备身份绑定在 dfpReport 请求的"加密体"(10B 魔数 + 加密采集数据)上:
selectOperator 里改 fingerprint / 机型号不影响 t1/t5 -> 不重新注册设备。
加密体生成算法未破解 (逆向笔记 待破解#2), 因此目前只能"重放真机加密体",
得到的设备身份永远等于抓包那台真机 —— 无法纯代码铸造全新设备。
- hdid 由 libhydeviceid.so 生成本地设备ID, 经 native setDeviceInfo 报告,
不走 HTTP (抓包不可见), 纯代码无法铸造新 hdid -> 多账号必须复用金样本 hdid。
- t1(32hex) 不是设备锚: 恒=appkey 变体回声; selectOperator 全改(sd+fp+机型)不影响它
- ★(2026-08-27 突破) 服务端不校验 dfpReport 加密体(cw)内容: 随机 4146B cw 照样
200 + 签发新 t2/t5。t5(device_id) = 服务端从 cw 解出的 40hex hdid, 随机 cw 时
为随机值但服务端宽容。-> 无需真机加密体, python 零设备注册链达成 (--gen)。
- 唯一不可铸造: WUP 登录帧 32hex hdid (libhydeviceid.so 本地生成 + native
setDeviceInfo 上报, 不走 HTTP) -> 多账号必须复用金样本 hdid (APP_SIGN_NOT_MATCH 硬错)。
用途:
登录前重放注册链拿"新鲜 action"作为 safedeviceid (比固定金样本更像真机),
登录前注册链拿"新鲜 action" + [重放|随机] dfpReport 作为 safedeviceid,
配合每账号随机 fingerprint/device_id/机型 (soft 字段) 使用。
用法:
python tools/huya_device_register.py # 走完整注册链并打印 t1/t2/t5
python tools/huya_device_register.py --login # 注册链 -> 用新action登录出cred
python tools/huya_device_register.py # 重放注册链并打印 t1/t2/t5
python tools/huya_device_register.py --gen # ★生成器随机cw注册链 (零设备)
python tools/huya_device_register.py --gen-login <acct> <pwd> # ★零设备注册链->登录
python tools/huya_device_register.py --login # 重放注册链 -> 用新action登录出cred
"""
from __future__ import annotations
@@ -102,6 +103,11 @@ def dfp_report(chain) -> tuple[str, str, str]:
r = _post(WSAPI, reqb)
r.raise_for_status()
b = r.content
return parse_dfp_response(b)
def parse_dfp_response(b: bytes) -> tuple[str, str, str]:
"""解析 dfpReport 响应 -> (t1, t2, t5)."""
t1 = re.search(rb"\x16\x20([0-9a-f]{32})", b).group(1).decode()
m2 = re.search(rb"\x26\xb4([A-Za-z0-9+/=]{180})", b)
t2 = m2.group(1).decode("latin1")
@@ -109,6 +115,58 @@ def dfp_report(chain) -> tuple[str, str, str]:
return t1, t2, t5
def gen_dfp_report(chain) -> tuple[str, str, str]:
"""dfpReport 用生成器随机 cw (零设备) -> (t1, t2, t5).
★中新突破 (2026-08-27): 服务端不校验 cw 内容, 随机 4146B cw 照样
签发新 action(t2)/device_id(t5), 与重放真机加密体等价, 但无需真机!
"""
sys.path.insert(0, str(HERE))
from dfp_gen import build_cw, build_body, make_triple_json
_hdid, _did, _appkey, jp = make_triple_json()
body = build_body(build_cw(jp))
r = _post(WSAPI, body)
r.raise_for_status()
return parse_dfp_response(r.content)
def make_select_operator(chain, fingerprint_40hex: str | None = None,
safedeviceid: str | None = None) -> bytes:
"""构造 selectOperator: 可替换 fingerprint 与 180B safedeviceid(action)."""
reqb, _ = chain["selectOperator"]
# 替换 180B safedeviceid (字段头 \x56\xb4 后接 180B base64)
if safedeviceid and len(safedeviceid) == 180:
idx = reqb.find(b"\x56\xb4")
if idx >= 0:
reqb = reqb[: idx + 2] + safedeviceid.encode() + reqb[idx + 2 + 180:]
if fingerprint_40hex and len(fingerprint_40hex) == 40:
idx = reqb.find(b"02df398797432eadefcc12767119ad5e80999389")
if idx >= 0:
reqb = reqb[:idx] + fingerprint_40hex.encode() + reqb[idx + 40:]
return reqb
def gen_fresh_identity() -> tuple[str, str, str]:
"""生成器全链路注册 (零设备) -> (t2 action, t5 device_id, t1 appkey回声)."""
chain = _load_chain()
get_dfp_config(chain)
# 用全新 fingerprint + 新 sd (先用金样本走一遍拿新 action, 再自定义 selectOperator)
so = make_select_operator(chain, fingerprint_40hex=None, safedeviceid=None)
_post(WSAPI, so, "application/x-wup")
t1, t2, t5 = gen_dfp_report(chain)
return t1, t2, t5
def gen_login(account: str, password: str,
fingerprint: str | None = None) -> str:
"""零设备注册链 -> 登录 (金样本 hdid), 返回 cred hex / NEED_RISK / REJECTED."""
t1, t2, t5 = gen_fresh_identity()
print(f"注册链: t1(appkey回声)={t1}")
print(f" t2(action)={t2[:16]}... len={len(t2)}")
print(f" t5(device_id)={t5}")
return login_with_registered(t2, t5, account, password, fingerprint=fingerprint)
def fresh_action() -> str:
"""跑一遍注册链返回新签发的 action (登录帧 safedeviceid)."""
chain = _load_chain()
@@ -156,7 +214,17 @@ def login_with_registered(action: str, t5_device_id: str,
if __name__ == "__main__":
if "--login" in sys.argv:
if "--gen-login" in sys.argv:
acct = sys.argv[sys.argv.index("--gen-login") + 1]
pwd = sys.argv[sys.argv.index("--gen-login") + 2]
out = gen_login(acct, pwd)
print("登录结果:", ("cred " + out[:16] + "...") if out.startswith(("0a", "0b")) else out)
elif "--gen" in sys.argv:
t1, t2, t5 = gen_fresh_identity()
print(f"生成器注册链: t1={t1}")
print(f" t2(action)={t2[:16]}... len={len(t2)}")
print(f" t5(device_id)={t5}")
elif "--login" in sys.argv:
acct = sys.argv[sys.argv.index("--login") + 1]
pwd = sys.argv[sys.argv.index("--login") + 2]
act = fresh_action()