2181 lines
74 KiB
Python
2181 lines
74 KiB
Python
"""encrypt_msg (web_new_encrypt) 生成器 — goodsBiz CHAOS VM 的纯 Python 移植
|
||
|
||
算法背景(见 FINDINGS.md F-2038..F-2043,破解见 F-2052/2026-08-12):
|
||
- goodsBiz.js 是腾讯 __TENCENT_CHAOS_VM 解释器(116 opcode,0..115)+ 178093 元素字节码。
|
||
- 加密核心 = webSave h@85091(18 参):表生成 → 检查 window.xMidasOps → 33 次块变换
|
||
(T 表 AES 类)→ 回调 h@69667(4 参)→ randGen h@85173(16 次重随机 key2)。
|
||
- 输入:18 参(key1 诱饵 / key2 真实 / args[10]=16 字节对齐的明文缓冲 / 常量表 / 回调)。
|
||
- key 派生已破解:key16 = Sbox[Te 链(key1)];服务端校验两者派生一致性,
|
||
离线用随机 key16 + derive_key1_from_key16 反解 key1 即可通过(无需页面捕获 key)。
|
||
- 输出:C[3476] 528 字节密文(hex 1056 字符,头部 5574bea9... 固定前缀)。
|
||
|
||
本文件复刻解释器语义,可在无浏览器环境运行;两组独立真实向量均 1056/1056 逐字节一致。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import itertools
|
||
import json
|
||
import math
|
||
import urllib.parse
|
||
from pathlib import Path
|
||
|
||
REPLAY = Path(__file__).resolve().parent.parent / "replay"
|
||
|
||
# ---------------------------------------------------------------- JS 语义辅助
|
||
|
||
M32 = 0xFFFFFFFF
|
||
|
||
|
||
def _ic(x):
|
||
# JS ToInt32 操作数强转:undefined/null/NaN->0,浮点截断,字符串解析
|
||
if x is UNDEF or x is None:
|
||
return 0
|
||
if isinstance(x, bool):
|
||
return 1 if x else 0
|
||
if isinstance(x, int):
|
||
return x
|
||
if isinstance(x, float):
|
||
return 0 if math.isnan(x) else int(x)
|
||
try:
|
||
f = float(x)
|
||
return 0 if math.isnan(f) else int(f)
|
||
except Exception:
|
||
return 0
|
||
|
||
|
||
def to_i32(x):
|
||
return _ic(x) & M32
|
||
|
||
|
||
def i32(x):
|
||
"""JS 位运算结果:有符号 32 位。"""
|
||
v = _ic(x) & M32
|
||
return v - (1 << 32) if v & 0x80000000 else v
|
||
|
||
|
||
def u32(x):
|
||
return _ic(x) & M32
|
||
|
||
|
||
def ushr(x, n):
|
||
return u32(x) >> (_ic(n) & 31)
|
||
|
||
|
||
def shl(x, n):
|
||
return i32(u32(x) << (_ic(n) & 31))
|
||
|
||
|
||
def shr(x, n):
|
||
return i32(_ic(x) >> (_ic(n) & 31))
|
||
|
||
|
||
def js_typeof(v):
|
||
if v is None or v is UNDEF:
|
||
return "undefined"
|
||
if isinstance(v, bool):
|
||
return "boolean"
|
||
if isinstance(v, (int, float)):
|
||
return "number"
|
||
if isinstance(v, str):
|
||
return "string"
|
||
if isinstance(v, JSFunction) or isinstance(v, HostFunction):
|
||
return "function"
|
||
return "object"
|
||
|
||
|
||
def js_truthy(v):
|
||
if v is None or v is UNDEF:
|
||
return False
|
||
if isinstance(v, bool):
|
||
return v
|
||
if isinstance(v, (int, float)):
|
||
return v != 0 and not (isinstance(v, float) and math.isnan(v))
|
||
if isinstance(v, str):
|
||
return len(v) > 0
|
||
return True
|
||
|
||
|
||
def _nan_ok(x):
|
||
try:
|
||
return not (isinstance(x, float) and math.isnan(x))
|
||
except Exception:
|
||
return True
|
||
|
||
|
||
def _cmp_lt(a, b):
|
||
x, y = js_num(a), js_num(b)
|
||
return x < y if (_nan_ok(x) and _nan_ok(y)) else False
|
||
|
||
|
||
def _cmp_le(a, b):
|
||
x, y = js_num(a), js_num(b)
|
||
return x <= y if (_nan_ok(x) and _nan_ok(y)) else False
|
||
|
||
|
||
def _cmp_gt(a, b):
|
||
x, y = js_num(a), js_num(b)
|
||
return x > y if (_nan_ok(x) and _nan_ok(y)) else False
|
||
|
||
|
||
def _cmp_ge(a, b):
|
||
x, y = js_num(a), js_num(b)
|
||
return x >= y if (_nan_ok(x) and _nan_ok(y)) else False
|
||
|
||
|
||
class _Undef:
|
||
def __repr__(self):
|
||
return "undefined"
|
||
|
||
def __bool__(self):
|
||
return False
|
||
|
||
def __eq__(self, other):
|
||
return other is UNDEF or other is None
|
||
|
||
def __hash__(self):
|
||
return hash("__undef__")
|
||
|
||
def __int__(self):
|
||
return 0
|
||
|
||
def __index__(self):
|
||
return 0
|
||
|
||
def __xor__(self, o):
|
||
return 0 ^ int(o)
|
||
|
||
def __rxor__(self, o):
|
||
return int(o) ^ 0
|
||
|
||
def __and__(self, o):
|
||
return 0 & int(o)
|
||
|
||
def __rand__(self, o):
|
||
return int(o) & 0
|
||
|
||
def __or__(self, o):
|
||
return 0 | int(o)
|
||
|
||
def __ror__(self, o):
|
||
return int(o) | 0
|
||
|
||
def __lshift__(self, o):
|
||
return 0 << int(o)
|
||
|
||
def __rlshift__(self, o):
|
||
return int(o) << 0
|
||
|
||
def __rshift__(self, o):
|
||
return 0 >> int(o)
|
||
|
||
def __rrshift__(self, o):
|
||
return int(o) >> 0
|
||
|
||
|
||
UNDEF = _Undef()
|
||
|
||
|
||
def js_str(v):
|
||
if v is UNDEF or v is None:
|
||
return "undefined"
|
||
if isinstance(v, bool):
|
||
return "true" if v else "false"
|
||
if isinstance(v, float) and v == int(v) and abs(v) < 1e15:
|
||
return str(int(v))
|
||
return str(v)
|
||
|
||
|
||
def js_add(a, b):
|
||
# JS + 运算符:仅当两侧均为 number 时数值相加,否则字符串拼接
|
||
if (
|
||
isinstance(a, (int, float))
|
||
and not isinstance(a, bool)
|
||
and isinstance(b, (int, float))
|
||
and not isinstance(b, bool)
|
||
):
|
||
return a + b
|
||
return js_str(a) + js_str(b)
|
||
|
||
|
||
def js_num(v):
|
||
if v is UNDEF or v is None:
|
||
return float("nan")
|
||
if isinstance(v, bool):
|
||
return 1 if v else 0
|
||
if isinstance(v, (int, float)):
|
||
return v
|
||
try:
|
||
return float(v)
|
||
except Exception:
|
||
return float("nan")
|
||
|
||
|
||
def js_eq(a, b):
|
||
"""JS 松散 ==(本 VM 用到的主要情形)。"""
|
||
if a is UNDEF or a is None:
|
||
return b is UNDEF or b is None
|
||
if b is UNDEF or b is None:
|
||
return False
|
||
if isinstance(a, bool) or isinstance(b, bool):
|
||
return js_truthy(a) == js_truthy(b)
|
||
if isinstance(a, (int, float)) and isinstance(b, (int, float)):
|
||
return a == b
|
||
if isinstance(a, str) and isinstance(b, str):
|
||
return a == b
|
||
if isinstance(a, (int, float)) and isinstance(b, str):
|
||
try:
|
||
return a == float(b)
|
||
except Exception:
|
||
return False
|
||
if isinstance(b, (int, float)) and isinstance(a, str):
|
||
try:
|
||
return float(a) == b
|
||
except Exception:
|
||
return False
|
||
return a == b
|
||
|
||
|
||
def js_streq(a, b):
|
||
return a is b or (
|
||
a is not UNDEF and b is not UNDEF and type(a) is type(b) and a == b
|
||
)
|
||
|
||
|
||
def js_index(obj, key):
|
||
"""obj[key](含数组越界/稀疏语义)。"""
|
||
if isinstance(key, float) and key.is_integer():
|
||
key = int(key)
|
||
if obj is None or obj is UNDEF:
|
||
raise TypeError(
|
||
"Cannot read properties of "
|
||
+ ("null" if obj is None else "undefined")
|
||
+ f" key={key!r} u={getattr(VM, '_last_u', None)}"
|
||
)
|
||
if isinstance(obj, str):
|
||
if isinstance(key, int):
|
||
if 0 <= key < len(obj):
|
||
return obj[key]
|
||
return UNDEF
|
||
if key == "length":
|
||
return len(obj)
|
||
if key in STRING_METHODS:
|
||
return STRING_METHODS[key]
|
||
if isinstance(key, str) and key.isdigit():
|
||
i = int(key)
|
||
if 0 <= i < len(obj):
|
||
return obj[i]
|
||
return UNDEF
|
||
if isinstance(obj, list):
|
||
if isinstance(key, int):
|
||
if 0 <= key < len(obj):
|
||
return obj[key]
|
||
return UNDEF
|
||
if key == "length":
|
||
return len(obj)
|
||
if isinstance(key, str) and key.lstrip("-").isdigit():
|
||
i = int(key)
|
||
if 0 <= i < len(obj):
|
||
return obj[i]
|
||
if key in (
|
||
"push",
|
||
"shift",
|
||
"join",
|
||
"slice",
|
||
"indexOf",
|
||
"concat",
|
||
"reverse",
|
||
"splice",
|
||
"map",
|
||
"forEach",
|
||
"pop",
|
||
"unshift",
|
||
):
|
||
return ARRAY_METHODS[key]
|
||
return UNDEF
|
||
if isinstance(obj, dict):
|
||
if key in obj:
|
||
return obj[key]
|
||
# 数字键
|
||
if isinstance(key, str) and key.lstrip("-").isdigit():
|
||
return obj.get(int(key), UNDEF)
|
||
return UNDEF
|
||
if isinstance(obj, JSObject):
|
||
return obj.get(key)
|
||
if isinstance(obj, Window):
|
||
return obj.get(key)
|
||
if isinstance(obj, (int, float)):
|
||
return UNDEF
|
||
# 通用宿主对象(可调用 + 属性,如 webpack loader)
|
||
try:
|
||
return getattr(obj, str(key))
|
||
except (AttributeError, TypeError):
|
||
return UNDEF
|
||
|
||
|
||
def js_set(obj, key, val):
|
||
if isinstance(key, float) and key.is_integer():
|
||
key = int(key)
|
||
if isinstance(obj, list):
|
||
if isinstance(key, int):
|
||
while len(obj) <= key:
|
||
obj.append(UNDEF)
|
||
obj[key] = val
|
||
return
|
||
if key == "length":
|
||
del obj[int(val) :]
|
||
return
|
||
if isinstance(key, str) and key.lstrip("-").isdigit():
|
||
i = int(key)
|
||
while len(obj) <= i:
|
||
obj.append(UNDEF)
|
||
obj[i] = val
|
||
return
|
||
raise TypeError("invalid array index " + str(key))
|
||
if isinstance(obj, dict):
|
||
obj[key] = val
|
||
return
|
||
if isinstance(obj, JSObject):
|
||
obj.set(key, val)
|
||
return
|
||
if isinstance(obj, Window):
|
||
obj.set(key, val)
|
||
return
|
||
# 通用宿主对象:支持 setattr(如 loader 的属性)
|
||
if hasattr(obj, "__dict__") and not isinstance(obj, (str, bytes, int, float)):
|
||
try:
|
||
setattr(obj, str(key), val)
|
||
return
|
||
except (AttributeError, TypeError):
|
||
pass
|
||
try:
|
||
obj[str(key)] = val
|
||
return
|
||
except (TypeError, KeyError, IndexError):
|
||
pass
|
||
if isinstance(obj, str):
|
||
raise TypeError("Cannot assign to read only property")
|
||
cur = getattr(
|
||
__import__("pyvm.algorithm", fromlist=["x"]).VM, "_last_u", None
|
||
)
|
||
raise TypeError(
|
||
f"invalid set target obj={type(obj).__name__} key={key!r} val={type(val).__name__} u={cur}"
|
||
)
|
||
|
||
|
||
def js_del(obj, key):
|
||
if isinstance(obj, list):
|
||
return False
|
||
if isinstance(obj, dict):
|
||
return obj.pop(key, None) is not None
|
||
if isinstance(obj, JSObject):
|
||
return obj.delete(key)
|
||
return False
|
||
|
||
|
||
def js_keys(obj):
|
||
if isinstance(obj, list):
|
||
return [str(i) for i in range(len(obj))]
|
||
if isinstance(obj, dict):
|
||
return [str(k) for k in obj]
|
||
if isinstance(obj, JSObject):
|
||
return list(obj.d.keys())
|
||
return []
|
||
|
||
|
||
class JSObject:
|
||
"""普通 JS 对象(可含任意键)。"""
|
||
|
||
def __init__(self):
|
||
self.d = {}
|
||
|
||
def get(self, key):
|
||
return self.d.get(key, UNDEF)
|
||
|
||
def set(self, key, val):
|
||
self.d[key] = val
|
||
|
||
def delete(self, key):
|
||
return self.d.pop(key, None) is not None
|
||
|
||
def __repr__(self):
|
||
return "JSObject(" + ",".join(str(k) for k in list(self.d)[:8]) + ")"
|
||
|
||
|
||
class Window:
|
||
def __init__(self):
|
||
self.d = {}
|
||
|
||
def get(self, key):
|
||
return self.d.get(key, UNDEF)
|
||
|
||
def set(self, key, val):
|
||
self.d[key] = val
|
||
|
||
def __repr__(self):
|
||
return "<window>"
|
||
|
||
|
||
class JSFunction:
|
||
"""VM 函数(解释器实例工厂返回的 h)。"""
|
||
|
||
__slots__ = ("args", "entry", "n", "name", "s", "t", "vm")
|
||
|
||
def __init__(self, vm, entry, args, s, n, t):
|
||
self.vm = vm
|
||
self.entry = entry
|
||
self.args = args
|
||
self.s = s
|
||
self.n = n
|
||
self.t = t
|
||
self.name = "h"
|
||
|
||
def __repr__(self):
|
||
return "f:h"
|
||
|
||
|
||
class HostFunction:
|
||
__slots__ = ("fn", "name")
|
||
|
||
def __init__(self, fn, name="anon"):
|
||
self.fn = fn
|
||
self.name = name
|
||
|
||
def __call__(self, this, *args):
|
||
return self.fn(this, *args)
|
||
|
||
def __repr__(self):
|
||
return "f:" + self.name
|
||
|
||
|
||
def js_call(fn, this, args):
|
||
"""fn.call(this, ...args) —— 兼容 VM 函数与宿主函数。"""
|
||
if isinstance(fn, JSFunction):
|
||
return fn.vm.run(fn, args)
|
||
if isinstance(fn, HostFunction):
|
||
return fn.fn(this, *args)
|
||
if callable(fn):
|
||
return fn(this, *args)
|
||
raise TypeError(f"{fn!s} is not a function u={getattr(VM, '_last_u', None)}")
|
||
|
||
|
||
def js_apply(fn, this, args):
|
||
return js_call(fn, this, args)
|
||
|
||
|
||
# ---------------------------------------------------------------- 宿主函数
|
||
|
||
|
||
def h_to_charcode(this, s, i=None):
|
||
if i is None:
|
||
# String.fromCharCode
|
||
return chr(s & 0xFFFF)
|
||
if isinstance(s, str) and isinstance(i, int) and 0 <= i < len(s):
|
||
return ord(s[i])
|
||
return float("nan")
|
||
|
||
|
||
ARRAY_METHODS = {}
|
||
STRING_METHODS = {}
|
||
|
||
|
||
def _arr_push(this, *args):
|
||
for a in args:
|
||
this.append(a)
|
||
return len(this)
|
||
|
||
|
||
def _arr_shift(this):
|
||
if not this:
|
||
return UNDEF
|
||
return this.pop(0)
|
||
|
||
|
||
def _arr_join(this, sep=None):
|
||
parts = []
|
||
for x in this:
|
||
parts.append("" if x is UNDEF or x is None else str(x))
|
||
return ("," if sep is None else sep).join(parts)
|
||
|
||
|
||
def _arr_slice(this, a=None, b=None):
|
||
return this[slice(a, b)] if (a is not None and b is not None) else this[a:]
|
||
|
||
|
||
def _arr_indexof(this, x, frm=0):
|
||
try:
|
||
return this.index(x, frm or 0)
|
||
except ValueError:
|
||
return -1
|
||
|
||
|
||
def _arr_concat(this, *others):
|
||
out = list(this)
|
||
for o in others:
|
||
if isinstance(o, list):
|
||
out.extend(o)
|
||
else:
|
||
out.append(o)
|
||
return out
|
||
|
||
|
||
def _arr_pop(this):
|
||
if not this:
|
||
return UNDEF
|
||
return this.pop()
|
||
|
||
|
||
def _arr_unshift(this, *vals):
|
||
for v in reversed(vals):
|
||
this.insert(0, v)
|
||
return len(this)
|
||
|
||
|
||
def _arr_reverse(this):
|
||
this.reverse()
|
||
return this
|
||
|
||
|
||
def _arr_splice(this, start, delete_count=0, *items):
|
||
del this[start : start + delete_count]
|
||
for i, it in enumerate(items):
|
||
this.insert(start + i, it)
|
||
return this
|
||
|
||
|
||
def _arr_map(this, fn):
|
||
return [js_call(fn, UNDEF, [x]) for x in this]
|
||
|
||
|
||
def _arr_foreach(this, fn):
|
||
for x in this:
|
||
js_call(fn, UNDEF, [x])
|
||
|
||
|
||
def _arr_filter(this, fn):
|
||
return [x for x in this if js_truthy(js_call(fn, UNDEF, [x]))]
|
||
|
||
|
||
for _k, _f in [
|
||
("push", _arr_push),
|
||
("shift", _arr_shift),
|
||
("join", _arr_join),
|
||
("slice", _arr_slice),
|
||
("indexOf", _arr_indexof),
|
||
("concat", _arr_concat),
|
||
("pop", _arr_pop),
|
||
("unshift", _arr_unshift),
|
||
("reverse", _arr_reverse),
|
||
("splice", _arr_splice),
|
||
("map", _arr_map),
|
||
("forEach", _arr_foreach),
|
||
("filter", _arr_filter),
|
||
]:
|
||
ARRAY_METHODS[_k] = HostFunction(_f, _k)
|
||
|
||
|
||
def _str_split(this, sep=None, limit=None):
|
||
if sep is None or sep == UNDEF:
|
||
return [this]
|
||
if sep == "":
|
||
return list(this)
|
||
parts = this.split(sep)
|
||
return parts if limit is None or limit == UNDEF else parts[: int(limit)]
|
||
|
||
|
||
def _str_substring(this, a=0, b=None):
|
||
return this[int(a) : None if b is None or b == UNDEF else int(b)]
|
||
|
||
|
||
def _str_substr(this, a=0, length=None):
|
||
return this[
|
||
int(a) : None if length is None or length == UNDEF else int(a) + int(length)
|
||
]
|
||
|
||
|
||
def _str_charat(this, i=0):
|
||
i = int(i)
|
||
return this[i] if 0 <= i < len(this) else ""
|
||
|
||
|
||
def _str_charcodeat(this, i=0):
|
||
i = int(i)
|
||
return ord(this[i]) if 0 <= i < len(this) else float("nan")
|
||
|
||
|
||
def _str_indexof(this, x, frm=0):
|
||
return this.find(str(x), int(frm))
|
||
|
||
|
||
def _str_lastindexof(this, x):
|
||
return this.rfind(str(x))
|
||
|
||
|
||
def _str_slice(this, a=None, b=None):
|
||
return this[None if a is None else int(a) : None if b is None else int(b)]
|
||
|
||
|
||
def _str_replace(this, pat, rep):
|
||
return this.replace(str(pat), str(rep))
|
||
|
||
|
||
def _str_tolower(this):
|
||
return this.lower()
|
||
|
||
|
||
def _str_toupper(this):
|
||
return this.upper()
|
||
|
||
|
||
def _str_trim(this):
|
||
return this.strip()
|
||
|
||
|
||
def _str_concat(this, *others):
|
||
return this + "".join(str(o) for o in others)
|
||
|
||
|
||
def _str_match(this, pat):
|
||
import re as _re
|
||
|
||
return [m for m in _re.findall(str(pat), this)]
|
||
|
||
|
||
def _str_starts(this, s):
|
||
return this.startswith(str(s))
|
||
|
||
|
||
def _str_ends(this, s):
|
||
return this.endswith(str(s))
|
||
|
||
|
||
def _str_includes(this, s):
|
||
return str(s) in this
|
||
|
||
|
||
def _str_search(this, pat):
|
||
import re as _re
|
||
|
||
m = _re.search(str(pat), this)
|
||
return m.start() if m else -1
|
||
|
||
|
||
for _k, _f in [
|
||
("split", _str_split),
|
||
("substring", _str_substring),
|
||
("substr", _str_substr),
|
||
("charAt", _str_charat),
|
||
("charCodeAt", _str_charcodeat),
|
||
("indexOf", _str_indexof),
|
||
("lastIndexOf", _str_lastindexof),
|
||
("slice", _str_slice),
|
||
("replace", _str_replace),
|
||
("toLowerCase", _str_tolower),
|
||
("toUpperCase", _str_toupper),
|
||
("trim", _str_trim),
|
||
("concat", _str_concat),
|
||
("match", _str_match),
|
||
("startsWith", _str_starts),
|
||
("endsWith", _str_ends),
|
||
("includes", _str_includes),
|
||
("search", _str_search),
|
||
]:
|
||
STRING_METHODS[_k] = HostFunction(_f, _k)
|
||
|
||
|
||
def h_math_random(this):
|
||
return random.random()
|
||
|
||
|
||
def h_math_floor(this, x):
|
||
return math.floor(float(x))
|
||
|
||
|
||
def h_math_round(this, x):
|
||
return (
|
||
float(math.floor(float(x) + 0.5))
|
||
if float(x) >= 0
|
||
else float(math.ceil(float(x) - 0.5))
|
||
)
|
||
|
||
|
||
def h_math_ceil(this, x):
|
||
return math.ceil(float(x))
|
||
|
||
|
||
def h_math_min(this, *args):
|
||
return min(float(a) for a in args) if args else float("inf")
|
||
|
||
|
||
def h_math_max(this, *args):
|
||
return max(float(a) for a in args) if args else float("-inf")
|
||
|
||
|
||
def h_math_abs(this, x):
|
||
return abs(float(x))
|
||
|
||
|
||
def h_math_pow(this, a, b):
|
||
return float(a) ** float(b)
|
||
|
||
|
||
def h_math_sqrt(this, x):
|
||
return math.sqrt(float(x))
|
||
|
||
|
||
def h_parseint(this, s, radix=None):
|
||
if isinstance(s, str):
|
||
return int(s, radix or 10)
|
||
return int(s)
|
||
|
||
|
||
def h_parsefloat(this, s):
|
||
return float(s)
|
||
|
||
|
||
def h_isnan(this, x):
|
||
try:
|
||
return math.isnan(float(x))
|
||
except Exception:
|
||
return True
|
||
|
||
|
||
def h_encodeuri(this, s):
|
||
return urllib.parse.quote(str(s), safe=";/?:@&=+$,#")
|
||
|
||
|
||
def h_encodeuricomponent(this, s):
|
||
return urllib.parse.quote(str(s), safe="-_.!~*'()")
|
||
|
||
|
||
def h_decodeuri(this, s):
|
||
return urllib.parse.unquote(str(s))
|
||
|
||
|
||
def h_decodeuricomponent(this, s):
|
||
return urllib.parse.unquote(str(s))
|
||
|
||
|
||
def h_string_fromcharcode(this, *codes):
|
||
return "".join(chr(c & 0xFFFF) for c in codes)
|
||
|
||
|
||
class JSDate:
|
||
def __init__(self, *args):
|
||
self.t = 0.0
|
||
if args:
|
||
self.t = float(args[0])
|
||
|
||
def getTime(self):
|
||
return self.t
|
||
|
||
def __repr__(self):
|
||
return "<Date>"
|
||
|
||
|
||
def h_new_date(this, *args):
|
||
return JSDate(*args)
|
||
|
||
|
||
# ---------------------------------------------------------------- 解释器
|
||
|
||
|
||
class VM:
|
||
def __init__(self, bytecode, constants, window, random_seed=None):
|
||
self.o = bytecode
|
||
self.constants = constants
|
||
self.window = window
|
||
self.inst_id = 0
|
||
self.use_init_c = False # 仅首个实例使用外部 C
|
||
self.init_c = None
|
||
self.out_hex = None
|
||
self.trace_on = False
|
||
self._all_trace = []
|
||
import random as _r
|
||
|
||
self._random = _r
|
||
if random_seed is not None:
|
||
self._random.seed(random_seed)
|
||
self._hosts = self._build_hosts()
|
||
|
||
def _build_hosts(self):
|
||
m = {}
|
||
m["Math"] = JSObject()
|
||
m["Math"].set(
|
||
"random", HostFunction(lambda this: self._random.random(), "random")
|
||
)
|
||
m["Math"].set("floor", HostFunction(h_math_floor, "floor"))
|
||
m["Math"].set("round", HostFunction(h_math_round, "round"))
|
||
m["Math"].set("ceil", HostFunction(h_math_ceil, "ceil"))
|
||
m["Math"].set("min", HostFunction(h_math_min, "min"))
|
||
m["Math"].set("max", HostFunction(h_math_max, "max"))
|
||
m["Math"].set("abs", HostFunction(h_math_abs, "abs"))
|
||
m["Math"].set("pow", HostFunction(h_math_pow, "pow"))
|
||
m["Math"].set("sqrt", HostFunction(h_math_sqrt, "sqrt"))
|
||
m["parseInt"] = HostFunction(h_parseint, "parseInt")
|
||
m["parseFloat"] = HostFunction(h_parsefloat, "parseFloat")
|
||
m["isNaN"] = HostFunction(h_isnan, "isNaN")
|
||
m["encodeURIComponent"] = HostFunction(
|
||
h_encodeuricomponent, "encodeURIComponent"
|
||
)
|
||
m["encodeURI"] = HostFunction(h_encodeuri, "encodeURI")
|
||
m["decodeURIComponent"] = HostFunction(
|
||
h_decodeuricomponent, "decodeURIComponent"
|
||
)
|
||
m["decodeURI"] = HostFunction(h_decodeuri, "decodeURI")
|
||
m["String"] = JSObject()
|
||
m["String"].set(
|
||
"fromCharCode", HostFunction(h_string_fromcharcode, "fromCharCode")
|
||
)
|
||
m["Date"] = HostFunction(h_new_date, "Date")
|
||
return m
|
||
|
||
def host(self, name):
|
||
h = self.window.get(name)
|
||
if h is not UNDEF:
|
||
return h
|
||
return self._hosts.get(name, UNDEF)
|
||
|
||
def make(self, entry, args, s, n, t):
|
||
return JSFunction(self, entry, args, s, n, t)
|
||
|
||
def run(self, fn: JSFunction, call_args):
|
||
"""执行一个 VM 函数实例(h)。fn 来自 make() 或 op87/58 创建的闭包。"""
|
||
self.inst_id += 1
|
||
iid = self.inst_id
|
||
if self.use_init_c and self.init_c is not None:
|
||
C = self.init_c
|
||
self.use_init_c = False
|
||
else:
|
||
C = [fn.s, fn.n, fn.args, UNDEF, call_args, fn, self.o, 0]
|
||
# 保证 C 可随机访问
|
||
C = list(C)
|
||
p = UNDEF
|
||
u = fn.entry
|
||
d = [] # 异常续延栈
|
||
l = UNDEF # 最近异常(op12 读取)
|
||
o = self.o
|
||
cap = None # 外部捕获回调(验证用)
|
||
if not hasattr(self, "_all_trace"):
|
||
self._all_trace = []
|
||
while True:
|
||
try:
|
||
while True:
|
||
u += 1
|
||
op = o[u]
|
||
if self.trace_on and len(self._all_trace) < 800000:
|
||
self._all_trace.append((op, u))
|
||
# ---- 结果捕获(与 Node 移植一致:webSave 返回点)----
|
||
if u == 121386 and not getattr(self, "_cap121", None):
|
||
self._cap121 = {}
|
||
for _s in (
|
||
10,
|
||
11,
|
||
13,
|
||
14,
|
||
15,
|
||
30,
|
||
42,
|
||
43,
|
||
48,
|
||
59,
|
||
70,
|
||
85,
|
||
86,
|
||
50,
|
||
54,
|
||
66,
|
||
88,
|
||
17,
|
||
63,
|
||
):
|
||
self._cap121[_s] = js_index(C, _s)
|
||
if u == 117134 and self.capture_at_return:
|
||
v = js_index(C, 3476)
|
||
if isinstance(v, list):
|
||
self.out_arr = list(v)
|
||
parts = []
|
||
for x in v:
|
||
try:
|
||
parts.append("%02x" % (int(x) & 255))
|
||
except Exception:
|
||
parts.append("??")
|
||
self.out_hex = "".join(parts)
|
||
# 仅供离线排查 webSave 输出缓冲何时变化,生产路径默认不启用。
|
||
if getattr(self, "capture_output_lengths", False):
|
||
v = js_index(C, 3476)
|
||
if isinstance(v, list):
|
||
length = len(v)
|
||
if length != getattr(self, "_last_output_length", None):
|
||
self._last_output_length = length
|
||
self.output_length_trace.append((u, op, length))
|
||
# ---- opcode dispatch ----
|
||
if op == 0:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(C, b) - c)
|
||
elif op == 1:
|
||
a, b, c, dd, e = (
|
||
o[u + 1],
|
||
o[u + 2],
|
||
o[u + 3],
|
||
o[u + 4],
|
||
o[u + 5],
|
||
)
|
||
u += 5
|
||
js_set(C, a, js_index(C, b))
|
||
js_set(C, c, i32(_ic(js_index(C, dd)) ^ _ic(js_index(C, e))))
|
||
js_set(C, o[u + 1], js_index(C, o[u + 2]))
|
||
u += 2
|
||
elif op == 2:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(
|
||
C, a, js_index(js_index(C, b), js_index(C, c)) is not UNDEF
|
||
)
|
||
elif op == 3:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, ushr(js_index(C, b), c))
|
||
elif op == 4:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, not js_truthy(js_index(C, b)))
|
||
elif op == 5:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _ic(js_index(C, b)) & _ic(c))
|
||
a2, b2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a2, js_index(js_index(C, b2), js_index(C, c2)))
|
||
a3, b3, c3 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a3, i32(_ic(js_index(C, b3)) ^ _ic(js_index(C, c3))))
|
||
elif op == 6:
|
||
if d:
|
||
d.pop()
|
||
elif op == 7:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_keys(js_index(C, b)))
|
||
elif op == 8:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), js_index(C, c)))
|
||
a2, b2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a2, i32(_ic(js_index(C, b2)) ^ _ic(js_index(C, c2))))
|
||
a3, b3, imm = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a3, js_index(js_index(C, b3), imm))
|
||
elif op == 9:
|
||
a, b, imm1 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), imm1))
|
||
c, imm2, d = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, c), imm2, js_index(C, d))
|
||
elif op == 10:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _ic(js_index(C, b)) & _ic(c))
|
||
a2, b2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a2, i32(_ic(js_index(C, b2)) ^ _ic(js_index(C, c2))))
|
||
a3, b3 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a3, js_index(C, b3))
|
||
elif op == 11:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, shr(js_index(C, b), c))
|
||
elif op == 12:
|
||
a = o[u + 1]
|
||
u += 1
|
||
js_set(C, a, l)
|
||
elif op == 13:
|
||
a, c = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_str(js_index(C, a)) + chr(c & 0xFFFF))
|
||
elif op == 14:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_num(js_index(C, b)) % js_num(js_index(C, c)))
|
||
elif op == 15:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, ~i32(js_index(C, b)))
|
||
elif op == 16:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), js_index(C, c)))
|
||
a2 = o[u + 1]
|
||
u += 1
|
||
js_set(C, a2, "")
|
||
a3, c2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a3, js_str(js_index(C, a3)) + chr(c2 & 0xFFFF))
|
||
elif op == 17:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_call(js_index(C, b), p, [js_index(C, c)]))
|
||
elif op == 18:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_add(js_index(C, b), js_index(C, c)))
|
||
elif op == 19:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, "")
|
||
js_set(C, b, js_add(js_index(C, b), chr(c & 0xFFFF)))
|
||
elif op == 20:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, i32(_ic(js_index(C, b)) | _ic(c)))
|
||
elif op == 21:
|
||
a = o[u + 1]
|
||
u += 1
|
||
js_set(C, a, "")
|
||
elif op == 22:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, [UNDEF] * b)
|
||
elif op == 23:
|
||
a, imm, b = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), imm, js_index(C, b))
|
||
c2 = o[u + 1]
|
||
u += 1
|
||
js_set(C, c2, "")
|
||
d2, ch = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, d2, js_str(js_index(C, d2)) + chr(ch & 0xFFFF))
|
||
elif op == 24:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, i32(_ic(js_index(C, b)) ^ _ic(js_index(C, c))))
|
||
a2, b2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a2, js_index(C, b2))
|
||
a3, b3 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a3, js_index(C, b3))
|
||
elif op == 25:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, shr(js_index(C, b), js_index(C, c)))
|
||
elif op == 26:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, -js_index(C, b))
|
||
c2, imm, d2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, c2), imm, js_index(C, d2))
|
||
e2, f2, imm2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, e2, js_index(js_index(C, f2), imm2))
|
||
elif op == 27:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_del(js_index(C, b), js_index(C, c))
|
||
elif op == 28:
|
||
a, c = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_str(js_index(C, a)) + chr(c & 0xFFFF))
|
||
a2, b2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a2, js_index(js_index(C, b2), js_index(C, c2)))
|
||
elif op == 29:
|
||
a, imm, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_add(imm, js_index(C, c)))
|
||
elif op == 30:
|
||
a, imm, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, imm - js_num(js_index(C, c)))
|
||
elif op == 31:
|
||
a, imm1 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, imm1)
|
||
b2, imm2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, b2), imm2, js_index(C, c2))
|
||
elif op == 32:
|
||
a, imm1 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, imm1)
|
||
b2, imm2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, b2), imm2, js_index(C, c2))
|
||
d3, imm3 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, d3, imm3)
|
||
elif op == 33:
|
||
a, c1, b, c2 = o[u + 1], o[u + 2], o[u + 3], o[u + 4]
|
||
u += 4
|
||
js_set(C, a, js_str(js_index(C, a)) + chr(c1 & 0xFFFF))
|
||
js_set(C, b, js_str(js_index(C, b)) + chr(c2 & 0xFFFF))
|
||
elif op == 34:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, b)
|
||
elif op == 35:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), c))
|
||
elif op == 36:
|
||
a, b, c, dd = o[u + 1], o[u + 2], o[u + 3], o[u + 4]
|
||
u += 4
|
||
js_set(
|
||
C,
|
||
a,
|
||
js_new(js_index(C, b), [js_index(C, c), js_index(C, dd)]),
|
||
)
|
||
elif op == 37:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_call(js_index(C, b), js_index(C, c), []))
|
||
elif op == 38:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_index(C, b))
|
||
c2x, d1, imm1 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, c2x, ushr(js_index(C, d1), imm1))
|
||
e, f, imm2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, e, js_index(C, f) & imm2)
|
||
elif op == 39:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_eq(js_index(C, b), js_index(C, c)))
|
||
elif op == 40:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, -js_index(C, b))
|
||
elif op == 41:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, [UNDEF] * b)
|
||
a2, b2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a2, [UNDEF] * b2)
|
||
elif op == 42:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _cmp_le(js_index(C, b), js_index(C, c)))
|
||
elif op == 43:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, ushr(js_index(C, b), c))
|
||
a2, b2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a2, js_index(C, b2) & c2)
|
||
a3, b3, c3 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a3, js_index(js_index(C, b3), js_index(C, c3)))
|
||
elif op == 44:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, shl(js_index(C, b), c))
|
||
elif op == 45:
|
||
a = o[u + 1]
|
||
u += 1
|
||
return js_index(C, a)
|
||
elif op == 46:
|
||
a = o[u + 1]
|
||
u += 1
|
||
js_set(C, a, JSObject())
|
||
elif op == 47:
|
||
a, b, imm1 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), imm1))
|
||
c2, d2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, c2, -js_index(C, d2))
|
||
e2, imm2, f2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, e2), imm2, js_index(C, f2))
|
||
elif op == 48:
|
||
a, imm1, b = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), imm1, js_index(C, b))
|
||
c2, imm2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, c2, imm2)
|
||
d2, imm3, e2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, d2), imm3, js_index(C, e2))
|
||
elif op == 49:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, i32(_ic(js_index(C, b)) | _ic(js_index(C, c))))
|
||
elif op == 50:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _cmp_gt(js_index(C, b), js_index(C, c)))
|
||
elif op == 51:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), js_index(C, c)))
|
||
a2, b2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a2, js_index(js_index(C, b2), js_index(C, c2)))
|
||
a3, b3, c3 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a3, i32(_ic(js_index(C, b3)) ^ _ic(js_index(C, c3))))
|
||
elif op == 52:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), js_index(C, c)))
|
||
elif op == 53:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _ic(js_index(C, b)) & _ic(js_index(C, c)))
|
||
elif op == 54:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_streq(js_index(C, b), js_index(C, c)))
|
||
elif op == 55:
|
||
a, imm1, b = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), imm1, js_index(C, b))
|
||
c2, imm2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, c2, imm2)
|
||
elif op == 56:
|
||
a, b, c, dd = o[u + 1], o[u + 2], o[u + 3], o[u + 4]
|
||
u += 4
|
||
js_set(
|
||
C,
|
||
a,
|
||
js_call(js_index(C, b), js_index(C, c), [js_index(C, dd)]),
|
||
)
|
||
elif op == 57:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(
|
||
C,
|
||
a,
|
||
isinstance(js_index(C, b), type(js_index(C, c)))
|
||
if isinstance(js_index(C, c), type)
|
||
else False,
|
||
)
|
||
elif op == 58:
|
||
ac = o[u + 1]
|
||
u += 1
|
||
f = []
|
||
for _ in range(ac):
|
||
f.append(js_index(C, o[u + 1]))
|
||
u += 1
|
||
dest = o[u + 1]
|
||
u += 1
|
||
off = o[u + 1]
|
||
u += 1
|
||
js_set(C, dest, self.make(u - 1 + off, f, fn.s, fn.n, fn.t))
|
||
elif op == 59:
|
||
a, imm1, b = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), imm1, js_index(C, b))
|
||
c2, imm2, d2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, c2), imm2, js_index(C, d2))
|
||
elif op == 60:
|
||
a, b, c, dd = o[u + 1], o[u + 2], o[u + 3], o[u + 4]
|
||
u += 4
|
||
js_set(
|
||
C,
|
||
a,
|
||
js_call(
|
||
js_index(C, b), p, [js_index(C, c), js_index(C, dd)]
|
||
),
|
||
)
|
||
elif op == 61:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, i32(_ic(js_index(C, b)) ^ _ic(c)))
|
||
elif op == 62:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_new(js_index(C, b), [js_index(C, c)]))
|
||
elif op == 63:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _cmp_lt(js_index(C, b), c))
|
||
elif op == 64:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_num(js_index(C, b)) - js_num(js_index(C, c)))
|
||
elif op == 65:
|
||
a, imm1 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, imm1)
|
||
b2, c2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, b2, -js_index(C, c2))
|
||
d2, imm2, e2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, d2), imm2, js_index(C, e2))
|
||
elif op == 66:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_new(js_index(C, b), []))
|
||
elif op == 67:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), c))
|
||
a2, b2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a2), js_index(C, b2), js_index(C, c2))
|
||
a3, b3 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a3, js_index(js_index(C, b3), o[u + 1]))
|
||
u += 1
|
||
elif op == 68:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_index(js_index(C, b), c))
|
||
a2 = o[u + 1]
|
||
u += 1
|
||
js_set(C, a2, "")
|
||
a3, c2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a3, js_str(js_index(C, a3)) + chr(c2 & 0xFFFF))
|
||
elif op == 69:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _cmp_ge(js_index(C, b), js_index(C, c)))
|
||
elif op == 70:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_index(C, b) - 0)
|
||
elif op == 71:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _ic(js_index(C, b)) & _ic(c))
|
||
elif op == 72:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_index(C, b) - 1)
|
||
elif op == 73:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _cmp_le(js_index(C, b), c))
|
||
elif op == 74:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_typeof(js_index(C, b)))
|
||
elif op == 75:
|
||
a, b, c, dd = o[u + 1], o[u + 2], o[u + 3], o[u + 4]
|
||
u += 4
|
||
js_set(
|
||
C,
|
||
a,
|
||
js_call(
|
||
js_index(C, b), p, [js_index(C, c), js_index(C, dd)]
|
||
),
|
||
)
|
||
elif op == 76:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), js_index(C, b), js_index(C, c))
|
||
a2 = o[u + 1]
|
||
u += 1
|
||
js_set(C, a2, "")
|
||
a3, c2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a3, js_str(js_index(C, a3)) + chr(c2 & 0xFFFF))
|
||
elif op == 77:
|
||
a, b, c, dd, e, f = (
|
||
o[u + 1],
|
||
o[u + 2],
|
||
o[u + 3],
|
||
o[u + 4],
|
||
o[u + 5],
|
||
o[u + 6],
|
||
)
|
||
u += 6
|
||
js_set(C, a, js_index(js_index(C, b), c))
|
||
js_set(C, dd, js_index(js_index(C, e), f))
|
||
elif op == 78:
|
||
a, b, c, dd, e, f = (
|
||
o[u + 1],
|
||
o[u + 2],
|
||
o[u + 3],
|
||
o[u + 4],
|
||
o[u + 5],
|
||
o[u + 6],
|
||
)
|
||
u += 6
|
||
js_set(C, a, js_index(C, b))
|
||
js_set(C, c, js_index(C, dd))
|
||
js_set(C, e, js_index(C, f))
|
||
elif op == 79:
|
||
a = o[u + 1]
|
||
u += 1
|
||
d.append(u - 1 + a)
|
||
elif op == 80:
|
||
ac = o[u + 1]
|
||
u += 1
|
||
f = []
|
||
for _ in range(ac):
|
||
f.append(js_index(C, o[u + 1]))
|
||
u += 1
|
||
dest = o[u + 1]
|
||
u += 1
|
||
fn = js_index(C, o[u + 1])
|
||
u += 1
|
||
js_set(C, dest, js_apply(fn, p, f))
|
||
elif op == 81:
|
||
a, b, c, dd = o[u + 1], o[u + 2], o[u + 3], o[u + 4]
|
||
u += 4
|
||
js_set(
|
||
C,
|
||
a,
|
||
js_call(js_index(C, b), js_index(C, c), [js_index(C, dd)]),
|
||
)
|
||
elif op == 82:
|
||
a = o[u + 1]
|
||
u += 1
|
||
js_set(C, a, False)
|
||
elif op == 83:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _cmp_ge(js_index(C, b), c))
|
||
elif op == 84:
|
||
a, imm1, b = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), imm1, js_index(C, b))
|
||
c2, d2, imm2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, c2, js_index(js_index(C, d2), imm2))
|
||
e2, f2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, e2, -js_index(C, f2))
|
||
elif op == 85:
|
||
u += o[u + 1]
|
||
elif op == 86:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, i32(_ic(js_index(C, b)) ^ _ic(js_index(C, c))))
|
||
elif op == 87:
|
||
ac = o[u + 1]
|
||
u += 1
|
||
f = []
|
||
for _ in range(ac):
|
||
f.append(js_index(C, o[u + 1]))
|
||
u += 1
|
||
dest = o[u + 1]
|
||
u += 1
|
||
off = o[u + 1]
|
||
u += 1
|
||
js_set(C, dest, self.make(u - 1 + off, f, fn.s, fn.n, fn.t))
|
||
elif op == 88:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _cmp_lt(js_index(C, b), js_index(C, c)))
|
||
elif op == 89:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_eq(js_index(C, b), c))
|
||
elif op == 90:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_num(js_index(C, b)) / js_num(js_index(C, c)))
|
||
elif op == 91:
|
||
a, imm, b = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), imm, js_index(C, b))
|
||
elif op == 92:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), js_index(C, b), js_index(C, c))
|
||
elif op == 93:
|
||
a, ch = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_str(js_index(C, a)) + chr(ch & 0xFFFF))
|
||
b2, imm, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, b2), imm, js_index(C, c2))
|
||
d2 = o[u + 1]
|
||
u += 1
|
||
js_set(C, d2, "")
|
||
elif op == 94:
|
||
cond = o[u + 1]
|
||
then_off = o[u + 2]
|
||
else_off = o[u + 3]
|
||
u += then_off if js_truthy(js_index(C, cond)) else else_off
|
||
elif op == 95:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_index(C, b))
|
||
elif op == 96:
|
||
a = o[u + 1]
|
||
u += 1
|
||
js_set(C, a, p)
|
||
elif op == 97:
|
||
ac = o[u + 1]
|
||
u += 1
|
||
f = []
|
||
for _ in range(ac):
|
||
f.append(js_index(C, o[u + 1]))
|
||
u += 1
|
||
dest = o[u + 1]
|
||
u += 1
|
||
fn = js_index(C, o[u + 1])
|
||
u += 1
|
||
thisv = js_index(C, o[u + 1])
|
||
u += 1
|
||
if not (
|
||
isinstance(fn, (JSFunction, HostFunction)) or callable(fn)
|
||
):
|
||
raise RuntimeError(
|
||
"op97 non-function at u=%s fn=%r" % (u, fn)
|
||
)
|
||
js_set(C, dest, js_apply(fn, thisv, f))
|
||
elif op == 98:
|
||
a = o[u + 1]
|
||
u += 1
|
||
raise _VMThrow(js_index(C, a))
|
||
elif op == 99:
|
||
a, imm1, b = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), imm1, js_index(C, b))
|
||
c2, d2, imm2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, c2, js_index(js_index(C, d2), imm2))
|
||
elif op == 100:
|
||
a, b, c, dd = o[u + 1], o[u + 2], o[u + 3], o[u + 4]
|
||
u += 4
|
||
js_set(
|
||
C,
|
||
a,
|
||
js_new(js_index(C, b), [js_index(C, c), js_index(C, dd)]),
|
||
)
|
||
elif op == 101:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
f = js_index(C, a)
|
||
if isinstance(f, list) and len(f) > 0:
|
||
js_set(C, b, True)
|
||
js_set(C, c, f.pop(0))
|
||
else:
|
||
js_set(C, b, False)
|
||
u += 1
|
||
elif op == 102:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_index(C, b) + 1)
|
||
elif op == 103:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, i32(_ic(js_index(C, b)) ^ _ic(js_index(C, c))))
|
||
a2, b2 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a2, js_index(C, b2))
|
||
elif op == 104:
|
||
a, imm1, b = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, a), imm1, js_index(C, b))
|
||
c2, d2, imm2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, c2, js_index(js_index(C, d2), imm2))
|
||
e2, imm3, f2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, e2), imm3, js_index(C, f2))
|
||
elif op == 105:
|
||
a, b, c, dd, e = (
|
||
o[u + 1],
|
||
o[u + 2],
|
||
o[u + 3],
|
||
o[u + 4],
|
||
o[u + 5],
|
||
)
|
||
u += 5
|
||
js_set(
|
||
C,
|
||
a,
|
||
js_call(
|
||
js_index(C, b),
|
||
js_index(C, c),
|
||
[js_index(C, dd), js_index(C, e)],
|
||
),
|
||
)
|
||
elif op == 106:
|
||
a = o[u + 1]
|
||
u += 1
|
||
js_set(C, a, True)
|
||
elif op == 107:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_add(js_index(C, b), c))
|
||
elif op == 108:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, shl(js_index(C, b), js_index(C, c)))
|
||
elif op == 109:
|
||
a, b, c, dd = o[u + 1], o[u + 2], o[u + 3], o[u + 4]
|
||
u += 4
|
||
js_set(C, a, js_index(C, b))
|
||
js_set(C, c, js_index(C, dd))
|
||
elif op == 110:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_streq(js_index(C, b), c))
|
||
elif op == 111:
|
||
a, b = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, js_call(js_index(C, b), p, []))
|
||
elif op == 112:
|
||
a, imm1 = o[u + 1], o[u + 2]
|
||
u += 2
|
||
js_set(C, a, imm1)
|
||
b2, imm2, c2 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(js_index(C, b2), imm2, js_index(C, c2))
|
||
d2, e2, imm3 = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, d2, js_index(js_index(C, e2), imm3))
|
||
elif op == 113:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, _cmp_gt(js_index(C, b), c))
|
||
elif op == 114:
|
||
a, b, c = o[u + 1], o[u + 2], o[u + 3]
|
||
u += 3
|
||
js_set(C, a, js_num(js_index(C, b)) * js_num(js_index(C, c)))
|
||
elif op == 115:
|
||
a = o[u + 1]
|
||
u += 1
|
||
js_set(C, a, None)
|
||
else:
|
||
raise RuntimeError("unknown opcode %s at %s" % (op, u))
|
||
except _VMThrow as e:
|
||
if not d:
|
||
raise RuntimeError("VM uncaught throw: %s" % (e.value,))
|
||
l = e.value
|
||
u = d.pop()
|
||
continue
|
||
except Exception as e:
|
||
if not d:
|
||
raise
|
||
if not isinstance(d, list):
|
||
raise RuntimeError(
|
||
"d corrupted: %r (type %s) at trace %s"
|
||
% (d, type(d).__name__, self._trace[-3:])
|
||
)
|
||
l = e
|
||
u = d.pop()
|
||
continue
|
||
|
||
|
||
class _VMThrow(Exception):
|
||
def __init__(self, value):
|
||
super().__init__(str(value))
|
||
self.value = value
|
||
|
||
|
||
def js_new(ctor, args):
|
||
if isinstance(ctor, HostFunction):
|
||
return ctor.fn(None, *args)
|
||
if callable(ctor):
|
||
return ctor(None, *args)
|
||
raise TypeError("not a constructor")
|
||
|
||
|
||
# ---------------------------------------------------------------- 编码/解码
|
||
|
||
|
||
def decode_d(v):
|
||
"""把深拷贝探针的 D 编码还原为 Python 值。"""
|
||
if not isinstance(v, str):
|
||
return v
|
||
if v == "u":
|
||
return UNDEF
|
||
if v == "n":
|
||
return None
|
||
if v == "W":
|
||
return None # window,由调用方替换
|
||
if v == "b:1":
|
||
return True
|
||
if v == "b:0":
|
||
return False
|
||
if v.startswith("s:"):
|
||
return v[2:]
|
||
if v.startswith("f:"):
|
||
return None
|
||
m = __import__("re").match(r"^a:(\d+):(.*)$", v, __import__("re").S)
|
||
if m:
|
||
n = int(m.group(1))
|
||
arr = json.loads(m.group(2))
|
||
out = [UNDEF] * n
|
||
for i in range(min(len(arr), n)):
|
||
out[i] = decode_d(arr[i])
|
||
return out
|
||
m = __import__("re").match(r"^t:(\w+):(\d+):(.*)$", v, __import__("re").S)
|
||
if m:
|
||
vals = [int(x) for x in m.group(3).split(",")] if m.group(3) else []
|
||
return vals
|
||
m = __import__("re").match(r"^o:\{(.*)\}$", v, __import__("re").S)
|
||
if m and m.group(1):
|
||
obj = JSObject()
|
||
inner = m.group(1)
|
||
parts, cur, d2, in_str = [], "", 0, False
|
||
for ch in inner:
|
||
if in_str:
|
||
cur += ch
|
||
if ch == '"' and cur[-2:] != '\\"':
|
||
in_str = False
|
||
elif ch == '"':
|
||
in_str = True
|
||
cur += ch
|
||
elif ch in "{[":
|
||
d2 += 1
|
||
cur += ch
|
||
elif ch in "}]":
|
||
d2 -= 1
|
||
cur += ch
|
||
elif ch == "," and d2 == 0:
|
||
parts.append(cur)
|
||
cur = ""
|
||
else:
|
||
cur += ch
|
||
if cur.strip():
|
||
parts.append(cur)
|
||
for p in parts:
|
||
ci = p.find(":")
|
||
key = json.loads(p[:ci])
|
||
vs = p[ci + 1 :]
|
||
try:
|
||
obj.set(key, decode_d(json.loads(vs)))
|
||
except Exception:
|
||
obj.set(key, decode_d(vs))
|
||
return obj
|
||
if v.lstrip("-").isdigit():
|
||
return int(v)
|
||
try:
|
||
return float(v)
|
||
except Exception:
|
||
return v
|
||
|
||
|
||
def generate_encrypt_msg(
|
||
args_json: str, cb_json: str, out_json: str | None = None
|
||
) -> str:
|
||
"""由深拷贝 18 参(cap-85091 的 C[2])与回调 4 参(cap-69667 的 C[2])生成 encrypt_msg hex。"""
|
||
replay = REPLAY
|
||
bytecode = json.loads((replay / "bytecode.json").read_text())
|
||
constants = json.loads((replay / "constants.json").read_text())
|
||
xmidas = json.loads((replay / "xmidasops.json").read_text())
|
||
args_raw = json.loads(json.loads(Path(args_json).read_text().strip()))["C"][2]
|
||
cb_raw = json.loads(json.loads(Path(cb_json).read_text().strip()))["C"][2]
|
||
|
||
window = Window()
|
||
for _k in ("window", "self", "globalThis", "top", "parent", "frames"):
|
||
window.set(_k, window)
|
||
window.set("Math", None) # placeholder; replaced below
|
||
# 页面全局
|
||
window.set("xMidasOps", xmidas)
|
||
window.set(
|
||
"xMidasToken",
|
||
"DE46DBA4754D42A6B66ADD4319FF80C144D9ED22ED73384C271793D9A9AA2FFBD6C104BE8D4A7F4ED2A8688FCB6F7540",
|
||
)
|
||
window.set("xMidasVersion", "web_1.0.6")
|
||
cfg = JSObject()
|
||
webpay = JSObject()
|
||
webpay.set("old_encrypt_offerid", ["1450007826", "1110165571"])
|
||
itemmap = JSObject()
|
||
itemmap.set("webpay", webpay)
|
||
cfg.set("itemMap", itemmap)
|
||
window.set("__midasStaticConfig_midas_webpay", cfg)
|
||
|
||
vm = VM(bytecode, constants, window)
|
||
window.set("Math", vm._hosts["Math"])
|
||
window.set("Date", vm._hosts["Date"])
|
||
window.set("String", vm._hosts["String"])
|
||
for k in (
|
||
"parseInt",
|
||
"parseFloat",
|
||
"isNaN",
|
||
"encodeURIComponent",
|
||
"encodeURI",
|
||
"decodeURIComponent",
|
||
"decodeURI",
|
||
):
|
||
window.set(k, vm._hosts[k])
|
||
|
||
args = decode_d(args_raw)
|
||
cb_args = decode_d(cb_raw)
|
||
# 共享同一对象(真实流程中参数数组跨实例共享)
|
||
cb_args[0] = args[10]
|
||
cb_args[1] = args[6]
|
||
cb_args[3] = args[0]
|
||
rand_gen = vm.make(85172, [], window, constants, None)
|
||
cb_args[2] = [rand_gen]
|
||
cb = vm.make(69666, cb_args, window, constants, None)
|
||
args[17][0] = cb
|
||
|
||
init_c = [None] * 8
|
||
init_c[0] = window
|
||
init_c[1] = constants
|
||
init_c[2] = args
|
||
init_c[5] = None
|
||
init_c[6] = bytecode
|
||
web = vm.make(85090, args, window, constants, None)
|
||
init_c[5] = web
|
||
vm.use_init_c = True
|
||
vm.init_c = init_c
|
||
vm.capture_at_return = True
|
||
vm.out_hex = None
|
||
vm.run(web, [])
|
||
|
||
if vm.out_hex is None:
|
||
raise RuntimeError("capture failed: flow diverged")
|
||
if out_json:
|
||
Path(out_json).write_text(json.dumps({"encrypt_msg": vm.out_hex}) + "\n")
|
||
return vm.out_hex
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import sys
|
||
|
||
a = (
|
||
sys.argv[1]
|
||
if len(sys.argv) > 1
|
||
else str(REPLAY / "deepcaps2" / "cap-85091.json")
|
||
)
|
||
b = (
|
||
sys.argv[2]
|
||
if len(sys.argv) > 2
|
||
else str(REPLAY / "deepcaps2" / "cap-69667.json")
|
||
)
|
||
o = sys.argv[3] if len(sys.argv) > 3 else None
|
||
hexout = generate_encrypt_msg(a, b, o)
|
||
print(hexout)
|
||
|
||
|
||
# ---------------------------------------------------------------- 离线生成(A2)
|
||
|
||
|
||
def _js_encodeuri(s):
|
||
"""JS encodeURIComponent 等价(不转义 -_.!~*'())。"""
|
||
return urllib.parse.quote(str(s), safe="-_.!~*'()")
|
||
|
||
|
||
def build_plaintext(params, fk_extend, ts, rand_val, jun_st_len="5"):
|
||
"""构造 21 字段明文(528 字符目标,实际长度由字段值决定)。
|
||
params: 订单字段字典(token_id/openid/openkey/session_id/session_type/zoneid/
|
||
pay_method/buy_quantity/mb_pwd/pay_id/auth_key/card_value/accounttype/
|
||
provide_uin/extend/from_h5/webversion)
|
||
"""
|
||
fields = [
|
||
"token_id",
|
||
"openid",
|
||
"openkey",
|
||
"session_id",
|
||
"session_type",
|
||
"zoneid",
|
||
"pay_method",
|
||
"buy_quantity",
|
||
"mb_pwd",
|
||
"pay_id",
|
||
"auth_key",
|
||
"card_value",
|
||
"accounttype",
|
||
"provide_uin",
|
||
"extend",
|
||
"ts",
|
||
"from_h5",
|
||
"webversion",
|
||
"fk_extend",
|
||
"_junStLen",
|
||
"_rand",
|
||
]
|
||
parts = []
|
||
for f in fields:
|
||
if f == "ts":
|
||
val = str(ts)
|
||
elif f == "fk_extend":
|
||
# fk_extend 已是最终编码形态(tdrc_session%3Dpay-...),不再二次编码
|
||
val = str(fk_extend)
|
||
elif f == "_junStLen":
|
||
val = str(jun_st_len)
|
||
elif f == "_rand":
|
||
val = rand_val
|
||
else:
|
||
val = _js_encodeuri(params.get(f, ""))
|
||
parts.append(f + "=" + val)
|
||
return "&".join(parts)
|
||
|
||
|
||
def recover_plaintext_from_buffer(buffer, key16, te_tables=None):
|
||
"""从 a:8 变换后的 528 字节明文缓冲反推 21 字段明文(纯离线,无浏览器)。
|
||
|
||
原理(F-2047):out[j] = word 大端字节 j XOR key16[(4*blk+j)%16],
|
||
word = Te0[c0]^Te1[c1]^Te2[c2]^Te3[c3](标准 AES Te0-Te3)。
|
||
对每 4 字符块用 meet-in-the-middle 求 ASCII 解(明文是 urlencoded,
|
||
除 _rand 末尾控制符外均为可见 ASCII)。E3:双会话(2026-08-11)恢复
|
||
明文与页面捕获缓冲 528/528 一致,且恢复的 ts/fk_extend/_rand 用于
|
||
离线生成 → 1056/1056 复现页面 encrypt_msg。
|
||
"""
|
||
if te_tables is None:
|
||
te_cap = json.loads(
|
||
json.loads((REPLAY / "e2e" / "multi-1.json").read_text().strip())
|
||
)
|
||
te_args = decode_d(te_cap["C"][2])
|
||
te_tables = [te_args[i][0] for i in (2, 3, 4, 5)]
|
||
Te0, Te1, Te2, Te3 = te_tables
|
||
printable = list(range(32, 127))
|
||
|
||
def build_midtable(chars):
|
||
tab = {}
|
||
for c2, c3 in itertools.product(chars, repeat=2):
|
||
v = (Te2[c2] ^ Te3[c3]) & M32
|
||
tab.setdefault(v, []).append((c2, c3))
|
||
return tab
|
||
|
||
mid_p = build_midtable(printable)
|
||
mid_all = build_midtable(list(range(1, 128))) # 含控制符(兜底 _rand 尾部)
|
||
|
||
def inv_block(blk, mid):
|
||
base = 4 * blk
|
||
word = 0
|
||
for j in range(4):
|
||
word = (word << 8) | (buffer[base + j] ^ key16[(base + j) % 16])
|
||
word &= M32
|
||
for c0, c1 in itertools.product(printable, repeat=2):
|
||
target = (word ^ Te0[c0] ^ Te1[c1]) & M32
|
||
for c2, c3 in mid.get(target, []):
|
||
yield (c0, c1, c2, c3)
|
||
|
||
out = []
|
||
for blk in range(len(buffer) // 4):
|
||
sols = list(inv_block(blk, mid_p))
|
||
if not sols:
|
||
sols = list(inv_block(blk, mid_all))
|
||
if not sols:
|
||
raise ValueError(f"第 {blk} 块(字节 {4 * blk})无法反推明文")
|
||
out.extend(sols[0])
|
||
return bytes(out).decode("latin-1")
|
||
|
||
|
||
def derive_key1_from_key16(key16, te_tables=None, sbox=None):
|
||
"""由 key16 反解 key1(纯 HTTP 关键突破,2026-08-12 实证)。
|
||
|
||
webSave 的 key 校验(E3,双会话 16/16):
|
||
C2201(i) = Te0[key1[i]] ^ Te1[key1[i+1]] ^ Te2[key1[i+2]] ^ Te3[key1[i+3]]
|
||
key16[i+t] = Sbox[ byte_t(C2201(i)) ] (t=0..3, i=0,4,8,12)
|
||
因此给定任意 key16(浏览器式随机),按组反推 C2201 的 4 字节
|
||
(Sbox^-1),再用 meet-in-the-middle 解 key1[i..i+3]。求解结果与
|
||
捕获 key1 完全一致(order10/order12),证明 key1 = G(key16) 可纯计算。
|
||
"""
|
||
if te_tables is None or sbox is None:
|
||
te_cap = json.loads(
|
||
json.loads((REPLAY / "e2e" / "multi-1.json").read_text().strip())
|
||
)
|
||
te_args = decode_d(te_cap["C"][2])
|
||
te_tables = [te_args[i][0] for i in (2, 3, 4, 5)]
|
||
# 注意:S-box 需要来自 args_template slot[5](跨会话恒定),无法从 e2e 推断
|
||
raise ValueError("需要显式传入 te_tables(4×256)与 sbox(256)")
|
||
Te0, Te1, Te2, Te3 = te_tables
|
||
s_inv = {v: idx for idx, v in enumerate(sbox)}
|
||
if len(s_inv) != 256:
|
||
raise ValueError("S-box 值非 256 唯一,无法反解")
|
||
pre = {}
|
||
for a in range(256):
|
||
for b in range(256):
|
||
pre.setdefault(u32(Te0[a] ^ Te1[b]), []).append((a, b))
|
||
k1 = [0] * 16
|
||
for g in range(4):
|
||
i = g * 4
|
||
c = 0
|
||
for t in range(4):
|
||
c = (c << 8) | s_inv[key16[i + t]]
|
||
target = u32(c)
|
||
found = None
|
||
for d_ in range(256):
|
||
for e in range(256):
|
||
rem = u32(target ^ Te2[d_] ^ Te3[e])
|
||
if rem in pre:
|
||
a, b = pre[rem][0]
|
||
found = (a, b, d_, e)
|
||
break
|
||
if found:
|
||
break
|
||
if not found:
|
||
raise ValueError(f"key1 组 {g} 无解(key16={key16})")
|
||
k1[i], k1[i + 1], k1[i + 2], k1[i + 3] = found
|
||
return k1
|
||
|
||
|
||
def generate_encrypt_msg_offline(
|
||
params,
|
||
fk_extend,
|
||
ts,
|
||
rand_val,
|
||
key16=None,
|
||
key1=None,
|
||
args_template=None,
|
||
cb_json=None,
|
||
random_seed=None,
|
||
xmidas=None,
|
||
xmidas_token=None,
|
||
diagnostics=None,
|
||
):
|
||
"""纯离线生成 encrypt_msg(A2 路线,2026-08-10 双向量 E3 闭环)。
|
||
|
||
params: 订单字段字典(18 字段,见 build_plaintext);fk_extend: tdrc_session 串
|
||
(最终编码形态 tdrc_session%3Dpay-...,不再二次编码);ts: 秒级时间戳;
|
||
rand_val: _rand 值(页面实测 8 字母数字 + 3×\\x03(run A)或 7 字母数字 +
|
||
4×\\x04(run B),调用方自定即可);
|
||
key16/key1: 16 字节密钥,**不影响最终输出密文字节**(E3:双向量随机密钥 1056/1056
|
||
复现;webSave 回调内部重派生密钥),但服务端会校验 key16 与 key1 的派生一致性
|
||
(key16=Sbox[Te 链(key1)],F-2052),key1 必须由 derive_key1_from_key16 从 key16
|
||
反解,不能随意给定;
|
||
args_template: 常量表模板(跨次恒定,缺省用 work/replay/e2e/ws-args.json)。
|
||
"""
|
||
import random as _r
|
||
|
||
if random_seed is not None:
|
||
_r.seed(random_seed)
|
||
replay = REPLAY
|
||
bytecode = json.loads((replay / "bytecode.json").read_text())
|
||
constants = json.loads((replay / "constants.json").read_text())
|
||
if xmidas is None:
|
||
xmidas = json.loads((replay / "xmidasops.json").read_text())
|
||
|
||
window = Window()
|
||
for _k in ("window", "self", "globalThis", "top", "parent", "frames"):
|
||
window.set(_k, window)
|
||
window.set("xMidasOps", xmidas)
|
||
window.set(
|
||
"xMidasToken",
|
||
xmidas_token
|
||
or "DE46DBA4754D42A6B66ADD4319FF80C144D9ED22ED73384C271793D9A9AA2FFBD6C104BE8D4A7F4ED2A8688FCB6F7540",
|
||
)
|
||
window.set("xMidasVersion", "web_1.0.6")
|
||
cfg = JSObject()
|
||
wp = JSObject()
|
||
wp.set("old_encrypt_offerid", ["1450007826", "1110165571"])
|
||
im = JSObject()
|
||
im.set("webpay", wp)
|
||
cfg.set("itemMap", im)
|
||
window.set("__midasStaticConfig_midas_webpay", cfg)
|
||
|
||
vm = VM(bytecode, constants, window)
|
||
window.set("Math", vm._hosts["Math"])
|
||
window.set("Date", vm._hosts["Date"])
|
||
window.set("String", vm._hosts["String"])
|
||
for k in (
|
||
"parseInt",
|
||
"parseFloat",
|
||
"isNaN",
|
||
"encodeURIComponent",
|
||
"encodeURI",
|
||
"decodeURIComponent",
|
||
"decodeURI",
|
||
):
|
||
window.set(k, vm._hosts[k])
|
||
|
||
# 常量表模板(18 参结构,来自 e2e 点击)
|
||
if args_template is None:
|
||
tpl = json.loads(
|
||
json.loads((replay / "e2e" / "ws-args.json").read_text().strip())
|
||
)
|
||
tpl_args = decode_d(tpl["C"][2])
|
||
else:
|
||
tpl_args = args_template
|
||
if cb_json is None:
|
||
cb_json = replay / "deepcaps2" / "cap-69667.json"
|
||
cb_tpl = json.loads(json.loads(cb_json.read_text().strip()))["C"][2]
|
||
cb_args = decode_d(cb_tpl)
|
||
|
||
if key16 is None:
|
||
key16 = [_r.randrange(256) for _ in range(16)]
|
||
if key1 is None:
|
||
key1 = [_r.randrange(256) for _ in range(16)]
|
||
# 回调会原地覆写/消费 key16/key1,避免副作用污染调用方
|
||
key16 = list(key16)
|
||
key1 = list(key1)
|
||
|
||
# 1) 明文 → a:8 变换 → 缓冲
|
||
# a:8(h@39808) 用标准 AES Te0-Te3(F-2046 修正:非 webSave 18 参 args[1..4]),
|
||
# 参数全部以 a:1 包装传入;key16 按全局块索引轮转:out[j] = word_byte[j] ^ key16[(4*blk+j)%16]
|
||
pt = build_plaintext(params, fk_extend, ts, rand_val)
|
||
pb = pt.encode("latin-1")
|
||
# goods VM 只逐个处理完整的 16 字节块,不会为尾块自动填充。
|
||
# 未对齐时继续执行会静默丢弃尾部字段,必须在请求前终止。
|
||
if len(pb) % 16:
|
||
raise ValueError(
|
||
f"webSave 明文必须为 16 字节对齐,当前 {len(pb)} 字节;"
|
||
"请用 _rand 补齐后再生成 encrypt_msg"
|
||
)
|
||
|
||
def _cont(*a):
|
||
return None
|
||
|
||
te_cap = json.loads(
|
||
json.loads((replay / "e2e" / "multi-1.json").read_text().strip())
|
||
)
|
||
te_args = decode_d(te_cap["C"][2])
|
||
te_tables = [te_args[i][0] for i in (2, 3, 4, 5)]
|
||
outbuf = []
|
||
tr_args = [
|
||
[list(pb)],
|
||
[_cont],
|
||
[te_tables[0]],
|
||
[te_tables[1]],
|
||
[te_tables[2]],
|
||
[te_tables[3]],
|
||
[outbuf],
|
||
[key16],
|
||
]
|
||
vm.run(vm.make(39808, tr_args, window, constants, None), [])
|
||
|
||
# 2) 组装 webSave 18 参(表常量 + 随机 key + 变换缓冲)
|
||
args = [None] * 18
|
||
for i in (1, 2, 3, 4, 5, 8, 11, 12, 13, 14, 15, 16):
|
||
args[i] = tpl_args[i]
|
||
args[0] = [key1]
|
||
args[6] = [key16]
|
||
args[7] = tpl_args[7] # [211, 602]
|
||
args[9] = [[]] # 输出密文缓冲(webSave 填充)
|
||
args[10] = [outbuf]
|
||
cb_args[0] = args[10]
|
||
cb_args[1] = args[6]
|
||
cb_args[3] = args[0]
|
||
rand_gen = vm.make(85172, [], window, constants, None)
|
||
cb_args[2] = [rand_gen]
|
||
cb = vm.make(69666, cb_args, window, constants, None)
|
||
args[17] = [cb]
|
||
|
||
init_c = [None] * 8
|
||
init_c[0] = window
|
||
init_c[1] = constants
|
||
init_c[2] = args
|
||
init_c[5] = None
|
||
init_c[6] = bytecode
|
||
web = vm.make(85090, args, window, constants, None)
|
||
init_c[5] = web
|
||
vm.use_init_c = True
|
||
vm.init_c = init_c
|
||
vm.capture_at_return = True
|
||
vm.out_hex = None
|
||
if diagnostics is not None:
|
||
vm.capture_output_lengths = True
|
||
vm.output_length_trace = []
|
||
vm._last_output_length = None
|
||
vm.run(web, [])
|
||
if vm.out_hex is None:
|
||
raise RuntimeError("webSave capture failed")
|
||
if diagnostics is not None:
|
||
diagnostics.update(
|
||
{
|
||
"plaintext_length": len(pb),
|
||
"transform_output_length": len(outbuf),
|
||
"ciphertext_length": len(vm.out_arr),
|
||
"output_length_trace": vm.output_length_trace,
|
||
}
|
||
)
|
||
return vm.out_hex
|