ksjsb/tools/deobf.py
2026-07-30 20:25:56 +08:00

122 lines
4.0 KiB
Python
Raw Permalink 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.

"""反混淆 iframe bundle解码 Na/Ia 字符串表并全文替换,输出可读 JS。
Na(e){ e-=0; t=Ia[e]; ... HaWBWy(t)=utf8(atob(t)) ... }
洗牌: (function(e,n){var t=function(n){while(--n)e.push(e.shift())};t(++n)})(Ia,292) => 292 次旋转
"""
import re, sys, json, base64
from pathlib import Path
SRC = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("js_08_atic_captcha_js_iframe_index_c9ae8c81_js.js")
s = SRC.read_text(encoding="utf-8", errors="ignore")
def extract_array_literal(src: str, name: str) -> list[str]:
"""从 `var NAME=[` 起,字符串感知地扫描到匹配的 `]`,返回元素字符串列表。"""
m = re.search(r"var\s+" + re.escape(name) + r"\s*=\s*\[", src)
if not m:
raise SystemExit(f"[!] array {name} not found")
i = m.end() # 指向第一个元素
elems = []
depth = 1
cur = []
in_str = False
q = ""
esc = False
j = i
while j < len(src):
ch = src[j]
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 = True
q = ch
cur.append(ch)
elif ch == "[":
depth += 1
cur.append(ch)
elif ch == "]":
depth -= 1
if depth == 0:
if cur:
elems.append("".join(cur))
break
cur.append(ch)
elif ch == "," and depth == 1:
elems.append("".join(cur))
cur = []
else:
cur.append(ch)
j += 1
# 每个 elem 形如 "b64" 或 'b64',去掉引号与空白
out = []
for e in elems:
e = e.strip()
if not e:
continue
if e[0] in "\"'`":
e = e[1:-1]
out.append(e)
return out
def decode_elem(raw: str) -> str:
"""HaWBWy: atob(raw) 的字节按 %xx 拼接后 decodeURIComponent = base64 解码再 UTF-8 解码。"""
try:
return base64.b64decode(raw).decode("utf-8")
except Exception:
try:
return base64.b64decode(raw + "==").decode("utf-8", "replace")
except Exception:
return f"<DECODE_FAIL:{raw}>"
def rotate(arr: list[str], n: int) -> list[str]:
a = list(arr)
# 292 次 push(shift())
for _ in range(n):
a.append(a.pop(0))
return a
def build_decoder_map(src: str, arr_name: str, dec_name: str, rotate_n: int, offset: int) -> dict[int, str]:
raw = extract_array_literal(src, arr_name)
rotated = rotate(raw, rotate_n)
return {i: decode_elem(v) for i, v in enumerate(rotated)}
# ---- Na / Ia ----
mp = build_decoder_map(s, "Ia", "Na", 292, 0)
print(f"[Na] Ia 元素数={len(mp)}, 示例 0x0={mp.get(0)!r} 0xc7={mp.get(0xc7)!r} 0x28={mp.get(0x28)!r}")
# 全文替换 Na("0xNN") -> 字面量
def js_quote(st: str) -> str:
return json.dumps(st, ensure_ascii=False)
def repl(m):
idx = int(m.group(1), 16)
val = mp.get(idx)
if val is None:
return m.group(0)
return js_quote(val)
out = re.sub(r'Na\(\s*"(0x[0-9a-fA-F]+)"\s*,?\s*(?:[^)]*)?\)', lambda m: repl(m) if int(m.group(1),16) in mp else m.group(0), s)
# 兜底Na('0x..') 单参
out = re.sub(r"Na\(\s*'(0x[0-9a-fA-F]+)'\s*\)", lambda m: repl(m) if int(m.group(1),16) in mp else m.group(0), out)
# Na("0x..") 单参(上面 regex 已含,但确保无第二参情况)
out = re.sub(r'Na\(\s*"(0x[0-9a-fA-F]+)"\s*\)', lambda m: repl(m) if int(m.group(1),16) in mp else m.group(0), out)
dest = SRC.with_name(SRC.stem + ".deobf.js")
dest.write_text(out, encoding="utf-8")
print(f"[+] 写出 {dest} ({len(out)} chars, 原 {len(s)})")
# 顺带把 Na 表导出,便于增量解码别的表
json.dump({f"0x{k:x}": v for k, v in mp.items()}, open(SRC.with_name("na_table.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print("[+] Na 表导出 na_table.json")