133 lines
4.6 KiB
Python
133 lines
4.6 KiB
Python
"""通用多表反混淆器:自动探测所有 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"(?<![A-Za-z0-9_$.])" + re.escape(arr) + r"\s*=\s*\[")
|
|
matches = list(pat.finditer(src))
|
|
if before >= 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"<FAIL:{raw}>"
|
|
|
|
|
|
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")
|