移除二维码信封历史数据依赖
This commit is contained in:
+98
-16
@@ -1,10 +1,9 @@
|
|||||||
"""wupData 信封构造与补丁工具。
|
"""wupData 信封构造与补丁工具。
|
||||||
|
|
||||||
解析与改写 WUP 信封中的 cert、uid、session 等字段。
|
生产二维码信封由 :meth:`Envelope.build_qr` 按当前账号状态编码;
|
||||||
|
:meth:`Envelope.load` 仅保留协议结构模板兼容测试和离线分析。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Verification fixture: this copy intentionally represents the modified branch.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
@@ -12,7 +11,8 @@ import json
|
|||||||
import struct
|
import struct
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from loguru import logger
|
from .taf_protocol import TafOutputStream
|
||||||
|
from .wup_protocol import WupRequest
|
||||||
|
|
||||||
INT8, INT16, INT32, INT64 = 0x00, 0x01, 0x02, 0x03
|
INT8, INT16, INT32, INT64 = 0x00, 0x01, 0x02, 0x03
|
||||||
STRING1, STRING4 = 0x06, 0x07
|
STRING1, STRING4 = 0x06, 0x07
|
||||||
@@ -121,24 +121,106 @@ class Envelope:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, path: str | Path | None = None) -> Envelope:
|
def load(cls, path: str | Path | None = None) -> Envelope:
|
||||||
"""加载信封模板,支持从文件加载或使用内嵌金样本。"""
|
"""加载显式模板,或使用内嵌协议结构模板。
|
||||||
|
|
||||||
|
不自动扫描 ``evidence/``,避免历史抓包成为隐式生产输入。
|
||||||
|
"""
|
||||||
if path:
|
if path:
|
||||||
p = Path(path)
|
p = Path(path)
|
||||||
if p.exists():
|
if p.exists():
|
||||||
return cls._load_from_path(p)
|
return cls._load_from_path(p)
|
||||||
# 尝试查找 evidence/cert_keycap.json
|
|
||||||
candidate = (
|
|
||||||
Path(__file__).resolve().parent.parent.parent
|
|
||||||
/ "evidence"
|
|
||||||
/ "cert_keycap.json"
|
|
||||||
)
|
|
||||||
if candidate.exists():
|
|
||||||
try:
|
|
||||||
return cls._load_from_path(candidate)
|
|
||||||
except Exception as exc: # noqa: BLE001
|
|
||||||
logger.debug(f"加载证书信封候选文件失败: {candidate}: {exc}")
|
|
||||||
return cls(base64.b64decode(PROTOCOL_QURL_TEMPLATE_B64))
|
return cls(base64.b64decode(PROTOCOL_QURL_TEMPLATE_B64))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build_qr(
|
||||||
|
cls,
|
||||||
|
*,
|
||||||
|
uid: int,
|
||||||
|
cert_b64: str,
|
||||||
|
safedeviceid: str,
|
||||||
|
session: int,
|
||||||
|
trace_id: str,
|
||||||
|
device_info: dict[str, str],
|
||||||
|
) -> Envelope:
|
||||||
|
"""按当前账号状态编码二维码绑定信封。
|
||||||
|
|
||||||
|
生产二维码请求不应从抓包信封复制字段。这里仅复用已知的 TAF/WUP
|
||||||
|
字段布局;证书、ACTION、UID、会话和全部设备画像都由本次登录提供。
|
||||||
|
"""
|
||||||
|
if len(cert_b64) != 260:
|
||||||
|
raise ValueError(f"证书 base64 长度异常: {len(cert_b64)} != 260")
|
||||||
|
if len(safedeviceid) != 180:
|
||||||
|
raise ValueError(f"safedeviceid 长度异常: {len(safedeviceid)} != 180")
|
||||||
|
dev = {k: str(v) for k, v in (device_info or {}).items()}
|
||||||
|
meta = json.dumps(
|
||||||
|
{
|
||||||
|
"associationId": 184549392,
|
||||||
|
"funcName": "",
|
||||||
|
"group": 0,
|
||||||
|
"id": 184549392,
|
||||||
|
"session": int(session),
|
||||||
|
"step": 0,
|
||||||
|
"stillLogin": False,
|
||||||
|
"traceId": str(trace_id),
|
||||||
|
"type": 2,
|
||||||
|
"uid": 0,
|
||||||
|
"userContext": "",
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
separators=(",", ":"),
|
||||||
|
)
|
||||||
|
body = TafOutputStream()
|
||||||
|
body.write_struct_begin(0)
|
||||||
|
body.write_struct_begin(0)
|
||||||
|
body.write_int8(0, 0)
|
||||||
|
body.write_string(1, "1.0")
|
||||||
|
body.write_string(2, meta)
|
||||||
|
body.write_string(3, "5008")
|
||||||
|
body.write_int8(4, 3)
|
||||||
|
body.write_string(5, safedeviceid)
|
||||||
|
body.write_string(6, "")
|
||||||
|
body.write_string(7, "")
|
||||||
|
body.write_string(8, "")
|
||||||
|
body.write_string(9, "")
|
||||||
|
body.write_struct_end()
|
||||||
|
|
||||||
|
body.write_struct_begin(1)
|
||||||
|
body.write_string(0, dev.get("hdid", ""))
|
||||||
|
body.write_string(1, dev.get("app_version", "13.4.22"))
|
||||||
|
body.write_string(2, dev.get("sdk_version", "1.0.80138"))
|
||||||
|
body.write_string(3, "")
|
||||||
|
body.write_string(4, dev.get("ip", "127.0.0.1"))
|
||||||
|
body.write_string(5, dev.get("vendor", "android"))
|
||||||
|
body.write_string(6, "")
|
||||||
|
body.write_struct_end()
|
||||||
|
|
||||||
|
body.write_struct_begin(2)
|
||||||
|
body.write_int8(0, 1)
|
||||||
|
body.write_string(1, dev.get("model", ""))
|
||||||
|
body.write_string(2, dev.get("fingerprint", ""))
|
||||||
|
body.write_string(3, dev.get("os", "android"))
|
||||||
|
body.write_string(4, dev.get("screen", ""))
|
||||||
|
body.write_string(6, dev.get("width", "1080"))
|
||||||
|
body.write_string(7, dev.get("height", "2120"))
|
||||||
|
body.write_string(8, dev.get("device_id", ""))
|
||||||
|
body.write_struct_end()
|
||||||
|
body.write_uint64(3, int(uid))
|
||||||
|
body.write_string(4, cert_b64)
|
||||||
|
body.write_string(5, "")
|
||||||
|
body.write_string(6, "")
|
||||||
|
body.write_struct_end()
|
||||||
|
|
||||||
|
req = TafOutputStream()
|
||||||
|
req.write_int32(0, int(session))
|
||||||
|
wup = WupRequest()
|
||||||
|
wup.iTimeout = 0
|
||||||
|
wup.setRequestId(int(session))
|
||||||
|
wup.setServant("huyaudbwebui")
|
||||||
|
wup.setFunc("default")
|
||||||
|
wup.newdata["_wup_data"] = body.get_bytes()
|
||||||
|
wup.newdata["wupudbrequest_v0"] = req.get_bytes()
|
||||||
|
return cls(wup.encode())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _load_from_path(cls, p: Path) -> Envelope:
|
def _load_from_path(cls, p: Path) -> Envelope:
|
||||||
if p.suffix == ".json":
|
if p.suffix == ".json":
|
||||||
|
|||||||
+18
-16
@@ -1,33 +1,35 @@
|
|||||||
changed branch/field: Huya App login device identity and QR envelope metadata
|
changed branch/field: Huya App login QR envelope and device-token propagation
|
||||||
|
|
||||||
MODIFIED_FILE: /Users/yml/codes/live-hub-py/MODIFIED_FILE
|
MODIFIED_FILE: /Users/yml/codes/live-hub-py/MODIFIED_FILE
|
||||||
DIFF_FILE: /Users/yml/codes/live-hub-py/DIFF_FILE
|
DIFF_FILE: /Users/yml/codes/live-hub-py/DIFF_FILE
|
||||||
VERIFICATION.txt: /Users/yml/codes/live-hub-py/VERIFICATION.txt
|
VERIFICATION.txt: /Users/yml/codes/live-hub-py/VERIFICATION.txt
|
||||||
ROLLBACK.sh: /Users/yml/codes/live-hub-py/ROLLBACK.sh
|
ROLLBACK.sh: /Users/yml/codes/live-hub-py/ROLLBACK.sh
|
||||||
MODIFIED_FILE original SHA-256 before fixture marker: 08bc9d7ca6425fab15af2c6218d2574f7f0180e929fa6ccb78c8b37e6ae8493a
|
MODIFIED_FILE original SHA-256 before fixture update: 08bc9d7ca6425fab15af2c6218d2574f7f0180e929fa6ccb78c8b37e6ae8493a
|
||||||
MODIFIED_FILE changed SHA-256: 9d7240baef81e135e83cb62eada00a6a5e9f20be7d5428189bb020ac9742a557
|
MODIFIED_FILE changed SHA-256: e15f95ce30ca0e0faa6e3c8c6cdf4725e4984434df0f39cf237ee0b7197375b1
|
||||||
|
|
||||||
BASELINE command:
|
BASELINE exact command:
|
||||||
/Users/yml/codes/live-hub-py/.venv/bin/pytest -q /tmp/live-hub-py-baseline.OFnU09/tests/test_huya_app_login.py /tmp/live-hub-py-baseline.OFnU09/tests/test_huya_dfp_register.py
|
set -e; BASE=/tmp/live-hub-py-baseline.PJsjpJ; git archive HEAD | tar -x -C "$BASE"; .venv/bin/pytest -q "$BASE/tests/test_huya_app_login.py" "$BASE/tests/test_huya_dfp_register.py"
|
||||||
BASELINE literal output/result:
|
BASELINE literal output/result:
|
||||||
24 passed, 1 warning in 1.43s
|
27 passed, 1 warning in 0.62s
|
||||||
BASELINE exit status: 0
|
BASELINE exit status: 0
|
||||||
|
|
||||||
MODIFIED command:
|
MODIFIED exact command:
|
||||||
/Users/yml/codes/live-hub-py/.venv/bin/pytest -q
|
.venv/bin/pytest -q
|
||||||
MODIFIED literal output/result:
|
MODIFIED literal output/result:
|
||||||
112 passed, 1 warning in 1.26s
|
113 passed, 1 warning in 1.13s
|
||||||
MODIFIED exit status: 0
|
MODIFIED exit status: 0
|
||||||
|
|
||||||
ROLLBACK command:
|
ROLLBACK exact command:
|
||||||
./ROLLBACK.sh /tmp/rollback-source.TCchx3 /tmp/rollback-target.atlKUI
|
set -e; SRC=/tmp/rollback-source.mKNKoP; TGT=/tmp/rollback-target.hHBqnQ; cp MODIFIED_FILE "$SRC"; printf '\ncorrupt\n' >> "$TGT"; ./ROLLBACK.sh "$SRC" "$TGT"; cmp -s "$SRC" "$TGT"
|
||||||
ROLLBACK literal output/result:
|
ROLLBACK literal output/result:
|
||||||
restored /tmp/rollback-target.atlKUI from /tmp/rollback-source.TCchx3
|
restored /tmp/rollback-target.hHBqnQ from /tmp/rollback-source.mKNKoP
|
||||||
restored behavior/status:
|
rollback_status=restored
|
||||||
target SHA-256 after rollback = source SHA-256 = 08bc9d7ca6425fab15af2c6218d2574f7f0180e929fa6ccb78c8b37e6ae8493a; exit status 0
|
source_sha256=e15f95ce30ca0e0faa6e3c8c6cdf4725e4984434df0f39cf237ee0b7197375b1
|
||||||
|
target_sha256=e15f95ce30ca0e0faa6e3c8c6cdf4725e4984434df0f39cf237ee0b7197375b1
|
||||||
|
ROLLBACK exit status: 0
|
||||||
|
restored behavior/status: target byte-for-byte equals source; MODIFIED_FILE remains changed.
|
||||||
|
|
||||||
Additional checks:
|
Additional checks:
|
||||||
.venv/bin/python -m ruff check core/huya tests/test_huya_app_login.py -> All checks passed, exit 0
|
.venv/bin/python -m ruff check core/huya tests/test_huya_app_login.py -> All checks passed, exit 0
|
||||||
|
git diff --check -> exit 0
|
||||||
bash -n dev.sh deploy.sh -> exit 0
|
bash -n dev.sh deploy.sh -> exit 0
|
||||||
node --check services/yyb-worker/runtime/probe-jsdom-pay.mjs -> exit 0
|
|
||||||
find scripts -name '*.py' -print0 | xargs -0 .venv/bin/python -m py_compile -> scripts_compile:0
|
|
||||||
|
|||||||
+21
-19
@@ -9,7 +9,8 @@
|
|||||||
6. POST /web/cookie/verify 兑换获取全套网页 Cookie
|
6. POST /web/cookie/verify 兑换获取全套网页 Cookie
|
||||||
|
|
||||||
注册链不再重放旧 dfpReport 密文。登录帧的 32hex hdid 仍是服务端硬锚,
|
注册链不再重放旧 dfpReport 密文。登录帧的 32hex hdid 仍是服务端硬锚,
|
||||||
当前继续使用已注册样本;新注册链动态更新的是 safedeviceid 和 device_id。
|
登录帧中的 HDID32 是 app 版本级协议常量;二维码信封的 ACTION、设备字段、
|
||||||
|
UID、证书和会话元数据均在本次登录中动态生成或由注册链签发。
|
||||||
注册链(``core/huya/dfp_register``)每次登录前执行,失败即抛错终止(``HuyaAppLoginError``),
|
注册链(``core/huya/dfp_register``)每次登录前执行,失败即抛错终止(``HuyaAppLoginError``),
|
||||||
不读取画像里的旧固定值,也不静默回退旧链。
|
不读取画像里的旧固定值,也不静默回退旧链。
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ from urllib.parse import parse_qs, quote, urlparse
|
|||||||
import requests
|
import requests
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from .cert_forge import build_p1, decrypt_cert, forge_cert, parse_p1
|
from .cert_forge import build_p1, forge_cert
|
||||||
from .cookie_utils import normalize_huya_cookie
|
from .cookie_utils import normalize_huya_cookie
|
||||||
from .device_fingerprint import account_state_dir, get_huya_sdid, reset_account_state
|
from .device_fingerprint import account_state_dir, get_huya_sdid, reset_account_state
|
||||||
from .device_profile import get_profile, mobile_user_agent
|
from .device_profile import get_profile, mobile_user_agent
|
||||||
@@ -270,7 +271,8 @@ def login_cred_with_flow(
|
|||||||
max_rounds: int = 3,
|
max_rounds: int = 3,
|
||||||
device_info: dict | None = None,
|
device_info: dict | None = None,
|
||||||
proxies: dict | None = None,
|
proxies: dict | None = None,
|
||||||
) -> tuple[bytes, int]:
|
include_device_token: bool = False,
|
||||||
|
) -> tuple[bytes, int] | tuple[bytes, int, str]:
|
||||||
"""新注册设备后登录,返回 ``(新鲜 cred, 真实 uid)``。
|
"""新注册设备后登录,返回 ``(新鲜 cred, 真实 uid)``。
|
||||||
|
|
||||||
注册只执行一次;safe_auth 通过后的重发继续使用同一组设备字段。
|
注册只执行一次;safe_auth 通过后的重发继续使用同一组设备字段。
|
||||||
@@ -305,6 +307,8 @@ def login_cred_with_flow(
|
|||||||
)
|
)
|
||||||
if cred:
|
if cred:
|
||||||
uid = parse_real_uid(resp)
|
uid = parse_real_uid(resp)
|
||||||
|
if include_device_token:
|
||||||
|
return cred, uid, safedeviceid
|
||||||
return cred, uid
|
return cred, uid
|
||||||
if risk_url:
|
if risk_url:
|
||||||
kind = (
|
kind = (
|
||||||
@@ -462,11 +466,12 @@ class HuyaAppPasswordLogin:
|
|||||||
|
|
||||||
# 1) 获取新鲜 cred 与 真实 uid (自动过 safe_auth 滑块)
|
# 1) 获取新鲜 cred 与 真实 uid (自动过 safe_auth 滑块)
|
||||||
try:
|
try:
|
||||||
cred, uid = login_cred_with_flow(
|
cred, uid, safedeviceid = login_cred_with_flow(
|
||||||
acct,
|
acct,
|
||||||
self.password,
|
self.password,
|
||||||
device_info=self.device_info,
|
device_info=self.device_info,
|
||||||
proxies=self.proxies,
|
proxies=self.proxies,
|
||||||
|
include_device_token=True,
|
||||||
)
|
)
|
||||||
except HuyaAppQrAuthRequiredError as exc:
|
except HuyaAppQrAuthRequiredError as exc:
|
||||||
return HuyaLoginResult(
|
return HuyaLoginResult(
|
||||||
@@ -485,30 +490,27 @@ class HuyaAppPasswordLogin:
|
|||||||
|
|
||||||
# 2) 本地生成 nonce 铸造证书 (P1 指纹与该账号设备画像一致)
|
# 2) 本地生成 nonce 铸造证书 (P1 指纹与该账号设备画像一致)
|
||||||
try:
|
try:
|
||||||
env = Envelope.load()
|
|
||||||
orig = base64.b64decode(env.cert_b64)
|
|
||||||
f = parse_p1(decrypt_cert(orig))
|
|
||||||
st = int(time.time() * 1000)
|
st = int(time.time() * 1000)
|
||||||
rnd = gen_nonce(uid, K1_DEFAULT, service_time_ms=st, counter=0)
|
rnd = gen_nonce(uid, K1_DEFAULT, service_time_ms=st, counter=0)
|
||||||
fp_bytes = self.device_info["fingerprint"].encode("ascii")
|
fp_bytes = self.device_info["fingerprint"].encode("ascii")
|
||||||
p1 = build_p1(f["app_id"], fp_bytes, cred, rnd=rnd)
|
p1 = build_p1(b"5008", fp_bytes, cred, rnd=rnd)
|
||||||
cert = base64.b64encode(forge_cert(p1, key_idx=orig[1])).decode()
|
# 证书头和 app_id 是协议版本常量,不从抓包信封读取。
|
||||||
|
cert = base64.b64encode(forge_cert(p1, key_idx=0x20)).decode()
|
||||||
|
|
||||||
# 3) 信封补丁
|
# 3) 按当前账号画像和本次注册令牌重编码二维码信封。
|
||||||
raw = bytearray(env.raw)
|
|
||||||
if env.cert_off is None or env.uid_off is None:
|
|
||||||
raise ValueError("信封缺少证书或 uid 偏移")
|
|
||||||
raw[env.cert_off : env.cert_off + env.cert_len] = cert.encode("ascii")
|
|
||||||
if env.uid != uid:
|
|
||||||
struct.pack_into(">Q", raw, env.uid_off, uid)
|
|
||||||
# QR 信封只保留协议结构;不要重放抓包里的旧会话值。
|
|
||||||
qr_session = random.randint(1_000_000, 9_999_999)
|
qr_session = random.randint(1_000_000, 9_999_999)
|
||||||
qr_trace = (
|
qr_trace = (
|
||||||
f"{uuid.uuid4().hex[:16]}-{random.randint(10000, 99999)}-"
|
f"{uuid.uuid4().hex[:16]}-{random.randint(10000, 99999)}-"
|
||||||
f"{time.time_ns():020d}"
|
f"{time.time_ns():020d}"
|
||||||
)
|
)
|
||||||
env.raw = raw
|
env = Envelope.build_qr(
|
||||||
env.patch_session(qr_session).patch_meta(qr_session, qr_trace)
|
uid=uid,
|
||||||
|
cert_b64=cert,
|
||||||
|
safedeviceid=safedeviceid,
|
||||||
|
session=qr_session,
|
||||||
|
trace_id=qr_trace,
|
||||||
|
device_info=self.device_info,
|
||||||
|
)
|
||||||
wup = base64.b64encode(bytes(env.raw)).decode("ascii")
|
wup = base64.b64encode(bytes(env.raw)).decode("ascii")
|
||||||
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return HuyaLoginResult(
|
return HuyaLoginResult(
|
||||||
|
|||||||
+98
-14
@@ -1,6 +1,7 @@
|
|||||||
"""wupData 信封构造与补丁工具。
|
"""wupData 信封构造与补丁工具。
|
||||||
|
|
||||||
解析与改写 WUP 信封中的 cert、uid、session 等字段。
|
生产二维码信封由 :meth:`Envelope.build_qr` 按当前账号状态编码;
|
||||||
|
:meth:`Envelope.load` 仅保留协议结构模板兼容测试和离线分析。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -10,7 +11,8 @@ import json
|
|||||||
import struct
|
import struct
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from loguru import logger
|
from .taf_protocol import TafOutputStream
|
||||||
|
from .wup_protocol import WupRequest
|
||||||
|
|
||||||
INT8, INT16, INT32, INT64 = 0x00, 0x01, 0x02, 0x03
|
INT8, INT16, INT32, INT64 = 0x00, 0x01, 0x02, 0x03
|
||||||
STRING1, STRING4 = 0x06, 0x07
|
STRING1, STRING4 = 0x06, 0x07
|
||||||
@@ -119,24 +121,106 @@ class Envelope:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, path: str | Path | None = None) -> Envelope:
|
def load(cls, path: str | Path | None = None) -> Envelope:
|
||||||
"""加载信封模板,支持从文件加载或使用内嵌金样本。"""
|
"""加载显式模板,或使用内嵌协议结构模板。
|
||||||
|
|
||||||
|
不自动扫描 ``evidence/``,避免历史抓包成为隐式生产输入。
|
||||||
|
"""
|
||||||
if path:
|
if path:
|
||||||
p = Path(path)
|
p = Path(path)
|
||||||
if p.exists():
|
if p.exists():
|
||||||
return cls._load_from_path(p)
|
return cls._load_from_path(p)
|
||||||
# 尝试查找 evidence/cert_keycap.json
|
|
||||||
candidate = (
|
|
||||||
Path(__file__).resolve().parent.parent.parent
|
|
||||||
/ "evidence"
|
|
||||||
/ "cert_keycap.json"
|
|
||||||
)
|
|
||||||
if candidate.exists():
|
|
||||||
try:
|
|
||||||
return cls._load_from_path(candidate)
|
|
||||||
except Exception as exc: # noqa: BLE001
|
|
||||||
logger.debug(f"加载证书信封候选文件失败: {candidate}: {exc}")
|
|
||||||
return cls(base64.b64decode(PROTOCOL_QURL_TEMPLATE_B64))
|
return cls(base64.b64decode(PROTOCOL_QURL_TEMPLATE_B64))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build_qr(
|
||||||
|
cls,
|
||||||
|
*,
|
||||||
|
uid: int,
|
||||||
|
cert_b64: str,
|
||||||
|
safedeviceid: str,
|
||||||
|
session: int,
|
||||||
|
trace_id: str,
|
||||||
|
device_info: dict[str, str],
|
||||||
|
) -> Envelope:
|
||||||
|
"""按当前账号状态编码二维码绑定信封。
|
||||||
|
|
||||||
|
生产二维码请求不应从抓包信封复制字段。这里仅复用已知的 TAF/WUP
|
||||||
|
字段布局;证书、ACTION、UID、会话和全部设备画像都由本次登录提供。
|
||||||
|
"""
|
||||||
|
if len(cert_b64) != 260:
|
||||||
|
raise ValueError(f"证书 base64 长度异常: {len(cert_b64)} != 260")
|
||||||
|
if len(safedeviceid) != 180:
|
||||||
|
raise ValueError(f"safedeviceid 长度异常: {len(safedeviceid)} != 180")
|
||||||
|
dev = {k: str(v) for k, v in (device_info or {}).items()}
|
||||||
|
meta = json.dumps(
|
||||||
|
{
|
||||||
|
"associationId": 184549392,
|
||||||
|
"funcName": "",
|
||||||
|
"group": 0,
|
||||||
|
"id": 184549392,
|
||||||
|
"session": int(session),
|
||||||
|
"step": 0,
|
||||||
|
"stillLogin": False,
|
||||||
|
"traceId": str(trace_id),
|
||||||
|
"type": 2,
|
||||||
|
"uid": 0,
|
||||||
|
"userContext": "",
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
separators=(",", ":"),
|
||||||
|
)
|
||||||
|
body = TafOutputStream()
|
||||||
|
body.write_struct_begin(0)
|
||||||
|
body.write_struct_begin(0)
|
||||||
|
body.write_int8(0, 0)
|
||||||
|
body.write_string(1, "1.0")
|
||||||
|
body.write_string(2, meta)
|
||||||
|
body.write_string(3, "5008")
|
||||||
|
body.write_int8(4, 3)
|
||||||
|
body.write_string(5, safedeviceid)
|
||||||
|
body.write_string(6, "")
|
||||||
|
body.write_string(7, "")
|
||||||
|
body.write_string(8, "")
|
||||||
|
body.write_string(9, "")
|
||||||
|
body.write_struct_end()
|
||||||
|
|
||||||
|
body.write_struct_begin(1)
|
||||||
|
body.write_string(0, dev.get("hdid", ""))
|
||||||
|
body.write_string(1, dev.get("app_version", "13.4.22"))
|
||||||
|
body.write_string(2, dev.get("sdk_version", "1.0.80138"))
|
||||||
|
body.write_string(3, "")
|
||||||
|
body.write_string(4, dev.get("ip", "127.0.0.1"))
|
||||||
|
body.write_string(5, dev.get("vendor", "android"))
|
||||||
|
body.write_string(6, "")
|
||||||
|
body.write_struct_end()
|
||||||
|
|
||||||
|
body.write_struct_begin(2)
|
||||||
|
body.write_int8(0, 1)
|
||||||
|
body.write_string(1, dev.get("model", ""))
|
||||||
|
body.write_string(2, dev.get("fingerprint", ""))
|
||||||
|
body.write_string(3, dev.get("os", "android"))
|
||||||
|
body.write_string(4, dev.get("screen", ""))
|
||||||
|
body.write_string(6, dev.get("width", "1080"))
|
||||||
|
body.write_string(7, dev.get("height", "2120"))
|
||||||
|
body.write_string(8, dev.get("device_id", ""))
|
||||||
|
body.write_struct_end()
|
||||||
|
body.write_uint64(3, int(uid))
|
||||||
|
body.write_string(4, cert_b64)
|
||||||
|
body.write_string(5, "")
|
||||||
|
body.write_string(6, "")
|
||||||
|
body.write_struct_end()
|
||||||
|
|
||||||
|
req = TafOutputStream()
|
||||||
|
req.write_int32(0, int(session))
|
||||||
|
wup = WupRequest()
|
||||||
|
wup.iTimeout = 0
|
||||||
|
wup.setRequestId(int(session))
|
||||||
|
wup.setServant("huyaudbwebui")
|
||||||
|
wup.setFunc("default")
|
||||||
|
wup.newdata["_wup_data"] = body.get_bytes()
|
||||||
|
wup.newdata["wupudbrequest_v0"] = req.get_bytes()
|
||||||
|
return cls(wup.encode())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _load_from_path(cls, p: Path) -> Envelope:
|
def _load_from_path(cls, p: Path) -> Envelope:
|
||||||
if p.suffix == ".json":
|
if p.suffix == ".json":
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from core.huya.login import HuyaLoginResult
|
|||||||
from core.huya.nonce_forge import K1_DEFAULT, gen_nonce
|
from core.huya.nonce_forge import K1_DEFAULT, gen_nonce
|
||||||
from core.huya.udb_aes import udb_decrypt, udb_encrypt
|
from core.huya.udb_aes import udb_decrypt, udb_encrypt
|
||||||
from core.huya.wup_encoder import _make_name, build_password_login_wup
|
from core.huya.wup_encoder import _make_name, build_password_login_wup
|
||||||
|
from core.huya.wup_protocol import WupResponse
|
||||||
from web.backend.database import Base
|
from web.backend.database import Base
|
||||||
from web.backend.models import HuyaAccount, User
|
from web.backend.models import HuyaAccount, User
|
||||||
from web.backend.routers.huya import (
|
from web.backend.routers.huya import (
|
||||||
@@ -102,6 +103,38 @@ class TestHuyaAppLogin:
|
|||||||
assert b'"session":7654321' in current
|
assert b'"session":7654321' in current
|
||||||
assert (b'"traceId":"' + new_trace.encode() + b'"') in current
|
assert (b'"traceId":"' + new_trace.encode() + b'"') in current
|
||||||
|
|
||||||
|
def test_qr_envelope_is_fully_dynamic(self):
|
||||||
|
profile = {
|
||||||
|
"hdid": "h" * 32,
|
||||||
|
"app_version": "13.4.22",
|
||||||
|
"sdk_version": "1.0.80138",
|
||||||
|
"ip": "10.0.0.8",
|
||||||
|
"vendor": "vivo",
|
||||||
|
"model": "V2370A",
|
||||||
|
"fingerprint": "f" * 40,
|
||||||
|
"os": "android",
|
||||||
|
"screen": "V2370A,34,13",
|
||||||
|
"width": "1080",
|
||||||
|
"height": "2412",
|
||||||
|
"device_id": "d" * 40,
|
||||||
|
}
|
||||||
|
env = Envelope.build_qr(
|
||||||
|
uid=1199664135026,
|
||||||
|
cert_b64="A" * 260,
|
||||||
|
safedeviceid="B" * 180,
|
||||||
|
session=7654321,
|
||||||
|
trace_id="t" * 16 + "-12345-" + "9" * 20,
|
||||||
|
device_info=profile,
|
||||||
|
)
|
||||||
|
wup = WupResponse()
|
||||||
|
wup.decode(bytes(env.raw))
|
||||||
|
body = wup.newdata["_wup_data"]
|
||||||
|
for value in (b"B" * 180, b"h" * 32, b"f" * 40, b"V2370A", b"d" * 40):
|
||||||
|
assert value in body
|
||||||
|
assert b"PQwemAN9NHkZKoMq" not in body
|
||||||
|
assert b"02df398797432eadefcc12767119ad5e80999389" not in body
|
||||||
|
assert b"7c5387e0539c023c31c4ff0e807e7256117385ee" not in body
|
||||||
|
|
||||||
def test_device_profile_generation(self):
|
def test_device_profile_generation(self):
|
||||||
p1 = generate_profile()
|
p1 = generate_profile()
|
||||||
assert p1["os"] == "android"
|
assert p1["os"] == "android"
|
||||||
@@ -271,10 +304,13 @@ class TestHuyaAppLogin:
|
|||||||
patch("core.huya.app_login.solve_safe_auth", return_value={"authId": "id"}),
|
patch("core.huya.app_login.solve_safe_auth", return_value={"authId": "id"}),
|
||||||
patch("core.huya.app_login.parse_real_uid", return_value=1199666914671),
|
patch("core.huya.app_login.parse_real_uid", return_value=1199666914671),
|
||||||
):
|
):
|
||||||
cred, uid = login_cred_with_flow("300023887", "pw")
|
cred, uid, safedeviceid = login_cred_with_flow(
|
||||||
|
"300023887", "pw", include_device_token=True
|
||||||
|
)
|
||||||
|
|
||||||
assert cred == b"c" * 114
|
assert cred == b"c" * 114
|
||||||
assert uid == 1199666914671
|
assert uid == 1199666914671
|
||||||
|
assert safedeviceid == "A" * 180
|
||||||
assert calls == [assets, assets]
|
assert calls == [assets, assets]
|
||||||
|
|
||||||
def test_login_cred_flow_maps_invalid_password_response(self):
|
def test_login_cred_flow_maps_invalid_password_response(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user