193 lines
6.4 KiB
Python
193 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 .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)
|
|
and 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)
|