feat(huya): device_mint 铸币机编排器 + 登录链验收矩阵(铸币hdid→APP_SIGN/金样本→NEED_RISK)

- tools/device_mint.py: --mint-only/--login/--control/--matrix 全编排
- huya_launch_mint.mint_sguid(): 上线铸币函数
- docs §11.9: 接受度矩阵 + 边界精确化 (sGuid可铸/登录签名独立硬锚)
This commit is contained in:
yml2213
2026-08-28 22:31:30 +08:00
parent ccfa742e3b
commit 9ff572191c
3 changed files with 184 additions and 0 deletions
+27
View File
@@ -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 <acct> <pwd> [--mid X]` : 铸币 → 零设备注册链(gen_fresh_identity) → WUP 登录(铸币 hdid)
- `--control <acct> <pwd>` : 金样本 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 恒定复现。
+123
View File
@@ -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 <acct> <pwd> [--mid X] # 铸币 → 注册链 → 登录
python tools/device_mint.py --control <acct> <pwd> # 金样本 hdid 登录 (对照基线)
python tools/device_mint.py --matrix <acct> <pwd> # 双雨矩阵: 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())
+34
View File
@@ -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
# ---------------------------------------------------------------------------