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:
@@ -381,3 +381,30 @@ mid = LiveUserbase→tUAEx→t5=sMId (16hex),**可任意铸造** → doLaunch
|
|||||||
- build 只发 tReq 单键 (对齐真机帧);
|
- build 只发 tReq 单键 (对齐真机帧);
|
||||||
- `parse_launch_rsp` 支持 gzip + `\x06\x20(32hex)` sGuid 可靠提取;
|
- `parse_launch_rsp` 支持 gzip + `\x06\x20(32hex)` sGuid 可靠提取;
|
||||||
- `--live` 一条命令出 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 恒定复现。
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -280,6 +280,40 @@ def parse_launch_rsp(resp: bytes) -> dict:
|
|||||||
return {"header": header, "sGuid_candidates": sguid_candidates, "structs": structs}
|
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
|
# CLI
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user