Files
live-hub-py/services/yyb-worker/runtime/pyvm/mall.py
T

190 lines
6.4 KiB
Python

"""mall 侧(PlaceOrder,pagedoo CHAOS VM 108 opcode)高层 API。
与 goods 侧(pyvm/goods.py)同属 YYB 加密体系:
- goods: web_save / web_new_encrypt(midas.gtimg.cn/goodsBiz,116 opcode)
- mall: PlaceOrder(pagedoo.pay.qq.com,108 opcode,详情页 e377650 变换核心)
mall 加密链路(E3 已验证,U-2022 闭环):
会话态(xMidasOps 59620 + key16/d/Te + 624B 中间态)
→ e377650 变换核心(正确回调链 e344354=[624B中间态, d, e336201, key16])
→ 624B 密文 = encrypt_msg(1248 hex)
注意:xMidasOps 是 mall 详情页页面级数据表(59620 长度,服务端生成),
必须从浏览器捕获(与 goods 的 59640 不同)。
"""
from __future__ import annotations
import copy
import json
import re
from pathlib import Path
from typing import Any
from .algorithm import UNDEF, JSObject, Window
from .pagedoo_vm import PagedooVM
REPLAY = Path(__file__).resolve().parent.parent / "replay" / "mall"
GLOBALS = [
UNDEF,
None,
True,
False,
4294967295,
3995986053,
2103143698,
1622111212,
4263108271,
3162892160,
1960464030,
2867129963,
3224029870,
3514649446,
1382846327,
1898428403,
1268470028,
1457769175,
1595352606,
1100935262,
]
class MallSession:
"""mall 会话态:e377650 变换核心的完整输入。
transform_input: e377650 创建参数(18 槽)——从浏览器 deepCap/J 转储提取
[0]=key16(16B) [1..5]=Te/S-box [6]=d(16B) [7]=[211] [8]=S-box2
[9]=outbuf [10]=624B中间态(a:8输出) [11..16]=1024/256表 [17]=回调
xmidas_ops: mall 详情页 xMidasOps(59620,服务端生成,页面级)
"""
def __init__(self, transform_input: list, xmidas_ops: list):
self.transform_input = transform_input
self.xmidas_ops = xmidas_ops
self.validate()
def validate(self) -> None:
if len(self.transform_input) != 18:
raise ValueError(
f"transform_input 应为 18 槽,实际 {len(self.transform_input)}"
)
if len(self.xmidas_ops) != 59620:
raise ValueError(
f"mall xMidasOps 应为 59620(非 goods 59640),实际 {len(self.xmidas_ops)}"
)
mid = self.transform_input[10]
if isinstance(mid, list) and mid and isinstance(mid[0], list):
if len(mid[0]) != 624:
raise ValueError(f"624B 中间态长度 != 624: {len(mid[0])}")
@classmethod
def from_capture_file(cls, frames_jsonl: str | Path) -> "MallSession":
"""从捕获的 frames.jsonl 提取同会话 transform_input + xMidasOps。
frames.jsonl 由 scripts/capture-mall-session.mjs 实时落盘:
- J|...|e377650|{JSON} e377650 创建参数(transform_input)
- XMIDAS_OPS|url|59620数组 mall 详情页 xMidasOps
"""
lines = (
Path(frames_jsonl)
.read_text(encoding="utf-8", errors="replace")
.splitlines()
)
transform_input = None
xmidas = None
# xMidasOps:取 mall 详情页(z.iwan / pagedoo)那条
for l in lines:
if l.startswith("XMIDAS_OPS"):
parts = l.split("|")
if len(parts) >= 3 and ("z.iwan" in parts[1] or "pagedoo" in parts[1]):
xmidas = [int(x) for x in parts[2].split(",")]
break
if xmidas is None:
# 回退:任意 59620 长度
for l in lines:
if l.startswith("XMIDAS_OPS"):
parts = l.split("|")
vals = [int(x) for x in parts[2].split(",")]
if len(vals) == 59620:
xmidas = vals
break
for l in lines:
if l.startswith("J|") and re.search(r"\|e377650\|", l):
transform_input = json.loads(l.split("|e377650|", 1)[1])
break
if transform_input is None:
raise ValueError(
"frames.jsonl 中未找到 e377650 J 转储(需 mall 详情页购买触发加密)"
)
if xmidas is None:
raise ValueError("frames.jsonl 中未找到 59620 长度 xMidasOps")
return cls(transform_input, xmidas)
def to_json(self) -> dict:
return {
"transform_input": self.transform_input,
"xmidas_ops": self.xmidas_ops,
}
@classmethod
def from_json(cls, d: dict) -> "MallSession":
return cls(d["transform_input"], d["xmidas_ops"])
def _mk_window(xmidas: list) -> Window:
w = Window()
for k in ("window", "self", "globalThis", "top", "parent", "frames"):
w.set(k, w)
w.set("document", JSObject())
w.set("navigator", JSObject())
w.set("location", JSObject())
w.set("localStorage", JSObject())
w.set("sessionStorage", JSObject())
w.set("screen", JSObject())
w.set("history", JSObject())
w.set(
"XMLHttpRequest",
type(
"XHR",
(),
{
"open": lambda *a: None,
"send": lambda *a: None,
"setRequestHeader": lambda *a: None,
},
),
)
w.set("fetch", lambda *a: None)
w.set("xMidasOps", xmidas)
return w
def generate_encrypt_msg(session: MallSession, random_seed: int = 1) -> str:
"""用会话态重放 e377650 变换核心,产出 encrypt_msg(1248 hex)。
E3 验证:同会话 transform_input + xMidasOps(59620) → 1248 hex 完全一致。
"""
bytecode = json.loads((REPLAY / "vm" / "bytecode-478657.json").read_text())
w = _mk_window(session.xmidas_ops)
vm = PagedooVM(bytecode, GLOBALS, w, random_seed=random_seed)
vm._root_this = w
for k, v in vm._hosts.items():
if k != "Array.prototype" and w.get(k) is UNDEF:
w.set(k, v)
h = copy.deepcopy(session.transform_input)
frame_rand = vm.make(336201, [], w, GLOBALS, None)
# 正确回调链:e344354 = [624B中间态, d, e336201随机帧, key16]
h344 = [h[10], h[6], [frame_rand], h[0]]
frame344 = vm.make(344354, h344, w, GLOBALS, None)
h[17] = [frame344]
frame = vm.make(377650, h, w, GLOBALS, None)
vm.run(frame, [])
h9 = h[9][0] if isinstance(h[9], list) and h[9] else h[9]
if not isinstance(h9, list) or len(h9) != 624:
raise RuntimeError(
f"e377650 输出异常: {type(h9).__name__} len={len(h9) if isinstance(h9, list) else '?'}"
)
return "".join(f"{x & 255:02x}" for x in h9)