diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 2efb20fed366e4eab53f22d34500ca40306462ab..9d7ea6e5db249169b09a54441403cf9d24ebb869 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -50,6 +50,18 @@ public final class JadxCLIArgs implements IJadxArgs { @Parameter(names = {"-v", "--verbose"}, description = "verbose output") protected boolean verbose = false; + @Parameter(names = {"--deobf"}, description = "activate deobfuscation") + protected boolean deobfuscationOn = false; + + @Parameter(names = {"--deobf-min"}, description = "min length of name") + protected int deobfuscationMinLength = 2; + + @Parameter(names = {"--deobf-max"}, description = "max length of name") + protected int deobfuscationMaxLength = 40; + + @Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map") + protected boolean deobfuscationForceSave = false; + @Parameter(names = {"-h", "--help"}, description = "print this help", help = true) protected boolean printHelp = false; @@ -209,4 +221,24 @@ public final class JadxCLIArgs implements IJadxArgs { public boolean isVerbose() { return verbose; } + + @Override + public boolean isDeobfuscationOn() { + return deobfuscationOn; + } + + @Override + public int getDeobfuscationMinLength() { + return deobfuscationMinLength; + } + + @Override + public int getDeobfuscationMaxLength() { + return deobfuscationMaxLength; + } + + @Override + public boolean isDeobfuscationForceSave() { + return deobfuscationForceSave; + } } diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index cb845706afb49745c74fae910cdd17575e5047b3..9ea24cd29e7095102902a6cc5218f5b63f5389f4 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -6,6 +6,7 @@ dependencies { runtime files(jadxClasspath) compile files('lib/dx-1.10.jar') + compile 'commons-io:commons-io:2.4' compile 'org.ow2.asm:asm:5.0.3' compile 'com.intellij:annotations:12.0' diff --git a/jadx-core/src/main/java/jadx/api/DefaultJadxArgs.java b/jadx-core/src/main/java/jadx/api/DefaultJadxArgs.java index 114b8d6e4abf2db560130b94131babd938d76588..9618dd29bc87ac1dad8ba04320678bf6120ff623 100644 --- a/jadx-core/src/main/java/jadx/api/DefaultJadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/DefaultJadxArgs.java @@ -48,4 +48,24 @@ public class DefaultJadxArgs implements IJadxArgs { public boolean isSkipSources() { return false; } + + @Override + public boolean isDeobfuscationOn() { + return false; + } + + @Override + public int getDeobfuscationMinLength() { + return Integer.MIN_VALUE+1; + } + + @Override + public int getDeobfuscationMaxLength() { + return Integer.MAX_VALUE-1; + } + + @Override + public boolean isDeobfuscationForceSave() { + return false; + } } diff --git a/jadx-core/src/main/java/jadx/api/IJadxArgs.java b/jadx-core/src/main/java/jadx/api/IJadxArgs.java index 424118163678a84906fd481d8fe5255f507d940c..c5bcfa34ddd2e05885995ef22692b69f7081d414 100644 --- a/jadx-core/src/main/java/jadx/api/IJadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/IJadxArgs.java @@ -20,4 +20,12 @@ public interface IJadxArgs { boolean isSkipResources(); boolean isSkipSources(); + + boolean isDeobfuscationOn(); + + int getDeobfuscationMinLength(); + + int getDeobfuscationMaxLength(); + + boolean isDeobfuscationForceSave(); } diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 94e6e880f7fffd1ff7844ad234f01eff1e46fb28..215f0106b2acb4f134625a0724587caebba26aba 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -3,6 +3,8 @@ package jadx.api; import jadx.core.Jadx; import jadx.core.ProcessClass; import jadx.core.codegen.CodeWriter; +import jadx.core.deobf.DefaultDeobfuscator; +import jadx.core.deobf.Deobfuscator; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.RootNode; @@ -253,6 +255,51 @@ public final class JadxDecompiler { root = new RootNode(); LOG.info("loading ..."); root.load(inputFiles); + + if (args.isDeobfuscationOn()) { + final String firstInputFileName = inputFiles.get(0).getFile().getAbsolutePath(); + final String inputPath = org.apache.commons.io.FilenameUtils.getFullPathNoEndSeparator( + firstInputFileName); + final String inputName = org.apache.commons.io.FilenameUtils.getBaseName(firstInputFileName); + + final File deobfuscationMapFile = new File(inputPath, inputName + ".jobf"); + + DefaultDeobfuscator deobfuscator = new DefaultDeobfuscator(); + + if (deobfuscationMapFile.exists()) { + try { + deobfuscator.load(deobfuscationMapFile); + } catch (IOException e) { + LOG.error("Failed to load deobfuscation map file '{}'", + deobfuscationMapFile.getAbsolutePath()); + } + } + + deobfuscator.setInputData(root.getDexNodes()); + deobfuscator.setMinNameLength(args.getDeobfuscationMinLength()); + deobfuscator.setMaxNameLength(args.getDeobfuscationMaxLength()); + + deobfuscator.process(); + + try { + if (deobfuscationMapFile.exists()) { + if (args.isDeobfuscationForceSave()) { + deobfuscator.save(deobfuscationMapFile); + } else { + LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf=rewrite-cfg'" + + " to rewrite it", deobfuscationMapFile.getAbsolutePath()); + } + } else { + deobfuscator.save(deobfuscationMapFile); + } + } catch (IOException e) { + LOG.error("Failed to load deobfuscation map file '{}'", + deobfuscationMapFile.getAbsolutePath()); + } + + Deobfuscator.setDeobfuscator(deobfuscator); + } + root.loadResources(getResources()); root.initAppResClass(); } diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 6df20d0fbcddade5cf452881eb73c4a3c5e78865..706c2e80f13bc9ad98b17350a76b0ec19875a128 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -1,6 +1,7 @@ package jadx.api; import jadx.core.codegen.CodeWriter; +import jadx.core.deobf.Deobfuscator; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.info.AccessInfo; @@ -150,16 +151,16 @@ public final class JavaClass implements JavaNode { @Override public String getName() { - return cls.getShortName(); + return Deobfuscator.instance().getClassShortName(cls); } @Override public String getFullName() { - return cls.getFullName(); + return Deobfuscator.instance().getClassFullName(cls); } public String getPackage() { - return cls.getPackage(); + return Deobfuscator.instance().getPackageName(cls.getPackage()); } @Override @@ -202,6 +203,6 @@ public final class JavaClass implements JavaNode { @Override public String toString() { - return getFullName(); + return cls.getFullName() + "[ " + getFullName() + " ]"; } } diff --git a/jadx-core/src/main/java/jadx/api/JavaPackage.java b/jadx-core/src/main/java/jadx/api/JavaPackage.java index 9af54572917eea9173730b8322014049aaa04bc1..1256dc0614d1e2ba82b5bed13ca5c1c4ce8cd4af 100644 --- a/jadx-core/src/main/java/jadx/api/JavaPackage.java +++ b/jadx-core/src/main/java/jadx/api/JavaPackage.java @@ -1,5 +1,7 @@ package jadx.api; +import jadx.core.deobf.Deobfuscator; + import java.util.List; import org.jetbrains.annotations.NotNull; @@ -9,7 +11,7 @@ public final class JavaPackage implements JavaNode, Comparable { private final List classes; JavaPackage(String name, List classes) { - this.name = name; + this.name = Deobfuscator.instance().getPackageName(name); this.classes = classes; } diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index c3cc1bd9e64380a47a854ceaf524bc028820f451..ecf5d2be8ab33ed0654863adb48022f269d09492 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -2,6 +2,7 @@ package jadx.core.codegen; import jadx.api.IJadxArgs; import jadx.core.Consts; +import jadx.core.deobf.Deobfuscator; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AttrNode; @@ -80,14 +81,14 @@ public class ClassGen { CodeWriter clsCode = new CodeWriter(); if (!"".equals(cls.getPackage())) { - clsCode.add("package ").add(cls.getPackage()).add(';'); + clsCode.add("package ").add(Deobfuscator.instance().getPackageName(cls.getPackage())).add(';'); clsCode.newLine(); } int importsCount = imports.size(); if (importsCount != 0) { List sortImports = new ArrayList(importsCount); for (ClassInfo ic : imports) { - sortImports.add(ic.getFullName()); + sortImports.add(Deobfuscator.instance().getClassFullName(ic)); } Collections.sort(sortImports); @@ -142,7 +143,7 @@ public class ClassGen { } else { clsCode.add("class "); } - clsCode.add(cls.getShortName()); + clsCode.add(Deobfuscator.instance().getClassShortName(cls)); addGenericMap(clsCode, cls.getGenericMap()); clsCode.add(' '); @@ -453,7 +454,8 @@ public class ClassGen { if (fallback) { return fullName; } - String shortName = classInfo.getShortName(); + fullName = Deobfuscator.instance().getClassFullName(classInfo); + String shortName = Deobfuscator.instance().getClassShortName(classInfo); if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) { return shortName; } else { @@ -474,7 +476,7 @@ public class ClassGen { return fullName; } if (classInfo.getPackage().equals(useCls.getPackage())) { - fullName = classInfo.getNameWithoutPackage(); + fullName = Deobfuscator.instance().getClassName(classInfo); } for (ClassInfo importCls : getImports()) { if (!importCls.equals(classInfo) diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index b6d9c65b8d689ac9d6154296886bf33ca0427323..da7267ef61bfdefab592507084e64406ccb87624 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -1,5 +1,6 @@ package jadx.core.codegen; +import jadx.core.deobf.Deobfuscator; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.annotations.MethodParameters; @@ -85,7 +86,7 @@ public class MethodGen { code.add(' '); } if (mth.getAccessFlags().isConstructor()) { - code.add(classGen.getClassNode().getShortName()); // constructor + code.add(Deobfuscator.instance().getClassShortName(classGen.getClassNode())); // constructor } else { classGen.useType(code, mth.getReturnType()); code.add(' '); diff --git a/jadx-core/src/main/java/jadx/core/deobf/DefaultDeobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/DefaultDeobfuscator.java new file mode 100644 index 0000000000000000000000000000000000000000..36ef399e9f6ae05f11b3f64557c388bd434cd94c --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/DefaultDeobfuscator.java @@ -0,0 +1,415 @@ +package jadx.core.deobf; + +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.DexNode; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultDeobfuscator implements IDeobfuscator { + private static final Logger LOG = LoggerFactory.getLogger(DefaultDeobfuscator.class); + + private static final boolean DEBUG = false; + + public static final char classNameSepearator = '.'; + + private int maxLength = 40; + private int minLength = 2; + + private int pkgIndex = 0; + private int clsIndex = 0; + + private List dexNodes; + private static PackageNode rootPackage = new PackageNode(""); + + private static final String MAP_FILE_CHARSET = "UTF-8"; + + /** + * Gets package node for full package name + * + * @param fullPkgName full package name + * @param _creat if {@code true} then will create all absent objects + * + * @return package node object or {@code null} if no package found and _creat set to {@code false} + */ + public static PackageNode getPackageNode(String fullPkgName, boolean _creat) { + if (fullPkgName.isEmpty() || fullPkgName.equals(classNameSepearator)) { + return rootPackage; + } + + PackageNode result = rootPackage; + PackageNode parentNode; + do { + String pkgName; + int idx = fullPkgName.indexOf(classNameSepearator); + + if (idx > -1) { + pkgName = fullPkgName.substring(0, idx); + fullPkgName = fullPkgName.substring(idx+1); + } else { + pkgName = fullPkgName; + fullPkgName = ""; + } + + parentNode = result; + result = result.getInnerPackageByName(pkgName); + if ((result == null) && (_creat)) { + result = new PackageNode(pkgName); + parentNode.addInnerPackage(result); + } + } while (!fullPkgName.isEmpty() && (result != null)); + + return result; + } + + private class DefaultDeobfuscatorClassInfo { + public ClassNode cls; + public PackageNode pkg; + public String alias; + + public DefaultDeobfuscatorClassInfo(ClassNode cls, PackageNode pkg) { + this.cls = cls; + this.pkg = pkg; + } + + public String getNameWithoutPackage(DefaultDeobfuscatorClassInfo deobfClsInfo) { + final ClassNode clsNode = deobfClsInfo.cls; + String prefix; + ClassNode parentClass = clsNode.getParentClass(); + if (parentClass != clsNode) { + DefaultDeobfuscatorClassInfo parentDeobfClassInfo = DefaultDeobfuscator.clsMap.get(parentClass.getClassInfo()); + + if (parentDeobfClassInfo != null) { + prefix = getNameWithoutPackage(parentDeobfClassInfo) + DefaultDeobfuscator.classNameSepearator; + } else { + prefix = DefaultDeobfuscator.getNameWithoutPackage(parentClass.getClassInfo()) + DefaultDeobfuscator.classNameSepearator; + } + } else { + prefix = ""; + } + + return prefix + ((deobfClsInfo.alias != null) ? deobfClsInfo.alias : deobfClsInfo.cls.getShortName()); + } + + public String getFullName() { + return pkg.getFullAlias() + DefaultDeobfuscator.classNameSepearator + getNameWithoutPackage(this); + } + } + + private Map preloadClsMap = Collections.emptyMap(); + private static Map clsMap = new HashMap(); + + public static String getNameWithoutPackage(ClassInfo clsInfo) { + String prefix; + ClassInfo parentClsInfo = clsInfo.getParentClass(); + if (parentClsInfo != null) { + DefaultDeobfuscatorClassInfo parentDeobfClsInfo = DefaultDeobfuscator.clsMap.get(parentClsInfo); + + if (parentDeobfClsInfo != null) { + prefix = parentDeobfClsInfo.getNameWithoutPackage(parentDeobfClsInfo) + DefaultDeobfuscator.classNameSepearator; + } else { + prefix = getNameWithoutPackage(parentClsInfo) + DefaultDeobfuscator.classNameSepearator; + } + } else { + prefix = ""; + } + return prefix + clsInfo.getShortName(); + } + + private void doClass(ClassNode cls) { + final String pkgFullName = cls.getPackage(); + + PackageNode pkg = getPackageNode(pkgFullName, true); + doPkg(pkg, pkgFullName); + + if (preloadClsMap.containsKey(cls.getFullName())) { + DefaultDeobfuscatorClassInfo clsInfo = new DefaultDeobfuscatorClassInfo(cls, pkg); + clsInfo.alias = preloadClsMap.get(cls.getFullName()); + clsMap.put(cls.getClassInfo(), clsInfo); + return; + } + + if (clsMap.containsKey(cls)) { + return; + } + + final String className = cls.getShortName(); + if (shouldRename(className)) { + DefaultDeobfuscatorClassInfo clsInfo = new DefaultDeobfuscatorClassInfo(cls, pkg); + + clsInfo.alias = String.format("C%04d%s", clsIndex++, short4LongName(className)); + clsMap.put(cls.getClassInfo(), clsInfo); + } + } + + private String short4LongName(String name) { + if (name.length() > maxLength) { + return "x" + Integer.toHexString(name.hashCode()); + } else { + return name; + } + } + + private Set pkgSet = new TreeSet(); + + private void doPkg(PackageNode pkg, String fullName) { + if (pkgSet.contains(fullName)) { + return; + } + pkgSet.add(fullName); + + // doPkg for all parent packages except root that not hasAlisas + PackageNode parentPkg = pkg.getParentPackage(); + while (!parentPkg.getName().isEmpty()) { + if (!parentPkg.hasAlias()) { + doPkg(parentPkg, parentPkg.getFullName()); + } + parentPkg = parentPkg.getParentPackage(); + } + + final String pkgName = pkg.getName(); + if (shouldRename(pkgName) && !pkg.hasAlias()) { + final String pkgAlias = String.format("p%03d%s", pkgIndex++, short4LongName(pkgName)); + pkg.setAlias(pkgAlias); + } + } + + private void preprocess() { + if (dexNodes != null) { + for (DexNode dexNode : dexNodes) { + for (ClassNode cls : dexNode.getClasses()) { + doClass(cls); + } + } + } + } + + private boolean shouldRename(String s) { + return s.length() > maxLength || s.length() < minLength || NameMapper.isReserved(s); + } + + private void dumpClassAlias(ClassNode cls) { + PackageNode pkg = getPackageNode(cls.getPackage(), false); + + if (pkg != null) { + if (!cls.getFullName().equals(getClassFullName(cls))) { + LOG.info("Alias name for class '{}' is '{}'", cls.getFullName(), getClassFullName(cls)); + } + } else { + LOG.error("Can't find package node for '{}'", cls.getPackage()); + } + } + + private void dumpAlias() { + for (DexNode dexNode : dexNodes) { + for (ClassNode cls : dexNode.getClasses()) { + dumpClassAlias(cls); + } + } + } + + /** + * Sets input data for processing + * + * @param nodes + * + * @return @{code this} + */ + public DefaultDeobfuscator setInputData(List nodes) { + this.dexNodes = nodes; + return this; + } + + /** + * Sets minimum name length, if name length lesser than value, + * DefaultDeobfuscator will work + * + * @param value + * + * @return @{code this} + */ + public DefaultDeobfuscator setMinNameLength(int value) { + this.minLength = value; + return this; + } + + /** + * Sets maximum name length, if name length greater than value, + * DefaultDeobfuscator will work + * + * @param value + * + * @return @{code this} + */ + public DefaultDeobfuscator setMaxNameLength(int value) { + this.maxLength = value; + return this; + } + + /** + * Loads DefaultDeobfuscator presets + * + * @param config + * @throws IOException + */ + public void load(File mapFile) throws IOException { + if (mapFile.exists()) { + List lines = FileUtils.readLines(mapFile, MAP_FILE_CHARSET); + + for (String l : lines) { + if (l.startsWith("p ")) { + final String rule = l.substring(2); + final String va[] = rule.split("="); + + if (va.length == 2) { + PackageNode pkg = getPackageNode(va[0], true); + pkg.setAlias(va[1]); + } + } else if (l.startsWith("c ")) { + final String rule = l.substring(2); + final String va[] = rule.split("="); + + if (va.length == 2) { + if (preloadClsMap.isEmpty()) { + preloadClsMap = new HashMap(); + } + preloadClsMap.put(va[0], va[1]); + } + } + } + } + } + + public void process() { + preprocess(); + if (DEBUG) { + dumpAlias(); + } + + preloadClsMap.clear(); + preloadClsMap = Collections.emptyMap(); + } + + private static void dfsPackageName(List list, String prefix, PackageNode node) { + for (PackageNode pp : node.getInnerPackages()) { + dfsPackageName(list, prefix + '.' + node.getName(), pp); + } + + if (node.hasAlias()) { + list.add(String.format("p %s.%s=%s", prefix, node.getName(), node.getAlias())); + } + } + + /** + * Saves DefaultDeobfuscator presets + * + * @param mapFile + * @throws IOException + */ + public void save(File mapFile) throws IOException { + List list = new ArrayList(); + + // packages + for (PackageNode p : rootPackage.getInnerPackages()) { + for (PackageNode pp : p.getInnerPackages()) { + dfsPackageName(list, p.getName(), pp); + } + + if (p.hasAlias()) { + list.add(String.format("p %s=%s", p.getName(), p.getAlias())); + } + } + + // classes + for (DefaultDeobfuscatorClassInfo deobfClsInfo : clsMap.values()) { + if (deobfClsInfo.alias != null) { + list.add(String.format("c %s=%s", deobfClsInfo.cls.getFullName(), deobfClsInfo.alias)); + } + } + + FileUtils.writeLines(mapFile, MAP_FILE_CHARSET, list); + list.clear(); + } + + + @Override + public String getPackageName(String packageName) { + final PackageNode pkg = getPackageNode(packageName, false); + if (pkg != null) { + return pkg.getFullAlias(); + } + return packageName; + } + + @Override + public String getClassShortName(ClassNode cls) { + return getClassShortName(cls.getClassInfo()); + } + + @Override + public String getClassShortName(ClassInfo clsInfo) { + final DefaultDeobfuscatorClassInfo deobfClsInfo = clsMap.get(clsInfo); + if (deobfClsInfo != null) { + return (deobfClsInfo.alias != null) ? deobfClsInfo.alias : clsInfo.getShortName(); + } + + return clsInfo.getShortName(); + } + + @Override + public String getClassName(ClassNode cls) { + return getClassName(cls.getClassInfo()); + } + + @Override + public String getClassName(ClassInfo clsInfo) { + final DefaultDeobfuscatorClassInfo deobfClsInfo = clsMap.get(clsInfo); + if (deobfClsInfo != null) { + return deobfClsInfo.getNameWithoutPackage(deobfClsInfo); + } + + return getNameWithoutPackage(clsInfo); + } + + @Override + public String getClassFullName(ClassNode cls) { + return getClassFullName(cls.getClassInfo()); + } + + @Override + public String getClassFullName(ClassInfo clsInfo) { + final DefaultDeobfuscatorClassInfo deobfClsInfo = clsMap.get(clsInfo); + if (deobfClsInfo != null) { + return deobfClsInfo.getFullName(); + } + + return getPackageName(clsInfo.getPackage()) + DefaultDeobfuscator.classNameSepearator + getClassName(clsInfo); + } + + @Override + public String getClassFullPath(ClassInfo clsInfo) { + final DefaultDeobfuscatorClassInfo deobfClsInfo = clsMap.get(clsInfo); + if (deobfClsInfo != null) { + return deobfClsInfo.pkg.getFullAlias().replace(DefaultDeobfuscator.classNameSepearator, File.separatorChar) + + File.separatorChar + + deobfClsInfo.getNameWithoutPackage(deobfClsInfo).replace(DefaultDeobfuscator.classNameSepearator, '_'); + } + + + return getPackageName(clsInfo.getPackage()).replace('.', File.separatorChar) + + File.separatorChar + + clsInfo.getNameWithoutPackage().replace('.', '_'); + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java new file mode 100644 index 0000000000000000000000000000000000000000..a9e966b987df4124df474033176b40ff62d5a8a0 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -0,0 +1,37 @@ +package jadx.core.deobf; + +public class Deobfuscator { + + private static final StubDeobfuscator stubDeobfuscator; + private static IDeobfuscator deobfuscatorInstance; + + static { + stubDeobfuscator = new StubDeobfuscator(); + deobfuscatorInstance = stubDeobfuscator; + } + + /** + * Gets instance of active deobfuscator + * + * @return deobfuscator instance + */ + public static IDeobfuscator instance() { + return deobfuscatorInstance; + } + + /** + * Sets active deobfuscator + * + * @param deobfuscator object that makes deobfuscation or {@code null} + * to set stub deobfuscator + * + */ + public static void setDeobfuscator(IDeobfuscator deobfuscator) { + if (deobfuscator != null) { + deobfuscatorInstance = deobfuscator; + } else { + deobfuscatorInstance = stubDeobfuscator; + } + } + +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/IDeobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/IDeobfuscator.java new file mode 100644 index 0000000000000000000000000000000000000000..16ea78bc97e5b9f0470af7ddddce7455d70081c1 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/IDeobfuscator.java @@ -0,0 +1,18 @@ +package jadx.core.deobf; + +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.nodes.ClassNode; + +public interface IDeobfuscator { + + public String getPackageName(String packageName); + + public String getClassShortName(ClassNode cls); + public String getClassShortName(ClassInfo clsInfo); + public String getClassName(ClassNode cls); + public String getClassName(ClassInfo clsInfo); + public String getClassFullName(ClassNode cls); + public String getClassFullName(ClassInfo clsInfo); + + public String getClassFullPath(ClassInfo clsInfo); +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java b/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java new file mode 100644 index 0000000000000000000000000000000000000000..c95dd6fa1ad054430b558c0a0a9ad0902e21b558 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java @@ -0,0 +1,147 @@ +package jadx.core.deobf; + +import jadx.core.dex.nodes.ClassNode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +public class PackageNode { + + private PackageNode parentPackage; + private List innerClasses = Collections.emptyList(); + private List innerPackages = Collections.emptyList(); + + public static final char separatorChar = '.'; + + private String packageName; + private String packageAlias; + + private String cachedPackageFullName; + private String cachedPackageFullAlias; + + public PackageNode(String packageName) { + this.packageName = packageName; + this.parentPackage = this; + } + + public String getName() { + return packageName; + } + + public String getFullName() { + if (cachedPackageFullName == null) { + Stack pp = getParentPackages(); + + StringBuilder result = new StringBuilder(); + result.append(pp.pop().getName()); + while (pp.size() > 0) { + result.append(separatorChar); + result.append(pp.pop().getName()); + } + + cachedPackageFullName = result.toString(); + } + + return cachedPackageFullName; + } + + public String getAlias() { + if (packageAlias != null) { + return packageAlias; + } + + return packageName; + } + + public void setAlias(String alias) { + packageAlias = alias; + } + + public boolean hasAlias() { + return (packageAlias != null); + } + + public String getFullAlias() { + if (cachedPackageFullAlias == null) { + Stack pp = getParentPackages(); + StringBuilder result = new StringBuilder(); + result.append(pp.pop().getAlias()); + while (pp.size() > 0) { + result.append(separatorChar); + result.append(pp.pop().getAlias()); + } + + cachedPackageFullAlias = result.toString(); + } + + return cachedPackageFullAlias; + } + + public PackageNode getParentPackage() { + return parentPackage; + } + + public List getInnerPackages() { + return innerPackages; + } + + public List getInnerClasses() { + return innerClasses; + } + + public void addInnerClass(ClassNode cls) { + if (innerClasses.isEmpty()) { + innerClasses = new ArrayList(); + } + innerClasses.add(cls); + } + + public void addInnerPackage(PackageNode pkg) { + if (innerPackages.isEmpty()) { + innerPackages = new ArrayList(); + } + innerPackages.add(pkg); + pkg.parentPackage = this; + } + + /** + * Gets inner package node by name + * + * @param name inner package name + * + * @return package node or {@code null} + */ + public PackageNode getInnerPackageByName(String name) { + PackageNode result = null; + for (PackageNode p : innerPackages) { + if (p.getName().equals(name)) { + result = p; + break; + } + } + + return result; + } + + /** + * Fills stack with parent packages exclude root node + * + * @return stack with parent packages + */ + private Stack getParentPackages() { + Stack pp = new Stack(); + + PackageNode currentP = this; + PackageNode parentP = currentP.getParentPackage(); + + while (currentP != parentP) { + pp.push(currentP); + currentP = parentP; + parentP = currentP.getParentPackage(); + } + + return pp; + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/StubDeobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/StubDeobfuscator.java new file mode 100644 index 0000000000000000000000000000000000000000..862fa78fde03f86536d95667763674f1ccbb63cb --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/StubDeobfuscator.java @@ -0,0 +1,48 @@ +package jadx.core.deobf; + +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.nodes.ClassNode; + +public class StubDeobfuscator implements IDeobfuscator { + + @Override + public String getPackageName(String packageName) { + return packageName; + } + + @Override + public String getClassShortName(ClassNode cls) { + return cls.getShortName(); + } + + @Override + public String getClassShortName(ClassInfo clsInfo) { + return clsInfo.getShortName(); + } + + @Override + public String getClassName(ClassNode cls) { + return cls.getClassInfo().getNameWithoutPackage(); + } + + @Override + public String getClassName(ClassInfo clsInfo) { + return clsInfo.getNameWithoutPackage(); + } + + @Override + public String getClassFullName(ClassNode cls) { + return cls.getFullName(); + } + + @Override + public String getClassFullName(ClassInfo clsInfo) { + return clsInfo.getFullName(); + } + + @Override + public String getClassFullPath(ClassInfo clsInfo) { + return clsInfo.getFullPath(); + } + +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java index 8593ec5d74aff82456164ef477266dc2c80ea2d7..698f5a46478094eacd8ae76c1a800063d5c5a886 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java @@ -2,6 +2,7 @@ package jadx.core.dex.visitors; import jadx.core.codegen.CodeWriter; import jadx.core.codegen.MethodGen; +import jadx.core.deobf.Deobfuscator; import jadx.core.dex.attributes.IAttributeNode; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; @@ -104,7 +105,7 @@ public class DotGraphVisitor extends AbstractVisitor { + (useRegions ? ".regions" : "") + (rawInsn ? ".raw" : "") + ".dot"; - dot.save(dir, mth.getParentClass().getClassInfo().getFullPath() + "_graphs", fileName); + dot.save(dir, Deobfuscator.instance().getClassFullPath(mth.getParentClass().getClassInfo()) + "_graphs", fileName); } private void processMethodRegion(MethodNode mth) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java index 9cabcb3aead47911d0201da353f2df5300509b99..7cf9c13ab98f76e3c9eba417acdd92c8a5c20c9b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java @@ -2,6 +2,7 @@ package jadx.core.dex.visitors; import jadx.api.IJadxArgs; import jadx.core.codegen.CodeWriter; +import jadx.core.deobf.Deobfuscator; import jadx.core.dex.nodes.ClassNode; import jadx.core.utils.exceptions.CodegenException; @@ -24,7 +25,7 @@ public class SaveCode extends AbstractVisitor { public static void save(File dir, IJadxArgs args, ClassNode cls) { CodeWriter clsCode = cls.getCode(); - String fileName = cls.getClassInfo().getFullPath() + ".java"; + String fileName = Deobfuscator.instance().getClassFullPath(cls.getClassInfo()) + ".java"; if (args.isFallbackMode()) { fileName += ".jadx"; }