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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user