hdid 溯源实测: libhydeviceid.so设备ID, 纯代码不可铸造; 共用登录无碍仅设备维度关联风险
- df/collect 响应含40hex hdid(每次新值)但 WUP 登录拒绝(652B) -> 非同一体系 - runner.js 输出 __HDID__, get_huya_sdid 一并返回 - 文档: hdid 来源/可铸造性/共用风险评估 + 设备池方案
This commit is contained in:
@@ -32,12 +32,16 @@ class HuyaSdidResult:
|
|||||||
"""sdid 获取结果。"""
|
"""sdid 获取结果。"""
|
||||||
|
|
||||||
sdid: str = ""
|
sdid: str = ""
|
||||||
|
hdid: str = "" # df/collect 同响应下发的 40hex 设备ID; 与 WUP 登录的 32hex hdid 非同一体系
|
||||||
source: str = ""
|
source: str = ""
|
||||||
message: str = ""
|
message: str = ""
|
||||||
|
|
||||||
|
|
||||||
def _run_node_runner(state_dir: Path, app_id: str, timeout: tuple[float, float]) -> str:
|
HDID_PREFIX = "__HDID__"
|
||||||
"""调用 node runner,返回 sdid。"""
|
|
||||||
|
|
||||||
|
def _run_node_runner(state_dir: Path, app_id: str, timeout: tuple[float, float]) -> tuple[str, str]:
|
||||||
|
"""调用 node runner,返回 (sdid, hdid)。"""
|
||||||
node_bin = shutil.which("node")
|
node_bin = shutil.which("node")
|
||||||
if not node_bin:
|
if not node_bin:
|
||||||
raise HuyaFingerprintError("未找到 node 可执行文件(hydevice.js 需要 JS 引擎)")
|
raise HuyaFingerprintError("未找到 node 可执行文件(hydevice.js 需要 JS 引擎)")
|
||||||
@@ -55,10 +59,15 @@ def _run_node_runner(state_dir: Path, app_id: str, timeout: tuple[float, float])
|
|||||||
except subprocess.TimeoutExpired as exc:
|
except subprocess.TimeoutExpired as exc:
|
||||||
raise HuyaFingerprintError(f"hydevice runner 超时({total_timeout}s)") from exc
|
raise HuyaFingerprintError(f"hydevice runner 超时({total_timeout}s)") from exc
|
||||||
|
|
||||||
|
sdid, hdid = "", ""
|
||||||
for line in (proc.stdout or "").splitlines():
|
for line in (proc.stdout or "").splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line.startswith(SDID_PREFIX) and len(line) > len(SDID_PREFIX) + 20:
|
if line.startswith(SDID_PREFIX) and len(line) > len(SDID_PREFIX) + 20:
|
||||||
return line[len(SDID_PREFIX):]
|
sdid = line[len(SDID_PREFIX):]
|
||||||
|
if line.startswith(HDID_PREFIX) and len(line) > len(HDID_PREFIX) + 20:
|
||||||
|
hdid = line[len(HDID_PREFIX):]
|
||||||
|
if sdid:
|
||||||
|
return sdid, hdid
|
||||||
stderr_tail = (proc.stderr or "").strip().splitlines()
|
stderr_tail = (proc.stderr or "").strip().splitlines()
|
||||||
detail = stderr_tail[-1][:120] if stderr_tail else f"exit={proc.returncode}"
|
detail = stderr_tail[-1][:120] if stderr_tail else f"exit={proc.returncode}"
|
||||||
raise HuyaFingerprintError(f"hydevice runner 未返回 sdid: {detail}")
|
raise HuyaFingerprintError(f"hydevice runner 未返回 sdid: {detail}")
|
||||||
@@ -104,10 +113,11 @@ def get_huya_sdid(
|
|||||||
state_dir = Path(tempfile.gettempdir()) / "huya_fp_state"
|
state_dir = Path(tempfile.gettempdir()) / "huya_fp_state"
|
||||||
state_dir = Path(state_dir)
|
state_dir = Path(state_dir)
|
||||||
try:
|
try:
|
||||||
sdid = _run_node_runner(state_dir, app_id, timeout)
|
sdid, hdid = _run_node_runner(state_dir, app_id, timeout)
|
||||||
if sdid:
|
if sdid:
|
||||||
logger.debug("虎牙设备指纹成功(node): sdid={}...", sdid[:24])
|
logger.debug("虎牙设备指纹成功(node): sdid={}... hdid={}...",
|
||||||
return HuyaSdidResult(sdid=sdid, source="fingerprint")
|
sdid[:24], hdid[:10])
|
||||||
|
return HuyaSdidResult(sdid=sdid, hdid=hdid, source="fingerprint")
|
||||||
except HuyaFingerprintError as exc:
|
except HuyaFingerprintError as exc:
|
||||||
logger.warning("虎牙 hydevice 指纹失败: {}", exc)
|
logger.warning("虎牙 hydevice 指纹失败: {}", exc)
|
||||||
if not allow_fallback:
|
if not allow_fallback:
|
||||||
|
|||||||
@@ -63,7 +63,11 @@ globalThis.XMLHttpRequest = class {
|
|||||||
if (String(this._u).includes('/collect')) {
|
if (String(this._u).includes('/collect')) {
|
||||||
try {
|
try {
|
||||||
const j = JSON.parse(r.text);
|
const j = JSON.parse(r.text);
|
||||||
if (j && j.data && j.data.sdid) { console.log('__SDID__' + j.data.sdid); setTimeout(() => process.exit(0), 200); }
|
if (j && j.data) {
|
||||||
|
if (j.data.sdid) console.log('__SDID__' + j.data.sdid);
|
||||||
|
if (j.data.hdid) console.log('__HDID__' + j.data.hdid);
|
||||||
|
setTimeout(() => process.exit(0), 200);
|
||||||
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
try { this.onreadystatechange && this.onreadystatechange(); } catch (e) {}
|
try { this.onreadystatechange && this.onreadystatechange(); } catch (e) {}
|
||||||
|
|||||||
@@ -520,3 +520,27 @@ multi 全换(除hdid/版本)| 滑块(可过) ✅
|
|||||||
- tools/app_login_flow.py: login_cred 支持 device_info 参数 (滑块自动过)
|
- tools/app_login_flow.py: login_cred 支持 device_info 参数 (滑块自动过)
|
||||||
- tools/full_web_cookie.py: 全链接入画像 (--new-device 强制换新画像)
|
- tools/full_web_cookie.py: 全链接入画像 (--new-device 强制换新画像)
|
||||||
- evidence/device_profiles.json: 账号→画像映射
|
- evidence/device_profiles.json: 账号→画像映射
|
||||||
|
|
||||||
|
### 追问: hdid 从哪来? 所有账号共用一个会不会出问题? (2026-08-26 追加实测)
|
||||||
|
|
||||||
|
**hdid 来源**:
|
||||||
|
- WUP 登录帧 t1.t0 的 hdid(`ed0db8334cadd236c00cadf7e11ab5a5`, 32hex)是
|
||||||
|
**App 原生 libhydeviceid.so 生成的设备 ID**(APK 静态分析: libhydeviceid.so
|
||||||
|
= "设备 ID 生成"), 经 App 渠道 dfpReport 链注册, 与 app_version(appSign)
|
||||||
|
绑定签名校验。
|
||||||
|
- 纯代码铸造新 hdid **不可行**(全部实测):
|
||||||
|
| 尝试 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| 随机 32hex hdid | 652B APP_SIGN_NOT_MATCH |
|
||||||
|
| df.huya.com df/collect 下发的 40hex hdid (每次新值) | 652B APP_SIGN_NOT_MATCH |
|
||||||
|
| 40hex 截断前 32 位 | 652B APP_SIGN_NOT_MATCH |
|
||||||
|
| 金样本 hdid | cred ✅ |
|
||||||
|
|
||||||
|
**共用风险**: 登录层无碍(新账号直出 cred, 无 qr_auth); 风险在服务端
|
||||||
|
**设备维度账号关联**(一台设备 N 账号)。若需真隔离: 从多台真机各抓一套
|
||||||
|
WUP 帧 {hdid, fingerprint, device_id, safedeviceid} 建设备池, 账号轮换分配;
|
||||||
|
或破解 dfpReport 加密体(逆向笔记待破解#2)后纯代码注册新设备。
|
||||||
|
|
||||||
|
**附带发现**: df/collect 响应含 40hex hdid 字段(与 sdid 同返), 已让
|
||||||
|
`core/huya/device_fingerprint.get_huya_sdid` 一并返回 (runner.js 输出
|
||||||
|
`__HDID__` 行); 它与 WUP 的 32hex hdid 非同一体系, 不能互用。
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ libhydeviceid.so 生成 / 服务端绑定版本), 多账号时**同一 hdid+版
|
|||||||
(它不泄露单一账号身份, 只是设备签名锚点)**; 其余字段每账号随机即可,
|
(它不泄露单一账号身份, 只是设备签名锚点)**; 其余字段每账号随机即可,
|
||||||
首次触发滑块由 solver 自动过 → 登录照常出 cred (已端到端实测)。
|
首次触发滑块由 solver 自动过 → 登录照常出 cred (已端到端实测)。
|
||||||
|
|
||||||
|
hdid 来源与可铸造性 (2026-08-26 实测):
|
||||||
|
- WUP 帧 t1.t0 的 hdid (32hex) = App 原生 libhydeviceid.so 生成的设备 ID
|
||||||
|
(APK 静态分析: libhydeviceid.so = 设备ID生成), 经 dfpReport 链注册。
|
||||||
|
- 换任意新值 → APP_SIGN_NOT_MATCH 硬错: 随机32hex ✗ / df.huya.com
|
||||||
|
df/collect 下发的 40hex hdid ✗ / 截断 ✗ —— 纯代码无法铸造新 hdid。
|
||||||
|
- 共用同一 hdid: 登录不受阻 (实测新账号直出 cred), 但服务端可在设备维度
|
||||||
|
关联账号; 若需真隔离, 须多台真机各采一套 {hdid, fingerprint, device_id}
|
||||||
|
建设备池轮换。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
python tools/huya_device_profile.py # 生成并打印一套新画像
|
python tools/huya_device_profile.py # 生成并打印一套新画像
|
||||||
python tools/huya_device_profile.py <账号> # 按账号取/建画像(持久化)
|
python tools/huya_device_profile.py <账号> # 按账号取/建画像(持久化)
|
||||||
|
|||||||
Reference in New Issue
Block a user