"""encrypt_msg (web_new_encrypt) 生成器 — goodsBiz CHAOS VM 的纯 Python 移植 算法背景(见 FINDINGS.md F-2038..F-2043): - 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]=528B 明文缓冲 / 常量表 / 回调)。 - 输出: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 "" 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 "" 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) # ---- 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): """纯离线生成 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 回调内部重派生密钥,机制见 U-2030); 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") 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 vm.run(web, []) if vm.out_hex is None: raise RuntimeError("webSave capture failed") return vm.out_hex