diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index cab33fc..3d95de7 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -381,3 +381,30 @@ mid = LiveUserbase→tUAEx→t5=sMId (16hex),**可任意铸造** → doLaunch - build 只发 tReq 单键 (对齐真机帧); - `parse_launch_rsp` 支持 gzip + `\x06\x20(32hex)` sGuid 可靠提取; - `--live` 一条命令出 sGuid; 确定性矩阵可复现。 + +## §11.9 铸币机接入登录链验收 (2026-08-28 夜, 3 个测试账号) + +### device_mint.py 落地 +`tools/device_mint.py` (编排器): +- `--mint-only [mid]` : mid → doLaunch → sGuid ×3 确定性 +- `--login [--mid X]` : 铸币 → 零设备注册链(gen_fresh_identity) → WUP 登录(铸币 hdid) +- `--control ` : 金样本 hdid 登录 (对照基线) +- `tools/huya_launch_mint.mint_sguid()` : 单函数铸币上线 (gzip + sGuid 提取) + +### 验收矩阵 (账号 hy_300030430/aa778899) +| 路径 | 结果 | 解读 | +|---|---|---| +| 零设备注册链 (getDfpConfig→selectOperator→dfpReport) | ✅ HTTP200, 新鲜 action(180B) + device_id(40hex) | 注册链仍在线 | +| 铸币 doLaunch (mid=aabbccdd00112233 ×2) | ✅ `0a7d910eb99a916a3801d7b0d8b02bce` 两次同值 | sGuid=f(mid) 确定性 | +| 铸币 hdid 登录 (hdid=上述 sGuid) | ❌ 652B `APP_SIGN_NOT_MATCH` "APP签名不匹配" | 签名校验拦截 | +| 对照: 金样本 hdid 登录 (ed0db8…) | ⚠️ NEED_RISK (safe_auth 滑块 URL) | **签名通过**, 进风控 (非签名错误) | + +### 结论: 铸币机的边界精确化 +1. **sGuid(32hex hdid) = 可铸** —— doLaunch 按 mid 确定性签发, 服务端接受度 ✅ (§11.8)。 +2. **登录链的签名 = 独立硬锚**: hypasswordLogin 的 APP_SIGN = native + (libudbauthunify/libhydeviceid) 私钥签名覆盖请求字段 (hdid/app_version 换值即错), + 与 doLaunch 的 sGuid 是两套体系 —— 铸币 hdid ≠ 签名证书, 登录被签名层拦截。 +3. 旧结论"32hex hdid 不可铸造"的准确表述修正为:**hdid 可铸造** (服务端签发), + 但 WUP 登录需要 native 私钥证书重签 (注册链/OTA/safe_auth 证书) —— + **登录签名的纯代码复刻 = 下一道关** (记录: AESkeyMgr 钥表 + bigaes 事件已定位, 未复刻)。 +4. device_mint 的登录接入 = 完成到"签名边界"可复现: 铸币→注册链→登录→APP_SIGN 恒定复现。 diff --git a/tools/device_mint.py b/tools/device_mint.py new file mode 100644 index 0000000..03514ab --- /dev/null +++ b/tools/device_mint.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""虎牙铸币机 —— 全管线编排: mid(铸) → doLaunch 收 sGuid → 零设备注册链 → WUP 登录验证. + +铸币闭环 (docs/HUYA_HDID_ALGORITHM_GEN.md §11.8): + sGuid = f(mid) —— 服务端按 mid(LiveUserbase→tUAEx→t5=sMId, 16hex) 确定性签发。 + mid 可任意铸造 → doLaunch 收 32hex sGuid → 登录帧 t1.t0(hdid) 用该 sGuid。 + +用法: + python tools/device_mint.py --mint-only [mid] # 只铸币: mid → sGuid + python tools/device_mint.py --login [--mid X] # 铸币 → 注册链 → 登录 + python tools/device_mint.py --control # 金样本 hdid 登录 (对照基线) + python tools/device_mint.py --matrix # 双雨矩阵: 3 个 mid ×2 复跑 + 登录 + +输出: cred hex / NEED_RISK / REJECTED 对照; 事件实时打印。 +""" +from __future__ import annotations + +import hashlib +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +if str(HERE) not in sys.path: + sys.path.insert(0, str(HERE)) + +from huya_launch_mint import DEFAULT_PROFILE, mint_sguid # noqa: E402 + +try: + from huya_device_register import gen_fresh_identity, login_with_registered # noqa: E402 + HAVE_REG = True +except Exception: # pragma: no cover + HAVE_REG = False + +GOLDEN_HDID = "ed0db8334cadd236c00cadf7e11ab5a5" # 金样本 hdid (对照基线) +URL = "https://wup.huya.com" + + +def mint_couple(mid: str, times: int = 2) -> list[str | None]: + """同一 mid 铸币多次, 验证确定性。""" + out = [] + for _ in range(times): + out.append(mint_sguid(DEFAULT_PROFILE, mid=mid, url=URL)) + return out + + +def full_mint_login(account: str, password: str, mid: str) -> str: + """铸币→注册链→登录 (铸币 hdid), 返回 cred hex / NEED_RISK / REJECTED。""" + sguid = mint_sguid(DEFAULT_PROFILE, mid=mid, url=URL) + print(f"[mint] mid={mid} -> sGuid={sguid}") + if not sguid: + return "REJECTED:no-sguid" + if not HAVE_REG: + return "REJECTED:no-reg-chain" + t1, t2, t5 = gen_fresh_identity() + print(f"[reg] t1(appkey回声)={t1}") + print(f"[reg] t2(action)={t2[:16]}... len={len(t2)}") + print(f"[reg] t5(device_id)={t5}") + out = login_with_registered(t2, t5, account, password, golden_hdid=sguid) + print(f"[login] 铸币hdid={sguid} -> {_brief(out)}") + return out + + +def full_control_login(account: str, password: str) -> str: + """对照: 金样本 hdid 登录 (注册链仍走零设备)。""" + if not HAVE_REG: + return "REJECTED:no-reg-chain" + t1, t2, t5 = gen_fresh_identity() + print(f"[reg] t1={t1} t2={t2[:16]}... t5={t5}") + out = login_with_registered(t2, t5, account, password, golden_hdid=GOLDEN_HDID) + print(f"[login] 金样本hdid={GOLDEN_HDID} -> {_brief(out)}") + return out + + +def _brief(out: str) -> str: + if out.startswith(("0a", "0b")): + return "cred " + out[:16] + "..." + return out + + +def main() -> int: + argv = sys.argv[1:] + if len(argv) >= 2 and argv[0] == "--mint-only": + mid = argv[1] if len(argv) > 1 else DEFAULT_PROFILE["mid"] + r = mint_couple(mid, times=3) + print(f"mid={mid} 铸币×3: {r}") + ok = len({x for x in r}) == 1 and r[0] is not None + print("确定性:", "✅ 三次同值" if ok else "❌ 不一致/失败") + return 0 if ok else 2 + + if len(argv) >= 3 and argv[0] in ("--login", "--control", "--matrix"): + acct, pwd = argv[1], argv[2] + if "--mid" in argv: + mid = argv[argv.index("--mid") + 1] + else: + mid = DEFAULT_PROFILE["mid"] + if argv[0] == "--login": + out = full_mint_login(acct, pwd, mid) + print("最终:", _brief(out)) + return 0 if out.startswith(("0a", "0b")) else 1 + if argv[0] == "--control": + out = full_control_login(acct, pwd) + print("最终:", _brief(out)) + return 0 if out.startswith(("0a", "0b")) else 1 + # --matrix: 3 mid ×2 复跑 (确定性) + 铸币登录 vs 金样本登录 + print("=== 铸币确定性矩阵 (mid → sGuid ×2) ===") + mids = [DEFAULT_PROFILE["mid"], "9c41d0a7b3e5f281", "31415f26a7c8b9d0"] + for m in mids: + r = mint_couple(m, times=2) + print(f"mid={m}: {r} {'✅' if len({x for x in r}) == 1 and r[0] else '❌'}") + print("\n=== 铸币 hdid 登录 (零设备注册链) ===") + out1 = full_mint_login(acct, pwd, mid) + print("\n=== 对照: 金样本 hdid 登录 ===") + out2 = full_control_login(acct, pwd) + print(f"\n铸币 hdid -> {_brief(out1)}") + print(f"金样本 -> {_brief(out2)}") + return 0 if out1.startswith(("0a", "0b")) else 1 + + print(__doc__) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/tools/huya_launch_mint.py b/tools/huya_launch_mint.py index 9440576..44b0586 100644 --- a/tools/huya_launch_mint.py +++ b/tools/huya_launch_mint.py @@ -280,6 +280,40 @@ def parse_launch_rsp(resp: bytes) -> dict: return {"header": header, "sGuid_candidates": sguid_candidates, "structs": structs} +# --------------------------------------------------------------------------- +# live 发送 +# --------------------------------------------------------------------------- + +def mint_sguid(profile: dict | None = None, mid: str | None = None, + url: str = WUP_URL, timeout: float = 20.0) -> str | None: + """doLaunch 上线铸币: 服务端按指纹(mid)确定性签发 sGuid (32hex). + + 返回 tRsp 里的 sGuid; 无则 None (请自行查 HTTP/解析日志)。 + """ + import gzip as _g + import urllib.request as _ur + p = dict(profile or DEFAULT_PROFILE) + if mid: + p["mid"] = mid + body = build_live_launch_wup(p) + req = _ur.Request( + url, data=body, + headers={ + "Content-Type": "application/octet-stream", + "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 11; M2102J2SC Build/RKQ1.200826.002)", + }) + with _ur.urlopen(req, timeout=timeout) as r: + resp = r.read() + if resp[:2] == b"\x1f\x8b": + try: + resp = _g.decompress(resp) + except Exception: + pass + parsed = parse_launch_rsp(resp) + cands = parsed["sGuid_candidates"] + return cands[0] if cands else None + + # --------------------------------------------------------------------------- # CLI # ---------------------------------------------------------------------------