"""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 .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)