122 lines
4.0 KiB
Python
122 lines
4.0 KiB
Python
"""反混淆 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")
|