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

2180 lines
74 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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(
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__ = ("entry", "args", "s", "n", "t", "vm", "name")
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"{str(fn)} 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