闭合QIMEI h38兼容键

This commit is contained in:
yml2213
2026-09-02 17:51:12 +08:00
parent 1f3773de28
commit 92ba6cbc62
5 changed files with 31 additions and 4 deletions
+3 -1
View File
@@ -357,7 +357,9 @@ def fill_web_cookie_fields(
# qimei 皮肤字段
# _qimei_fingerprint: QIMEI SDK x64hash128(浏览器能力特征,31) 32hex。
add("_qimei_fingerprint", qimei_fingerprint_generate(qimei_feature))
add("_qimei_h38", secrets.token_hex(19))
# SDK 内部 h38 就是 42 位 UUID42,并不存在独立的 38 位生成器。当前活动
# getUserScore 只检查同名 Cookie 键是否存在,不检查值;复用 SDK h38 的真实值。
add("_qimei_h38", pairs["_qimei_uuid42"])
# 会话/统计字段
yasmid = _rand_float()
@@ -30,7 +30,7 @@
同一网页会话内这些值保持稳定:
- `_qimei_uuid42`:42 位;`_qimei_h38`:38 位;`_qimei_fingerprint`:32 位。三者是 QIMEI 设备标识;其中 fingerprint 与 uuid42 均已按 SDK 0.6.3 算法复刻。
- `_qimei_uuid42`:42 位;`_qimei_fingerprint`:32 位,两者已按 SDK 0.6.3 算法复刻。早期文档曾把 `_qimei_h38` 记为独立 38 位设备标识,后续 VM 追踪确认 SDK 内部 `h38` 就是 42 位 UUID42;当前活动积分链路仅要求 Cookie 中存在 `_qimei_h38` 键。
- `udb_anobiztoken`:344 位 URL-safe 字符串,和 `udb_anouid` 共同属于匿名设备状态,不能用重复 UUID 字节拼接。
- `guid` 与 `udb_guiddata` 均为 32 位,但抓包值不同,不能复用同一画像字段。
- `udb_deviceid` 为 `w_` 加 19 位数字;格式可以校验,但仅凭格式无法得到真实值。
@@ -40,7 +40,7 @@
## 旧实现的问题
旧实现曾在空 Session 中生成 `__yamid_new`、`game_did`、三个 QIMEI 字段、`udb_anobiztoken`、`HMACCOUNT`、`_yasids` 和多个固定计数器。它们只能满足长度测试,不能证明值具有协议语义;当前 fingerprint 与 uuid42 均已替换为 SDK 0.6.3 算法,仍需以同一会话的线上结果确认风控关联性。
旧实现曾在空 Session 中生成 `__yamid_new`、`game_did`、两个 QIMEI 值、`_qimei_h38` 兼容键、`udb_anobiztoken`、`HMACCOUNT`、`_yasids` 和多个固定计数器。它们只能满足长度测试,不能证明值具有协议语义;当前 fingerprint 与 uuid42 均已替换为 SDK 0.6.3 算法,`_qimei_h38` 也已改为复用 SDK 内部 `h38` 的 UUID42 值。
旧实现还把 `device_info["guid32"][:32]` 同时写入 `guid` 和 `udb_guiddata`,并把手机号直接写入 `udb_passport`/`username`。这两点会造成设备身份和账号映射偏差。
@@ -52,7 +52,7 @@
### 9.1 抓包新增证据
`705-meta.json` 的 `GET www.huya.com/30596253` 在请求发出时已经携带 43 个网页 Cookie,
包括 `guid`、`game_did`、三个 QIMEI、`udb_guiddata`、`udb_deviceid`、`udb_anobiztoken`
包括 `guid`、`game_did`、QIMEI 相关字段、`udb_guiddata`、`udb_deviceid`、`udb_anobiztoken`
和统计字段;该响应没有 `Set-Cookie`。随后 `714-meta.json` 的
`POST df.huya.com/web/df/collect` 只返回 `sdid`、`hdid` 两个 `Set-Cookie`。
+7
View File
@@ -21,6 +21,7 @@ App 登录可以得到新鲜的 `udb_cred`、`udb_uid`、`yyuid`、`udb_biztoken
| `_yasids` | 格式完成,精确共享种子未确认 | 与 `__yamid_new` 使用同类时钟生成器 |
| `_qimei_uuid42` | 已完成 | QIMEI SDK 0.6.3 的 14 位时间前缀 + CRC32 替换段 + 20 位随机半字节段 |
| `_qimei_fingerprint` | 算法已复刻 | QIMEI `x64hash128(feature_string, 31)` |
| `_qimei_h38` | 兼容门槛已闭合 | SDK 内部 `h38` 就是 42 位 UUID42;活动积分链路只校验 Cookie 键存在,缺失时复用 `_qimei_uuid42` |
| 统计字段 | 已完成 | 线上查询已接受;部分字段仍是统计用途 |
## `_qimei_fingerprint` 算法
@@ -72,3 +73,9 @@ sequence=0..f,0..3 17b0f060d14100b2230123456789abcdef012396a9
```
每个值都可以拆成 `14 + 4 + 20 + 4 = 42` 位。时间前缀由本地时间组成:年份末两位转十六进制并补两位、月份转十六进制、日/时/分/秒转十六进制并各补两位,最后追加 `cookieEnabled ? 100 : 000`。中间 20 位由 20 次独立的 `floor(Math.random() * 16)` 依次拼接;上面的单值向量因为探针固定了每次随机返回值,所以表现为相同半字节重复。两个 `----` 占位组成模板后,SDK 对模板 ASCII 字节计算标准 CRC32(多项式 `0xEDB88320`),将 8 位小写结果的前后四位分别替换占位,最后调用 `toLocaleLowerCase()`。Python 实现位于 `core/huya/web_cookie_fields.py`,并用上述固定与递增序列向量回归;随机上界按 15 处理,避免理论输入 `1.0` 产生两位半字节。VM 追踪在 `/tmp/qvmtrace17.out`,生成器入口为 `e=6064`,模板拼接和替换发生在 `m≈5015..5137`。该实现只用于查询链路的设备态生成,不触发兑换、下单或支付。
## `_qimei_h38` 兼容键(2026-09-02)
QIMEI SDK 0.6.3 的 `getQimei36(false)` 结果中,内部字段 `h38` 实际为上述 42 位 UUID42。当前无头 Chrome 在活动页不会生成名为 `_qimei_h38` 的 Cookie,现有浏览器采集证据中也没有该键。
账号 id=60 的活动 WSS 只读 A/B 表明:完全删除键时,角色查询返回 `200`,积分查询连续三轮均在 10 秒后无响应;保留空值、1 位或 8/37/38/39/42 位十六进制、38 位非十六进制值时,积分均在约 0.3 秒返回 `200`。因此它是当前积分链路的键存在性兼容门槛,不是独立的 38 位协议值;项目在缺失时写入 SDK `h38` 的真实值,即 `_qimei_uuid42`,已有值继续保留。
+2
View File
@@ -81,6 +81,7 @@ qimeisdk-web 0.6.3 源码已定位:
| `__yamid_new` | 浏览器脚本(CBCC 前缀+种子)| 高(与 rootsid 同源)| `room_normal.js` `generate32` 已复刻;精确 rootsid 共享计数仍待确认 |
| `_qimei_uuid42` | QIMEI SDK 0.6.3 VM 生成器 `e=6064` | 中 | Python 已按时间编码、CRC32 和随机半字节逐项复刻 |
| `_qimei_fingerprint` | QIMEI SDK 0.6.3 x64hash128(feature_string, 31) | 中 | Python 已复刻,输入字段见路线状态文档 |
| `_qimei_h38` | 当前页面不写入同名 Cookie;SDK 内部 `h38` = UUID42 | 低 | 积分链路仅检查键存在,缺失时复用 `_qimei_uuid42` |
| `game_did` | 浏览器脚本 | 中 | 长度需对齐(35 变体含 `-`)|
| 统计字段(SoundValue 等)| 页面脚本 | 低 | 随机 OK |
@@ -127,6 +128,7 @@ B = (t=35) => { const e="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012
随后计算模板 ASCII 字节的标准 CRC32,将 8 位小写结果的前 4 位和后 4 位分别填入占位,最后调用 `toLocaleLowerCase()`。例如固定每次 `random=0.25` 时,模板是 `17b0f060d14100----44444444444444444444----`,替换值是 `037f37df`,结果为 `17b0f060d14100037f4444444444444444444437df`;递增序列探针则得到随机段 `0123456789abcdef0123`,证明 SDK 会独立抽样 20 次。`navigator.cookieEnabled` 只影响时间前缀末三位(`100`/`000`)。
- 由 qimeisdk-web 0.6.3 内部 VM 生成(`_qimei_ssid`/`_qimei_last_get_id_time_<16hex>` 同源);字段本身为 42 位小写十六进制。
- Python 实现按 SDK 时间、CRC32 和随机上界复刻,固定向量覆盖 `0`、`.25`、`.5`、`.75`、`.999`。
- SDK 对该内部值使用 `h38` 属性名,但它的长度是 42;`_qimei_h38` 只是活动积分链路的 Cookie 兼容键,不存在另一套 38 位 SDK 生成算法。
### ✅ _qimei_fingerprint = x64hash128(feature_string,31),已完成 Python 复刻
- Cookie 输入由屏幕分辨率、浏览器能力、WebGL 和时区字段按 `###` 拼接。
+16
View File
@@ -44,9 +44,25 @@ def test_fill_replaces_empty_values_and_completes_core_fields():
assert fields["__yamid_new"]
assert fields["game_did"]
assert fields["_qimei_fingerprint"] == qimei_fingerprint_generate()
assert fields["_qimei_h38"] == fields["_qimei_uuid42"]
assert not missing_web_cookie_fields(result)
def test_qimei_h38_aliases_uuid42_without_overwriting_existing_value():
uuid42 = "1" * 42
fields = cookie_fields(
fill_web_cookie_fields(f"_qimei_uuid42={uuid42}; _qimei_h38=", "")
)
assert fields["_qimei_h38"] == uuid42
fields = cookie_fields(
fill_web_cookie_fields(
f"_qimei_uuid42={uuid42}; _qimei_h38=historical", ""
)
)
assert fields["_qimei_h38"] == "historical"
def test_qimei_uuid42_matches_sdk_vectors_and_random_upper_bound():
base = 1700000000000000000
assert _qimei_uuid42_generate(base, random_value=0.25) == (