web_cookie_fields 用逆向算法替换随机:__yamid_new=UUIDv1时间戳复刻、game_did=35位63字符集、_yasids同源、_qimei_uuid42=1a前缀、guid回退加固定段
This commit is contained in:
@@ -17,8 +17,8 @@ status=200(和平精英已绑定)、getUserScore 返回 status=200(积分
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
import time
|
||||
|
||||
# 统计/会话字段生成规则;9.1 抓包值为样例,格式保持对齐
|
||||
@@ -30,6 +30,71 @@ _FILL_MAP: tuple[tuple[str, str], ...] = (
|
||||
("udb_accdata", "undefined"),
|
||||
)
|
||||
|
||||
# 浏览器实测逆向(2026-09-01,见 docs/HUYA_WEB_FIELD_TRACE.md):
|
||||
# - __yamid_new: room_normal.js generate32(UUID-v1 风格,1582-epoch 本地时区时间戳
|
||||
# + v1 版本位 + 时钟序列 + 48 位随机 node)。浏览器 epoch = new Date(1582,10,15,
|
||||
# 0,0,0,0).getTime() 实测 -12216643543000(GMT+8)。CBCC 前缀是时间戳自然结果。
|
||||
# - game_did: vplayerUI.js 35 位随机,字符集 64 去掉末尾 '_'(63 选 1)。
|
||||
# - _qimei_uuid42: QIMEI SDK 生成,cookie 值首字节固定 '1a'(本地存储 44hex 截断)。
|
||||
|
||||
_SDK_EPOCH_1582 = -12216643543000 # 浏览器实测 JS Date(1582,10,15) 本地时区 ms
|
||||
_BASE16 = "0123456789ABCDEF"
|
||||
_GAME_DID_CHARS = (
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
|
||||
)
|
||||
|
||||
|
||||
def _return_base16(a: int) -> str:
|
||||
"""JS returnBase(a,16):正整数转大写十六进制;a<=0 返回空串。"""
|
||||
if a <= 0:
|
||||
return ""
|
||||
out = ""
|
||||
while a > 0:
|
||||
out = _BASE16[a % 16] + out
|
||||
a //= 16
|
||||
return out
|
||||
|
||||
|
||||
def _generate_bits(a: int, b: int, c: int) -> str:
|
||||
"""JS generateBits(a,b,c):取 a 的 16 进制串 [floor(b/4)..floor(c/4)] 字符,越界补0。"""
|
||||
d = _return_base16(a)
|
||||
out = ""
|
||||
for g in range(b // 4, c // 4 + 1):
|
||||
out += d[g] if g < len(d) and d[g] != "" else "0"
|
||||
return out
|
||||
|
||||
|
||||
def _yamid_new_generate32(rng=None) -> str:
|
||||
"""逐字复刻 room_normal.js generate32(__yamid_new 生成器)。"""
|
||||
import random
|
||||
|
||||
r = rng if rng is not None else random.Random()
|
||||
now_ms = int(time.time() * 1000)
|
||||
c = now_ms - _SDK_EPOCH_1582
|
||||
return "".join((
|
||||
_generate_bits(c, 0, 31),
|
||||
_generate_bits(c, 32, 47),
|
||||
_generate_bits(c, 48, 59) + "1",
|
||||
_generate_bits(r.randrange(4096), 0, 7),
|
||||
_generate_bits(r.randrange(4096), 0, 7),
|
||||
_generate_bits(r.randrange(8192), 0, 7)
|
||||
+ _generate_bits(r.randrange(8192), 8, 15)
|
||||
+ _generate_bits(r.randrange(8192), 0, 7)
|
||||
+ _generate_bits(r.randrange(8192), 8, 15)
|
||||
+ _generate_bits(r.randrange(8192), 0, 15),
|
||||
))
|
||||
|
||||
|
||||
def _game_did_generate(length: int = 35) -> str:
|
||||
"""逐字复刻 vplayerUI.js game_did:35 位随机,字符集 64 去掉末尾 '_'。"""
|
||||
import random
|
||||
|
||||
out = ""
|
||||
for _ in range(length):
|
||||
idx = int(random.random() * (len(_GAME_DID_CHARS) - 1)) # 63 → 排除 '_'
|
||||
out += _GAME_DID_CHARS[idx]
|
||||
return out
|
||||
|
||||
|
||||
def _rand_float() -> str:
|
||||
import random
|
||||
@@ -78,15 +143,17 @@ def fill_web_cookie_fields(
|
||||
else:
|
||||
add("guid", str(guid))
|
||||
elif "guid" not in pairs:
|
||||
add("guid", secrets.token_hex(16))
|
||||
add("_qimei_uuid42", secrets.token_hex(21)) # 42 hex
|
||||
add("__yamid_new", secrets.token_hex(16).upper()) # 32 hex 大写
|
||||
alnum = string.ascii_letters + string.digits
|
||||
import random
|
||||
|
||||
add("game_did", "".join(random.choices(alnum, k=30))) # 30 位字母数字
|
||||
# guid 结构: '0a' + 10hex + '956a'/'966a' + 16hex(浏览器实测固定段,见 trace 文档)
|
||||
add("guid", "0a" + secrets.token_hex(5) + "966a" + secrets.token_hex(8))
|
||||
# _qimei_uuid42: QIMEI SDK 产物,cookie 首字节 '1a' + UUID 布局 32hex + 8hex 附加
|
||||
add("_qimei_uuid42", "1a" + secrets.token_hex(20))
|
||||
# __yamid_new: room_normal.js generate32 精确复刻(UUID-v1 时间戳,含 CBCC 前缀规律)
|
||||
add("__yamid_new", _yamid_new_generate32())
|
||||
add("game_did", _game_did_generate()) # 35 位随机(真实长度)
|
||||
|
||||
# qimei 皮肤字段
|
||||
# _qimei_fingerprint: QIMEI SDK SpookyHashV2(canvas指纹,31) 32hex;设备稳定,
|
||||
# 无真实 canvas 时可随机(格式 32hex 对齐),真实算法见 trace 文档
|
||||
add("_qimei_fingerprint", secrets.token_hex(16))
|
||||
add("_qimei_h38", secrets.token_hex(19))
|
||||
|
||||
@@ -99,7 +166,8 @@ def fill_web_cookie_fields(
|
||||
add("h_unt", str(int(time.time())))
|
||||
if uid:
|
||||
add("__yaoldyyuid", str(uid))
|
||||
add("_yasids", "__rootsid%3D" + secrets.token_hex(16).upper())
|
||||
# _yasids 与 __yamid_new 同源(共享时间戳生成器,浏览器实测前缀一致)
|
||||
add("_yasids", "__rootsid%3D" + _yamid_new_generate32())
|
||||
add("Hm_lvt_51700b6c722f5bb4cf39906a596ea41f", str(int(time.time())))
|
||||
add("Hm_lpvt_51700b6c722f5bb4cf39906a596ea41f", str(int(time.time())))
|
||||
add("HMACCOUNT", secrets.token_hex(8).upper())
|
||||
|
||||
@@ -85,4 +85,48 @@ qimeisdk-web 0.6.3 源码已定位:
|
||||
1. `_qimei_fingerprint` 直接用 SpookyHashV2 实现(canvas 指纹用标准值替代)
|
||||
2. `guid` 采样更多样本确认固定段后按 `0a` + 结构仿造
|
||||
3. `__yamid_new` 需找到生成它的具体脚本(非 document.cookie 写入,可能在
|
||||
ubtk/埋点脚本里)做种子同步
|
||||
ubtk/埋点脚本里)做种子同步
|
||||
## 六、核心字段生成算法逆向结果(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 结构已定,算法待深挖
|
||||
- cookie 值:首字节 `1a` 固定 + 40hex(localStorage 是 44hex,cookie 为截断子串)
|
||||
- 由 qimeisdk-web 0.6.3 内部 VM 生成(`_qimei_ssid`/`_qimei_last_get_id_time_<16hex>` 同源)
|
||||
- 当前实现对齐结构(1a 前缀 + 42hex 随机化),精确算法待继续
|
||||
|
||||
### ⚠️ _qimei_fingerprint = SpookyHashV2(canvas指纹,31),待 canvas 复现
|
||||
- SDK 0.6.3 内置 x64hash128 与 canvas 指纹逻辑(文本+三圆+环形)
|
||||
- 设备稳定(同 profile 跨会话不变);当前实现随机 32hex 格式对齐
|
||||
- 复现需在真实环境执行 canvas(headless 下 canvas 指纹固定,可抽取一次固化为常量)
|
||||
|
||||
### 已修正生成器(core/huya/web_cookie_fields.py, commit 待定)
|
||||
- `__yamid_new` → `_yamid_new_generate32()`(UUID-v1 时间戳复刻)
|
||||
- `game_did` → `_game_did_generate()`(35 位 63 字符集)
|
||||
- `_yasids` → 与 `__yamid_new` 同源同刻生成
|
||||
- `_qimei_uuid42` → `1a` 前缀 + 42hex
|
||||
- `guid`(无会话值回退)→ `0a` + 10hex + `966a` + 16hex 结构
|
||||
|
||||
Reference in New Issue
Block a user