"""通用多表反混淆器:自动探测所有 obfuscator.io 字符串表并全文替换。 每张表结构: var ARR=["b64",...]; (function(e,n){var t=function(n){while(--n)e["push"](e["shift"]())};t(++n)})(ARR,N); // N 次旋转 var DEC=function(e,n){e-=K;var t=ARR[e]; ... CpCnJS(t)=utf8(atob(t)) ...} 解码: rotated_ARR[e-K] 经 base64->utf8. """ import re, sys, json, base64 from pathlib import Path SRC = Path(sys.argv[1]) s = SRC.read_text(encoding="utf-8", errors="ignore") def bracket_extract(src: str, open_idx: int) -> int: """给定 '[' 的位置(已位于 '['),返回匹配 ']' 的位置。字符串感知。""" depth = 0 in_str = False q = "" esc = False j = open_idx while j < len(src): ch = src[j] if in_str: if esc: esc = False elif ch == "\\": esc = True elif ch == q: in_str = False else: if ch in "\"'`": in_str = True q = ch elif ch == "[": depth += 1 elif ch == "]": depth -= 1 if depth == 0: return j j += 1 return -1 def parse_array_literal(src: str, arr: str, before: int = -1) -> list[str]: """找 arr=[ 字面量。before>=0 时只在该偏移之前找最近一个(按邻近消歧跨模块短名冲突)。""" pat = re.compile(r"(?= 0: matches = [m for m in matches if m.start() < before] if not matches: return [] m = matches[-1] # 离解码器最近的那个 ob = m.end() - 1 # 指向 '[' cb = bracket_extract(src, ob) body = src[ob + 1:cb] # 拆分顶层逗号 out, cur, in_str, q, esc, depth = [], [], False, "", False, 0 for ch in body: if in_str: cur.append(ch) if esc: esc = False elif ch == "\\": esc = True elif ch == q: in_str = False else: if ch in "\"'`": in_str, q = True, ch; cur.append(ch) elif ch == "[": depth += 1; cur.append(ch) elif ch == "]": depth -= 1; cur.append(ch) elif ch == "," and depth == 0: out.append("".join(cur)); cur = [] else: cur.append(ch) if "".join(cur).strip(): out.append("".join(cur)) res = [] for e in out: e = e.strip() if e and e[0] in "\"'`": e = e[1:-1] res.append(e) return res def b64utf8(raw: str) -> str: try: return base64.b64decode(raw).decode("utf-8") except Exception: try: return base64.b64decode(raw + "==").decode("utf-8", "replace") except Exception: return f"" def rotate(arr, n): a = list(arr) for _ in range(n): a.append(a.pop(0)) return a # 1) 探测所有解码器: var DEC=function(e,n){e-=K;var t=ARR[e]; decoders = [] for m in re.finditer(r"var\s+(\w+)\s*=\s*function\(e,n\)\{e-=(\d+);var t=(\w+)\[e\];", s): decoders.append((m.group(1), int(m.group(2)), m.group(3), m.start())) print(f"[+] 探测到 {len(decoders)} 个解码器") # 2) 构建 map 并替换(每个解码器用自己偏移前的数组 + 自己的旋转数) out = s tables = {} for dec, off, arr, doff in decoders: raw = parse_array_literal(s, arr, before=doff) if not raw: print(f" [-] {dec}/{arr}@{doff}: 数组字面量未找到") continue # 旋转数: 解码器前最近的 })(ARR,N) rpat = re.compile(r"\}\)\(" + re.escape(arr) + r",(\d+)\)") rm = [x for x in rpat.finditer(s) if x.start() < doff] rot = int(rm[-1].group(1)) if rm else 0 rotated = rotate(raw, rot) mp = {i: b64utf8(v) for i, v in enumerate(rotated)} tables[dec] = (arr, off, rot, mp) def make_repl(mp, off): def repl(m): idx = int(m.group(1), 16) - off v = mp.get(idx) return json.dumps(v, ensure_ascii=False) if v is not None else m.group(0) return repl out = re.sub(re.escape(dec) + r'\(\s*"(0x[0-9a-fA-F]+)"\s*\)', make_repl(mp, off), out) out = re.sub(re.escape(dec) + r"\(\s*'(0x[0-9a-fA-F]+)'\s*\)", make_repl(mp, off), out) print(f" [+] {dec}/{arr}@{doff}: {len(mp)} 项, rotate={rot}, off={off}, 0x0={mp.get(0)!r}") dest = SRC.with_name(SRC.stem + ".deobf2.js") dest.write_text(out, encoding="utf-8") print(f"\n[+] 写出 {dest}") json.dump({dec: {f"0x{int(k)+off:x}": v for k, v in info[3].items()} for dec, info in tables.items()}, open(SRC.with_name("all_tables.json"), "w", encoding="utf-8"), ensure_ascii=False) print("[+] 全表导出 all_tables.json")