#!/usr/bin/env python3 """把反编译源码里的混淆动态派发还原成真实方法名。 编辑器里几乎每个调用都被编译成 invokedynamic,反编译出来长这样: yf_2.OB("lKRXTTGf", "<密文>", "Find") 其中 "lKRXTTGf" 是混淆名,Ob/OB/Nb/HW… 是每个类各自的引导方法。 obmap.tsv 给出了每个混淆名真正指向的方法,于是: yf_2.OB("lKRXTTGf", "<密文>", "Find") -> java.lang.String.contains("Find") 实例方法统一写成 类名.方法(接收者, 参数...) 的静态形式 —— 不做参数搬移, 这样永远不会解析错参数边界,读起来照样一目了然。 用法:rewrite_source.py <反编译目录> <输出目录> """ import os import re import sys # X.bsm("混淆名", "<密文>", <- 只吃掉这一段 CALL = re.compile( r'([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)\(\s*"([A-Za-z0-9_$]+)"\s*,\s*"(?:[^"\\]|\\.)*"\s*(?:,\s*)?' ) def load_map(path): mapping = {} with open(path, encoding="utf-8") as handle: for line in handle: parts = line.rstrip("\n").split("\t") if len(parts) < 4: continue name, target = parts[2], parts[3] if target.startswith("<失败>") or target.startswith("?"): continue target = target.replace(" (static)", "") mapping.setdefault(name, target) return mapping def main(): source_dir, map_path, out_dir = sys.argv[1], sys.argv[2], sys.argv[3] mapping = load_map(map_path) print("映射条目 %d" % len(mapping)) files = replaced_total = files_touched = 0 missed = set() for root, _, names in os.walk(source_dir): for name in names: if not name.endswith(".java"): continue files += 1 path = os.path.join(root, name) with open(path, encoding="utf-8", errors="replace") as handle: text = handle.read() count = [0] def swap(match): target = mapping.get(match.group(3)) if target is None: missed.add(match.group(3)) return match.group(0) count[0] += 1 return target + "(" new_text = CALL.sub(swap, text) if count[0]: files_touched += 1 replaced_total += count[0] relative = os.path.relpath(path, source_dir) destination = os.path.join(out_dir, relative) os.makedirs(os.path.dirname(destination), exist_ok=True) with open(destination, "w", encoding="utf-8") as handle: handle.write(new_text) print("扫描 %d 个文件,改写 %d 个文件,替换 %d 处调用" % (files, files_touched, replaced_total)) print("没映射上的混淆名 %d 个" % len(missed)) if __name__ == "__main__": main()