Files
live-hub-py/docs/HUYA_WEB_FIELD_TRACE.md
T

163 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 虎牙网页设备字段溯源(历史证据,2026-09-01 浏览器实测)
> 当前实现状态以 [HUYA_COOKIE路线状态.md](HUYA_COOKIE路线状态.md) 为准;本文保留原始浏览器证据与算法推导。
## 一、来源判定方法(有头 Chrome + 打点探针)
- `Page.addScriptToEvaluateOnNewDocument` 拦截 `Document.prototype.cookie` setter,
记录每条写入的 值/时间/调用栈(栈中含脚本 URL + 行号)。
- `Network.responseReceivedExtraInfo` 捕获服务端 **Set-Cookie** 头。
- 全新 profile,导航 www.huya.com + 直播间页,静置后导出。
## 二、决定性结论:哪些字段服务端下发,哪些浏览器生成
### ✅ 服务端 Set-Cookie 下发(仅 5 个)
| 字段 | 实测 |
| --- | --- |
| `PHPSESSID` | l.web.huya.com / l.huya.com |
| `udb_deviceid` | udblgn.huya.com(`w_` + 19 位数字,匿名设备) |
| `sdid` | udblgn.huya.com(`0UnHUgv0_qmfD4KA...` 设备指纹) |
| `v` | 页面版本 |
| `HMACCOUNT` | 百度统计 |
### ✅ 浏览器脚本 document.cookie 写入(铁证:调用栈)
| 字段 | 生成脚本 | 关键位置 |
| --- | --- | --- |
| `guid` | `a.msstatic.com/huya/main3/npm..pnpm-d84ecac2_850fe68b.js` `Object.setCookie` | 写入 5 次;**位置 12-16 固定 `966a`(跨会话一致)** |
| `_qimei_fingerprint` | `staticfile.qq.com/qimei/qimeisdk-web/0.6.3/index.js` | 写入 1 次 |
| `udb_passdata` | `a.msstatic.com/huya/main3/common/headerfunction_d8b3ad0c.js` `writeCookie` | 固定值 3 |
| `__yasmid` / `__yamid_tt1` | 页面脚本 | 同值 17 位小数 |
### ⚠️ 浏览器生成但**非 document.cookie 写入**(走 Cookie Store API / localStorage 同步等)
| 字段 | 证据 | 说明 |
| --- | --- | --- |
| `__yamid_new` | 88 条写入中无此 key,但最终 cookie 有值 | **CBCC 前缀 + 递增种子**(见下) |
| `game_did` | 同上 | 30-35 位字母数字 |
| `_qimei_uuid42` | 同上 | 14 位时间前缀 + 8 位 CRC32 替换值 + 20 位随机半字节 + 4 位 CRC32 尾段 |
## 三、设备字段结构规律(真实样本)
### guid = `0a` + 10hex + `956a`/`966a` + 16hex —— 有固定段,非纯随机
```
0a7dfa5fc9ee966aab02a2eaa3ea1de1 ← live 窗口
0a7dfaa2d7af966a2101f370f470ce50 ← round2 fresh
0a7de265986f956a24015b505c504506 ← reqable 8.31
0a89d5f6614b956aa6020bbc0cbc18fe ← cahrles 8.31
```
位置 12-16 恒为 `956a` / `966a`(产品段?),8.31 为 `956a`、我们机器为 `966a` ——
可能与 SDK/页面版本有关。
### __yamid_new = `CBCC`/`CBCB` + 种子6hex + 计数4hex + `0001` + 随机12hex
```
CBCC 6479D9 B000 0001 52EC19001D2017B2 ← live 窗口
CBCC 551C11 E000 0001 783D8F5530901BCC ← round2 fresh
CBCC 06ECB5 0000 0001 B63016D6162BC990 ← reqable 8.31
```
- 前缀 `CBCC`/`CBCB` 恒定(产品标识)
- 种子段递增且与 `_yasids`(rootsid)**同源共享时钟**:
`yamid=CBCC6479D9B0...` ↔ `rootsid=CBCC647A8A20...`(同会话,种子差 0xB1≈177)
- `0001` 固定,尾部大段随机
### _qimei_uuid42 = 14 位时间前缀 + CRC32 替换段 + 20 位随机半字节 + CRC32 尾段
```
17b0f060d14100037f4444444444444444444437df ← 固定时间/随机 .25
17b0f060d1410053db888888888888888888883ff6 ← 固定时间/随机 .5
17b0f060d141003174ffffffffffffffffffffd85d ← 固定时间/随机 .999
```
时间前缀的编码为:年份末两位十六进制(补两位)+ 月份十六进制 + 日/时/分/秒各两位十六进制 + `cookieEnabled` 标志(`100`/`000`)。模板 `prefix + "----" + random_nibbles + "----"` 的标准 CRC32 小写结果会拆成前后四位填回两个占位;`random_nibbles` 由 20 次独立的 `floor(Math.random() * 16)` 依次拼接,因此每位合法范围为 `0` 到 `f`。
### _qimei_fingerprint = x64hash128(feature_string, 31) 32hex
qimeisdk-web 0.6.3 源码已定位:
- 画 "qimei" 文本(Times New Roman 11pt)+ `#f60` 背景 + 透明度矩形
- 三圆叠加(`#f2f`/`#2ff`/`#ff2` 40 半径)+ 环形 `#f9c` evenodd
- Canvas `id`/`geometry` 是 SDK 特征对象的独立字段;Cookie 指纹实际使用浏览器能力特征串
→ SDK 内置 **x64hash128**(seed=31)→ 32hex。
## 四、溯源结论与建议
| 字段 | 来源 | 风控关联性 | 建议 |
| --- | --- | --- | --- |
| `guid` | 活动 `wsLaunch` 响应 `sGuid`;页面 SDK 负责持久化 | 高(设备与会话绑定) | **优先复用当前 wsLaunch 会话值**;`956a/966a/976a` 等中间段随服务端返回变化,不能固化为版本常量 |
| `__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 |
**当时的下一步记录(部分已完成,保留作历史)**:
1. `_qimei_fingerprint` 已改为浏览器能力特征串的 `x64hash128`,见路线状态文档。
2. `guid` 已统一使用 `wsLaunch` 会话值;活动 AUTH、getConfig 与后续业务 Cookie 均复用同一返回值。
3. `__yamid_new` 已定位 `room_normal.js` `generate32` 并完成 Python 复刻。
## 六、核心字段生成算法逆向结果(2026-09-01 晚,浏览器实测 + 源码定位)
### ✅ __yamid_new = room_normal.js generate32(UUID-v1 风格,已 Python 复刻验证)
```js
// a.msstatic.com/huya/main3/app/room_normal_55c7cc4f.js
function u(e){var t="__yamid_new",n=getCookieVal(t);if(n)return n;n=e||d.generate32();...writeCookie(t,n,...)}
d.generate32(){
c = new Date().getTime() - new Date(1582,10,15,0,0,0,0).getTime(); // 本地时区 epoch
return generateBits(c,0,31) // time_low 8hex
+ generateBits(c,32,47) // time_mid 4hex
+ generateBits(c,48,59)+"1" // time_hi+版本位
+ generateBits(rand(4095),0,7) + generateBits(rand(4095),0,7)
+ generateBits(rand(8191),0,7)+... 48位随机node;
}
```
- 浏览器实测 epoch(GMT+8): `-12216643543000`;Python 复刻 12hex 时间戳段与浏览器**逐位一致**
- **`CBCC` 前缀是时间戳自然结果**(1582-epoch 当前值的高位),非固定魔数;随生成时刻缓慢递增
- `_yasids`(rootsid)同源同刻生成(浏览器实测 yamid=`CBCC653E4B...` / rootsid=`CBCC647A8A...` 共享种子时钟)
### ✅ game_did = vplayerUI.js 35 位随机(已复刻)
```js
// a.msstatic.com/huya/h5player/room/2608311423/vplayerUI.js
B = (t=35) => { const e="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
for(i="";i.length<t;) i += e.charAt(parseInt(Math.random()*(e.length-1))); return i }
```
- **长度 35**(旧随机 30 位是错的);字符集 64 去掉末尾 `_`(63 选 1)
- cookie 名 `game_did`,无 cookie 时生成并写入
### ✅ _qimei_uuid42 = QIMEI SDK VM 生成器(已 Python 复刻)
清理 localStorage/cookie 后对 QIMEI SDK 0.6.3 做固定时间、固定随机值追踪,定位到 VM 生成器 `e=6064`。它先拼出:
```text
14 位时间前缀 + "----" + 20 次独立抽样组成的随机半字节串 + "----"
```
随后计算模板 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 和时区字段按 `###` 拼接。
- Python 实现位于 `core/huya/web_cookie_fields.py`,测试向量与 SDK 输出一致。
- 没有实时浏览器能力对象时,生成器使用稳定的默认 Web 特征档案。
### 已修正生成器(core/huya/web_cookie_fields.py)
- `__yamid_new` → `_yamid_new_generate32()`(UUID-v1 时间戳复刻)
- `game_did` → `_game_did_generate()`(35 位 63 字符集)
- `_yasids` → 与 `__yamid_new` 同源同刻生成
- `_qimei_uuid42` → `_qimei_uuid42_generate()`(14 位时间前缀 + CRC32 替换段 + 20 位随机半字节 + CRC32 尾段)
- `guid`(无会话值回退)→ `0a` + 10hex + `966a` + 16hex 结构
## 七、逆向生成器端到端验证(2026-09-01 晚,账号 18397482576 重新登录后)
新版生成器(web_cookie_fields.py commit 8fef04d)产出字段:
- guid=`0a43b01a366c966a7fe26a281e1d3c88`(0a 前缀 + 966a 固定段)
- __yamid_new=`CBCC65D6C5200001F176114BCF181E25`(UUIDv1 时间戳)
- _qimei_uuid42=42 位小写十六进制(SDK VM 时间/CRC32 结构)
- game_did=35 位
- _yasids 与 yamid 同源(CBCC65D6C5 前缀一致)
webActUI 全链路 status=200:
1. checkUserBindGameAccount → 和平精英/痛仰之吻
2. getUserScore → gain=5580 available=280
3. getActPrizeList → 29 商品
4. **scoreExchangePrize → status=200 真实兑换成功**
(荣耀勋章礼包(小)20 分,订单 sOrderId_2203_12855_1199667330433_...)
5. getUserPrizeRecords → 4 条
↑ 之前 getConfig 超时是旧 cookie 过期(id58 已清空),非生成器问题。