From 8fef04d0af67b7043e680770d27dfedd0f97c1e7 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 1 Sep 2026 23:48:00 +0800 Subject: [PATCH] =?UTF-8?q?web=5Fcookie=5Ffields=20=E7=94=A8=E9=80=86?= =?UTF-8?q?=E5=90=91=E7=AE=97=E6=B3=95=E6=9B=BF=E6=8D=A2=E9=9A=8F=E6=9C=BA?= =?UTF-8?q?=EF=BC=9A=5F=5Fyamid=5Fnew=3DUUIDv1=E6=97=B6=E9=97=B4=E6=88=B3?= =?UTF-8?q?=E5=A4=8D=E5=88=BB=E3=80=81game=5Fdid=3D35=E4=BD=8D63=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E9=9B=86=E3=80=81=5Fyasids=E5=90=8C=E6=BA=90=E3=80=81?= =?UTF-8?q?=5Fqimei=5Fuuid42=3D1a=E5=89=8D=E7=BC=80=E3=80=81guid=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E5=8A=A0=E5=9B=BA=E5=AE=9A=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/huya/web_cookie_fields.py | 86 ++++++++++++++++++++++++++++++---- docs/HUYA_WEB_FIELD_TRACE.md | 46 +++++++++++++++++- 2 files changed, 122 insertions(+), 10 deletions(-) diff --git a/core/huya/web_cookie_fields.py b/core/huya/web_cookie_fields.py index b2bf57a..c8fdae3 100644 --- a/core/huya/web_cookie_fields.py +++ b/core/huya/web_cookie_fields.py @@ -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()) diff --git a/docs/HUYA_WEB_FIELD_TRACE.md b/docs/HUYA_WEB_FIELD_TRACE.md index 5b5cf07..3254ede 100644 --- a/docs/HUYA_WEB_FIELD_TRACE.md +++ b/docs/HUYA_WEB_FIELD_TRACE.md @@ -85,4 +85,48 @@ qimeisdk-web 0.6.3 源码已定位: 1. `_qimei_fingerprint` 直接用 SpookyHashV2 实现(canvas 指纹用标准值替代) 2. `guid` 采样更多样本确认固定段后按 `0a` + 结构仿造 3. `__yamid_new` 需找到生成它的具体脚本(非 document.cookie 写入,可能在 - ubtk/埋点脚本里)做种子同步 \ No newline at end of file + 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` 同源) +- 当前实现对齐结构(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 结构