"""反混淆 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"" 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")