diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 8f32e8acd..c5fe652c0 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -18,8 +18,12 @@ import org.slf4j.LoggerFactory; import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.ResourceNameSource; import jadx.api.data.ICodeData; +import jadx.api.deobf.IAliasProvider; +import jadx.api.deobf.IRenameCondition; import jadx.api.impl.AnnotatedCodeWriter; import jadx.api.impl.InMemoryCodeCache; +import jadx.core.deobf.DeobfAliasProvider; +import jadx.core.deobf.DeobfCondition; import jadx.core.utils.files.FileUtils; public class JadxArgs { @@ -79,6 +83,16 @@ public class JadxArgs { private int deobfuscationMinLength = 0; private int deobfuscationMaxLength = Integer.MAX_VALUE; + /** + * Nodes alias provider for deobfuscator and rename visitor + */ + private IAliasProvider aliasProvider = new DeobfAliasProvider(); + + /** + * Condition to rename node in deobfuscator + */ + private IRenameCondition renameCondition = new DeobfCondition(); + private boolean escapeUnicode = false; private boolean replaceConsts = true; private boolean respectBytecodeAccModifiers = false; @@ -388,6 +402,22 @@ public class JadxArgs { this.resourceNameSource = resourceNameSource; } + public IAliasProvider getAliasProvider() { + return aliasProvider; + } + + public void setAliasProvider(IAliasProvider aliasProvider) { + this.aliasProvider = aliasProvider; + } + + public IRenameCondition getRenameCondition() { + return renameCondition; + } + + public void setRenameCondition(IRenameCondition renameCondition) { + this.renameCondition = renameCondition; + } + public boolean isEscapeUnicode() { return escapeUnicode; } diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 64f07807b..c3b1a42dc 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -120,6 +120,7 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable { loadInputFiles(); root = new RootNode(args); + root.init(); root.setDecompilerRef(this); root.mergePasses(customPasses); root.loadClasses(loadedInputs); diff --git a/jadx-core/src/main/java/jadx/api/deobf/IAliasProvider.java b/jadx-core/src/main/java/jadx/api/deobf/IAliasProvider.java new file mode 100644 index 000000000..a6cb2ada3 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/deobf/IAliasProvider.java @@ -0,0 +1,29 @@ +package jadx.api.deobf; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; + +public interface IAliasProvider { + + default void init(RootNode root) { + // optional + } + + String forPackage(PackageNode pkg); + + String forClass(ClassNode cls); + + String forField(FieldNode fld); + + String forMethod(MethodNode mth); + + /** + * Optional method to set initial max indexes loaded from mapping + */ + default void initIndexes(int pkg, int cls, int fld, int mth) { + // optional + } +} diff --git a/jadx-core/src/main/java/jadx/api/deobf/IRenameCondition.java b/jadx-core/src/main/java/jadx/api/deobf/IRenameCondition.java new file mode 100644 index 000000000..a9f2e3bbd --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/deobf/IRenameCondition.java @@ -0,0 +1,20 @@ +package jadx.api.deobf; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; + +public interface IRenameCondition { + + void init(RootNode root); + + boolean shouldRename(PackageNode pkg); + + boolean shouldRename(ClassNode cls); + + boolean shouldRename(FieldNode fld); + + boolean shouldRename(MethodNode mth); +} diff --git a/jadx-core/src/main/java/jadx/api/deobf/impl/AlwaysRename.java b/jadx-core/src/main/java/jadx/api/deobf/impl/AlwaysRename.java new file mode 100644 index 000000000..39373ad14 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/deobf/impl/AlwaysRename.java @@ -0,0 +1,40 @@ +package jadx.api.deobf.impl; + +import jadx.api.deobf.IRenameCondition; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; + +public class AlwaysRename implements IRenameCondition { + + public static final IRenameCondition INSTANCE = new AlwaysRename(); + + private AlwaysRename() { + } + + @Override + public void init(RootNode root) { + } + + @Override + public boolean shouldRename(PackageNode pkg) { + return true; + } + + @Override + public boolean shouldRename(ClassNode cls) { + return true; + } + + @Override + public boolean shouldRename(FieldNode fld) { + return true; + } + + @Override + public boolean shouldRename(MethodNode mth) { + return true; + } +} diff --git a/jadx-core/src/main/java/jadx/api/deobf/impl/AnyRenameCondition.java b/jadx-core/src/main/java/jadx/api/deobf/impl/AnyRenameCondition.java new file mode 100644 index 000000000..e2b6fa88d --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/deobf/impl/AnyRenameCondition.java @@ -0,0 +1,44 @@ +package jadx.api.deobf.impl; + +import java.util.function.BiPredicate; + +import jadx.api.deobf.IRenameCondition; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.IDexNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; + +public class AnyRenameCondition implements IRenameCondition { + + private final BiPredicate predicate; + + public AnyRenameCondition(BiPredicate predicate) { + this.predicate = predicate; + } + + @Override + public void init(RootNode root) { + } + + @Override + public boolean shouldRename(PackageNode pkg) { + return predicate.test(pkg.getAliasPkgInfo().getName(), pkg); + } + + @Override + public boolean shouldRename(ClassNode cls) { + return predicate.test(cls.getAlias(), cls); + } + + @Override + public boolean shouldRename(FieldNode fld) { + return predicate.test(fld.getAlias(), fld); + } + + @Override + public boolean shouldRename(MethodNode mth) { + return predicate.test(mth.getAlias(), mth); + } +} diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 5669d438a..a1097ddb7 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory; import jadx.api.CommentsLevel; import jadx.api.JadxArgs; +import jadx.core.deobf.DeobfuscatorVisitor; +import jadx.core.deobf.SaveDeobfMapping; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.visitors.AnonymousClassVisitor; import jadx.core.dex.visitors.AttachCommentsVisitor; @@ -91,7 +93,12 @@ public class Jadx { List passes = new ArrayList<>(); passes.add(new SignatureProcessor()); passes.add(new OverrideMethodVisitor()); + + // rename and deobfuscation + passes.add(new DeobfuscatorVisitor()); passes.add(new RenameVisitor()); + passes.add(new SaveDeobfMapping()); + passes.add(new UsageInfoVisitor()); passes.add(new ProcessAnonymous()); passes.add(new ProcessMethodsForInline()); diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfAliasProvider.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfAliasProvider.java new file mode 100644 index 000000000..2177ca7f2 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfAliasProvider.java @@ -0,0 +1,119 @@ +package jadx.core.deobf; + +import jadx.api.deobf.IAliasProvider; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; + +public class DeobfAliasProvider implements IAliasProvider { + + private int pkgIndex = 0; + private int clsIndex = 0; + private int fldIndex = 0; + private int mthIndex = 0; + + private int maxLength; + + @Override + public void init(RootNode root) { + this.maxLength = root.getArgs().getDeobfuscationMaxLength(); + } + + @Override + public void initIndexes(int pkg, int cls, int fld, int mth) { + pkgIndex = pkg; + clsIndex = cls; + fldIndex = fld; + mthIndex = mth; + } + + @Override + public String forPackage(PackageNode pkg) { + return String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getPkgInfo().getName())); + } + + @Override + public String forClass(ClassNode cls) { + String prefix = makeClsPrefix(cls); + return String.format("%sC%04d%s", prefix, clsIndex++, prepareNamePart(cls.getName())); + } + + @Override + public String forField(FieldNode fld) { + return String.format("f%d%s", fldIndex++, prepareNamePart(fld.getName())); + } + + @Override + public String forMethod(MethodNode mth) { + String prefix = mth.contains(AType.METHOD_OVERRIDE) ? "mo" : "m"; + return String.format("%s%d%s", prefix, mthIndex++, prepareNamePart(mth.getName())); + } + + private String prepareNamePart(String name) { + if (name.length() > maxLength) { + return 'x' + Integer.toHexString(name.hashCode()); + } + return NameMapper.removeInvalidCharsMiddle(name); + } + + /** + * Generate a prefix for a class name that bases on certain class properties, certain + * extended superclasses or implemented interfaces. + */ + private String makeClsPrefix(ClassNode cls) { + if (cls.isEnum()) { + return "Enum"; + } + StringBuilder result = new StringBuilder(); + if (cls.getAccessFlags().isInterface()) { + result.append("Interface"); + } else if (cls.getAccessFlags().isAbstract()) { + result.append("Abstract"); + } + + // Process current class and all super classes + ClassNode currentCls = cls; + outerLoop: while (currentCls != null) { + if (currentCls.getSuperClass() != null) { + String superClsName = currentCls.getSuperClass().getObject(); + if (superClsName.startsWith("android.app.")) { + // e.g. Activity or Fragment + result.append(superClsName.substring(12)); + break; + } else if (superClsName.startsWith("android.os.")) { + // e.g. AsyncTask + result.append(superClsName.substring(11)); + break; + } + } + for (ArgType intf : cls.getInterfaces()) { + String intfClsName = intf.getObject(); + if (intfClsName.equals("java.lang.Runnable")) { + result.append("Runnable"); + break outerLoop; + } else if (intfClsName.startsWith("java.util.concurrent.")) { + // e.g. Callable + result.append(intfClsName.substring(21)); + break outerLoop; + } else if (intfClsName.startsWith("android.view.")) { + // e.g. View.OnClickListener + result.append(intfClsName.substring(13)); + break outerLoop; + } else if (intfClsName.startsWith("android.content.")) { + // e.g. DialogInterface.OnClickListener + result.append(intfClsName.substring(16)); + break outerLoop; + } + } + if (currentCls.getSuperClass() == null) { + break; + } + currentCls = cls.root().resolveClass(currentCls.getSuperClass()); + } + return result.toString(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfClsInfo.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfClsInfo.java deleted file mode 100644 index 4fa823892..000000000 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfClsInfo.java +++ /dev/null @@ -1,50 +0,0 @@ -package jadx.core.deobf; - -import jadx.core.dex.nodes.ClassNode; - -class DeobfClsInfo { - private final Deobfuscator deobfuscator; - private final ClassNode cls; - private final PackageNode pkg; - private final String alias; - - public DeobfClsInfo(Deobfuscator deobfuscator, ClassNode cls, PackageNode pkg, String alias) { - this.deobfuscator = deobfuscator; - this.cls = cls; - this.pkg = pkg; - this.alias = alias; - } - - public String makeNameWithoutPkg() { - String prefix; - ClassNode parentClass = cls.getParentClass(); - if (parentClass != cls) { - DeobfClsInfo parentDeobfClsInfo = deobfuscator.getClsMap().get(parentClass.getClassInfo()); - if (parentDeobfClsInfo != null) { - prefix = parentDeobfClsInfo.makeNameWithoutPkg(); - } else { - prefix = deobfuscator.getNameWithoutPackage(parentClass.getClassInfo()); - } - prefix += Deobfuscator.INNER_CLASS_SEPARATOR; - } else { - prefix = ""; - } - return prefix + (this.alias != null ? this.alias : this.cls.getShortName()); - } - - public String getFullName() { - return pkg.getFullAlias() + Deobfuscator.CLASS_NAME_SEPARATOR + makeNameWithoutPkg(); - } - - public ClassNode getCls() { - return cls; - } - - public PackageNode getPkg() { - return pkg; - } - - public String getAlias() { - return alias; - } -} diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfCondition.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfCondition.java new file mode 100644 index 000000000..241256f97 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfCondition.java @@ -0,0 +1,103 @@ +package jadx.core.deobf; + +import java.util.HashSet; +import java.util.Set; + +import jadx.api.JadxArgs; +import jadx.api.deobf.IRenameCondition; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; + +public class DeobfCondition implements IRenameCondition { + + private int minLength; + private int maxLength; + + private final Set avoidClsNames = new HashSet<>(); + + @Override + public void init(RootNode root) { + JadxArgs args = root.getArgs(); + this.minLength = args.getDeobfuscationMinLength(); + this.maxLength = args.getDeobfuscationMaxLength(); + + for (PackageNode pkg : root.getPackages()) { + avoidClsNames.add(pkg.getPkgInfo().getName()); + } + } + + @Override + public boolean shouldRename(PackageNode pkg) { + String name = pkg.getAliasPkgInfo().getName(); + return shouldRename(name) + && !pkg.hasAlias() + && !TldHelper.contains(name); + } + + @Override + public boolean shouldRename(ClassNode cls) { + if (cls.contains(AFlag.DONT_RENAME) + || cls.getClassInfo().hasAlias() + || isR(cls.getTopParentClass())) { + return false; + } + String name = cls.getAlias(); + if (avoidClsNames.contains(name)) { + return true; + } + return shouldRename(name); + } + + @Override + public boolean shouldRename(FieldNode fld) { + return shouldRename(fld.getAlias()) + && !fld.contains(AFlag.DONT_RENAME) + && !fld.getFieldInfo().hasAlias() + && !isR(fld.getTopParentClass()); + } + + @Override + public boolean shouldRename(MethodNode mth) { + return shouldRename(mth.getAlias()) + && !mth.contains(AFlag.DONT_RENAME) + && !mth.getMethodInfo().hasAlias() + && !mth.isConstructor(); + } + + private boolean shouldRename(String s) { + int len = s.length(); + return len < minLength || len > maxLength; + } + + private static boolean isR(ClassNode cls) { + if (cls.contains(AFlag.ANDROID_R_CLASS)) { + return true; + } + if (!cls.getClassInfo().getShortName().equals("R")) { + return false; + } + if (!cls.getMethods().isEmpty() || !cls.getFields().isEmpty()) { + return false; + } + for (ClassNode inner : cls.getInnerClasses()) { + for (MethodNode m : inner.getMethods()) { + if (!m.getMethodInfo().isConstructor() && !m.getMethodInfo().isClassInit()) { + return false; + } + } + for (FieldNode field : cls.getFields()) { + ArgType type = field.getType(); + if (type != ArgType.INT && (!type.isArray() || type.getArrayElement() != ArgType.INT)) { + return false; + } + } + } + cls.add(AFlag.ANDROID_R_CLASS); + return true; + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java index 95cf566e8..9728d9aed 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java @@ -17,9 +17,15 @@ import org.slf4j.LoggerFactory; import jadx.api.JadxArgs; import jadx.api.args.DeobfuscationMapFileMode; +import jadx.api.deobf.IAliasProvider; +import jadx.api.deobf.impl.AlwaysRename; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.files.FileUtils; @@ -140,6 +146,66 @@ public class DeobfPresets { LOG.info("Deobfuscation map file saved as: {}", deobfMapFile); } + public void fill(RootNode root) { + for (PackageNode pkg : root.getPackages()) { + if (pkg.isLeaf()) { // ignore middle packages + if (pkg.hasParentAlias()) { + pkgPresetMap.put(pkg.getPkgInfo().getFullName(), pkg.getAliasPkgInfo().getFullName()); + } else if (pkg.hasAlias()) { + pkgPresetMap.put(pkg.getPkgInfo().getFullName(), pkg.getAliasPkgInfo().getName()); + } + } + } + for (ClassNode cls : root.getClasses()) { + ClassInfo classInfo = cls.getClassInfo(); + if (classInfo.hasAlias()) { + clsPresetMap.put(classInfo.makeRawFullName(), classInfo.getAliasShortName()); + } + for (FieldNode fld : cls.getFields()) { + FieldInfo fieldInfo = fld.getFieldInfo(); + if (fieldInfo.hasAlias()) { + fldPresetMap.put(fieldInfo.getRawFullId(), fld.getAlias()); + } + } + for (MethodNode mth : cls.getMethods()) { + MethodInfo methodInfo = mth.getMethodInfo(); + if (methodInfo.hasAlias()) { + mthPresetMap.put(methodInfo.getRawFullId(), methodInfo.getAlias()); + } + } + } + } + + public void apply(RootNode root) { + DeobfuscatorVisitor.process(root, + AlwaysRename.INSTANCE, + new IAliasProvider() { + @Override + public String forPackage(PackageNode pkg) { + return pkgPresetMap.get(pkg.getPkgInfo().getFullName()); + } + + @Override + public String forClass(ClassNode cls) { + return getForCls(cls.getClassInfo()); + } + + @Override + public String forField(FieldNode fld) { + return getForFld(fld.getFieldInfo()); + } + + @Override + public String forMethod(MethodNode mth) { + return getForMth(mth.getMethodInfo()); + } + }); + } + + public void initIndexes(IAliasProvider aliasProvider) { + aliasProvider.initIndexes(pkgPresetMap.size(), clsPresetMap.size(), fldPresetMap.size(), mthPresetMap.size()); + } + public String getForCls(ClassInfo cls) { if (clsPresetMap.isEmpty()) { return null; @@ -162,6 +228,7 @@ public class DeobfPresets { } public void clear() { + pkgPresetMap.clear(); clsPresetMap.clear(); fldPresetMap.clear(); mthPresetMap.clear(); diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java deleted file mode 100644 index e2babf448..000000000 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ /dev/null @@ -1,696 +0,0 @@ -package jadx.core.deobf; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.NavigableSet; -import java.util.Set; -import java.util.TreeSet; - -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.api.JadxArgs; -import jadx.api.args.DeobfuscationMapFileMode; -import jadx.api.plugins.input.data.attributes.JadxAttrType; -import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; -import jadx.core.dex.attributes.AFlag; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.nodes.MethodOverrideAttr; -import jadx.core.dex.info.ClassInfo; -import jadx.core.dex.info.FieldInfo; -import jadx.core.dex.info.MethodInfo; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.kotlin.KotlinMetadataUtils; - -public class Deobfuscator { - private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class); - - private static final boolean DEBUG = false; - - public static final String CLASS_NAME_SEPARATOR = "."; - public static final String INNER_CLASS_SEPARATOR = "$"; - - private final JadxArgs args; - private final RootNode root; - private final DeobfPresets deobfPresets; - - private final Map clsMap = new LinkedHashMap<>(); - private final Map fldMap = new HashMap<>(); - private final Map mthMap = new HashMap<>(); - - private final PackageNode rootPackage = new PackageNode(""); - private final Set pkgSet = new TreeSet<>(); - private final Set reservedClsNames = new HashSet<>(); - - private final NavigableSet mthProcessQueue = new TreeSet<>(); - - private final int maxLength; - private final int minLength; - private final boolean useSourceNameAsAlias; - private final boolean parseKotlinMetadata; - - private int pkgIndex = 0; - private int clsIndex = 0; - private int fldIndex = 0; - private int mthIndex = 0; - - public Deobfuscator(RootNode root) { - this.root = root; - this.args = root.getArgs(); - - this.minLength = args.getDeobfuscationMinLength(); - this.maxLength = args.getDeobfuscationMaxLength(); - this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias(); - this.parseKotlinMetadata = args.isParseKotlinMetadata(); - - this.deobfPresets = DeobfPresets.build(root); - } - - public void execute() { - if (args.getDeobfuscationMapFileMode().shouldRead()) { - if (deobfPresets.load()) { - for (Map.Entry pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) { - addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue()); - } - deobfPresets.getPkgPresetMap().clear(); // not needed anymore - initIndexes(); - } - } - process(); - } - - public void savePresets() { - DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode(); - if (!mode.shouldWrite()) { - return; - } - Path deobfMapFile = deobfPresets.getDeobfMapFile(); - if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) { - return; - } - try { - deobfPresets.clear(); - fillDeobfPresets(); - deobfPresets.save(); - } catch (Exception e) { - LOG.error("Failed to save deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e); - } - } - - private void fillDeobfPresets() { - for (PackageNode p : getRootPackage().getInnerPackages()) { - for (PackageNode pp : p.getInnerPackages()) { - dfsPackageName(p.getName(), pp); - } - if (p.hasAlias()) { - deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias()); - } - } - for (ClassNode cls : root.getClasses()) { - ClassInfo classInfo = cls.getClassInfo(); - if (classInfo.hasAlias()) { - deobfPresets.getClsPresetMap().put(classInfo.makeRawFullName(), classInfo.getAliasShortName()); - } - - for (FieldNode fld : cls.getFields()) { - FieldInfo fieldInfo = fld.getFieldInfo(); - if (fieldInfo.hasAlias()) { - deobfPresets.getFldPresetMap().put(fieldInfo.getRawFullId(), fld.getAlias()); - } - } - - for (MethodNode mth : cls.getMethods()) { - MethodInfo methodInfo = mth.getMethodInfo(); - if (methodInfo.hasAlias()) { - deobfPresets.getMthPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias()); - } - } - } - } - - private void dfsPackageName(String prefix, PackageNode node) { - for (PackageNode pp : node.getInnerPackages()) { - dfsPackageName(prefix + '.' + node.getName(), pp); - } - if (node.hasAlias()) { - deobfPresets.getPkgPresetMap().put(node.getName(), node.getAlias()); - } - } - - public void clear() { - deobfPresets.clear(); - clsMap.clear(); - fldMap.clear(); - mthMap.clear(); - } - - private void initIndexes() { - pkgIndex = pkgSet.size(); - clsIndex = deobfPresets.getClsPresetMap().size(); - fldIndex = deobfPresets.getFldPresetMap().size(); - mthIndex = deobfPresets.getMthPresetMap().size(); - } - - private void preProcess() { - for (ClassNode cls : root.getClasses()) { - Collections.addAll(reservedClsNames, cls.getPackage().split("\\.")); - } - for (ClassNode cls : root.getClasses()) { - preProcessClass(cls); - } - } - - private void process() { - preProcess(); - if (DEBUG) { - dumpAlias(); - } - for (ClassNode cls : root.getClasses()) { - processClass(cls); - } - while (true) { - MethodNode next = mthProcessQueue.pollLast(); - if (next == null) { - break; - } - renameMethod(next); - } - } - - private void processClass(ClassNode cls) { - if (isR(cls.getParentClass())) { - return; - } - ClassInfo clsInfo = cls.getClassInfo(); - DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo); - if (deobfClsInfo != null) { - clsInfo.changeShortName(deobfClsInfo.getAlias()); - PackageNode pkgNode = deobfClsInfo.getPkg(); - if (!clsInfo.isInner() && pkgNode.hasAnyAlias()) { - clsInfo.changePkg(pkgNode.getFullAlias()); - } - } else if (!clsInfo.isInner()) { - // check if package renamed - PackageNode pkgNode = getPackageNode(clsInfo.getPackage(), false); - if (pkgNode != null && pkgNode.hasAnyAlias()) { - clsInfo.changePkg(pkgNode.getFullAlias()); - } - } - for (FieldNode field : cls.getFields()) { - if (field.contains(AFlag.DONT_RENAME)) { - continue; - } - renameField(field); - } - mthProcessQueue.addAll(cls.getMethods()); - - for (ClassNode innerCls : cls.getInnerClasses()) { - processClass(innerCls); - } - } - - private void renameField(FieldNode field) { - FieldInfo fieldInfo = field.getFieldInfo(); - String alias = getFieldAlias(field); - if (alias != null) { - fieldInfo.setAlias(alias); - } - } - - public void forceRenameField(FieldNode field) { - field.getFieldInfo().setAlias(makeFieldAlias(field)); - } - - private void renameMethod(MethodNode mth) { - String alias = getMethodAlias(mth); - if (alias != null) { - applyMethodAlias(mth, alias); - } - } - - public void forceRenameMethod(MethodNode mth) { - String alias = makeMethodAlias(mth); - applyMethodAlias(mth, alias); - } - - private void applyMethodAlias(MethodNode mth, String alias) { - setSingleMethodAlias(mth, alias); - - MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE); - if (overrideAttr != null) { - for (MethodNode ovrdMth : overrideAttr.getRelatedMthNodes()) { - if (ovrdMth != mth) { - setSingleMethodAlias(ovrdMth, alias); - } - } - } - } - - private void setSingleMethodAlias(MethodNode mth, String alias) { - MethodInfo mthInfo = mth.getMethodInfo(); - mthInfo.setAlias(alias); - mthMap.put(mthInfo, alias); - mthProcessQueue.remove(mth); - } - - public void addPackagePreset(String origPkgName, String pkgAlias) { - PackageNode pkg = getPackageNode(origPkgName, true); - pkg.setAlias(pkgAlias); - } - - /** - * Gets package node for full package name - * - * @param fullPkgName - * full package name - * @param create - * if {@code true} then will create all absent objects - * @return package node object or {@code null} if no package found and create set to - * {@code false} - */ - private PackageNode getPackageNode(String fullPkgName, boolean create) { - if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) { - return rootPackage; - } - PackageNode result = rootPackage; - PackageNode parentNode; - do { - String pkgName; - int idx = fullPkgName.indexOf(CLASS_NAME_SEPARATOR); - - 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 && create) { - result = new PackageNode(pkgName); - parentNode.addInnerPackage(result); - } - } while (!fullPkgName.isEmpty() && result != null); - - return result; - } - - String getNameWithoutPackage(ClassInfo clsInfo) { - String prefix; - ClassInfo parentClsInfo = clsInfo.getParentClass(); - if (parentClsInfo != null) { - DeobfClsInfo parentDeobfClsInfo = clsMap.get(parentClsInfo); - if (parentDeobfClsInfo != null) { - prefix = parentDeobfClsInfo.makeNameWithoutPkg(); - } else { - prefix = getNameWithoutPackage(parentClsInfo); - } - prefix += INNER_CLASS_SEPARATOR; - } else { - prefix = ""; - } - return prefix + clsInfo.getShortName(); - } - - private void preProcessClass(ClassNode cls) { - ClassInfo classInfo = cls.getClassInfo(); - String pkgFullName = classInfo.getPackage(); - PackageNode pkg = getPackageNode(pkgFullName, true); - processPackageFull(pkg, pkgFullName); - - String alias = deobfPresets.getForCls(classInfo); - if (alias != null) { - clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias)); - } else { - if (!clsMap.containsKey(classInfo)) { - String clsShortName = classInfo.getShortName(); - boolean badName = shouldRename(clsShortName) - || (args.isRenameValid() && reservedClsNames.contains(clsShortName)); - makeClsAlias(cls, badName); - } - } - for (ClassNode innerCls : cls.getInnerClasses()) { - preProcessClass(innerCls); - } - } - - public String getClsAlias(ClassNode cls) { - DeobfClsInfo deobfClsInfo = clsMap.get(cls.getClassInfo()); - if (deobfClsInfo != null) { - return deobfClsInfo.getAlias(); - } - return makeClsAlias(cls, true); - } - - public String getPkgAlias(ClassNode cls) { - ClassInfo classInfo = cls.getClassInfo(); - if (classInfo.hasAliasPkg()) { - // already renamed - PackageNode pkg = getPackageNode(classInfo.getPackage(), true); - // update all parts of package - String[] aliasParts = classInfo.getAliasPkg().split("\\."); - PackageNode subPkg = pkg; - for (int i = aliasParts.length - 1; i >= 0; i--) { - String aliasPart = aliasParts[i]; - if (!subPkg.getName().equals(aliasPart)) { - subPkg.setAlias(aliasPart); - } - subPkg = subPkg.getParentPackage(); - } - return pkg.getFullAlias(); - } - PackageNode pkg; - DeobfClsInfo deobfClsInfo = clsMap.get(classInfo); - if (deobfClsInfo != null) { - pkg = deobfClsInfo.getPkg(); - } else { - String fullPkgName = classInfo.getPackage(); - pkg = getPackageNode(fullPkgName, true); - processPackageFull(pkg, fullPkgName); - } - if (pkg.hasAnyAlias()) { - return pkg.getFullAlias(); - } else { - return pkg.getFullName(); - } - } - - private String makeClsAlias(ClassNode cls, boolean badName) { - String alias = null; - String pkgName = null; - if (this.parseKotlinMetadata) { - ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls); - if (kotlinCls != null) { - alias = kotlinCls.getName(); - pkgName = kotlinCls.getPkg(); - } - } - if (alias == null && this.useSourceNameAsAlias) { - alias = getAliasFromSourceFile(cls); - } - - ClassInfo classInfo = cls.getClassInfo(); - if (alias == null) { - if (badName) { - String clsName = classInfo.getShortName(); - String prefix = makeClsPrefix(cls); - alias = String.format("%sC%04d%s", prefix, clsIndex++, prepareNamePart(clsName)); - } else { - // rename not needed - return classInfo.getShortName(); - } - } - if (pkgName == null) { - pkgName = classInfo.getPackage(); - } - PackageNode pkg = getPackageNode(pkgName, true); - clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias)); - return alias; - } - - /** - * Generate a prefix for a class name that bases on certain class properties, certain - * extended superclasses or implemented interfaces. - */ - private String makeClsPrefix(ClassNode cls) { - if (cls.isEnum()) { - return "Enum"; - } - String result = ""; - if (cls.getAccessFlags().isInterface()) { - result += "Interface"; - } else if (cls.getAccessFlags().isAbstract()) { - result += "Abstract"; - } - - // Process current class and all super classes - ClassNode currentCls = cls; - outerLoop: while (currentCls != null) { - if (currentCls.getSuperClass() != null) { - String superClsName = currentCls.getSuperClass().getObject(); - if (superClsName.startsWith("android.app.")) { - // e.g. Activity or Fragment - result += superClsName.substring(12); - break; - } else if (superClsName.startsWith("android.os.")) { - // e.g. AsyncTask - result += superClsName.substring(11); - break; - } - } - for (ArgType intf : cls.getInterfaces()) { - String intfClsName = intf.getObject(); - if (intfClsName.equals("java.lang.Runnable")) { - result += "Runnable"; - break outerLoop; - } else if (intfClsName.startsWith("java.util.concurrent.")) { - // e.g. Callable - result += intfClsName.substring(21); - break outerLoop; - } else if (intfClsName.startsWith("android.view.")) { - // e.g. View.OnClickListener - result += intfClsName.substring(13); - break outerLoop; - } else if (intfClsName.startsWith("android.content.")) { - // e.g. DialogInterface.OnClickListener - result += intfClsName.substring(16); - break outerLoop; - } - } - if (currentCls.getSuperClass() == null) { - break; - } - currentCls = cls.root().resolveClass(currentCls.getSuperClass()); - } - return result; - } - - @Nullable - private String getAliasFromSourceFile(ClassNode cls) { - SourceFileAttr sourceFileAttr = cls.get(JadxAttrType.SOURCE_FILE); - if (sourceFileAttr == null) { - return null; - } - if (cls.getClassInfo().isInner()) { - return null; - } - String name = sourceFileAttr.getFileName(); - if (name.endsWith(".java")) { - name = name.substring(0, name.length() - ".java".length()); - } else if (name.endsWith(".kt")) { - name = name.substring(0, name.length() - ".kt".length()); - } - if (!NameMapper.isValidAndPrintable(name)) { - return null; - } - for (DeobfClsInfo deobfClsInfo : clsMap.values()) { - if (deobfClsInfo.getAlias().equals(name)) { - return null; - } - } - ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name); - if (otherCls != null) { - return null; - } - cls.remove(JadxAttrType.SOURCE_FILE); - return name; - } - - @Nullable - private String getFieldAlias(FieldNode field) { - FieldInfo fieldInfo = field.getFieldInfo(); - String alias = fldMap.get(fieldInfo); - if (alias != null) { - return alias; - } - alias = deobfPresets.getForFld(fieldInfo); - if (alias != null) { - fldMap.put(fieldInfo, alias); - return alias; - } - if (shouldRename(field.getName())) { - return makeFieldAlias(field); - } - return null; - } - - @Nullable - private String getMethodAlias(MethodNode mth) { - if (mth.contains(AFlag.DONT_RENAME)) { - return null; - } - MethodInfo methodInfo = mth.getMethodInfo(); - if (methodInfo.isClassInit() || methodInfo.isConstructor()) { - return null; - } - String alias = getAssignedAlias(methodInfo); - if (alias != null) { - return alias; - } - if (shouldRename(mth.getName())) { - return makeMethodAlias(mth); - } - return null; - } - - @Nullable - private String getAssignedAlias(MethodInfo methodInfo) { - String alias = mthMap.get(methodInfo); - if (alias != null) { - return alias; - } - return deobfPresets.getForMth(methodInfo); - } - - public String makeFieldAlias(FieldNode field) { - String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName())); - fldMap.put(field.getFieldInfo(), alias); - return alias; - } - - public String makeMethodAlias(MethodNode mth) { - String prefix; - if (mth.contains(AType.METHOD_OVERRIDE)) { - prefix = "mo"; - } else { - prefix = "m"; - } - return String.format("%s%d%s", prefix, mthIndex++, prepareNamePart(mth.getName())); - } - - private void processPackageFull(PackageNode pkg, String fullName) { - if (pkgSet.contains(fullName)) { - return; - } - pkgSet.add(fullName); - - // doPkg for all parent packages except root that not hasAliases - PackageNode parentPkg = pkg.getParentPackage(); - while (!parentPkg.getName().isEmpty()) { - if (!parentPkg.hasAlias()) { - processPackageFull(parentPkg, parentPkg.getFullName()); - } - parentPkg = parentPkg.getParentPackage(); - } - - if (!pkg.hasAlias()) { - String pkgName = pkg.getName(); - if ((args.isDeobfuscationOn() && shouldRename(pkgName)) - && (pkg.getParentPackage() != rootPackage || !TldHelper.contains(pkgName)) // check if first level is a valid tld - || (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName)) - || (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) { - String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName())); - pkg.setAlias(pkgAlias); - } - } - } - - private boolean shouldRename(String s) { - int len = s.length(); - return len < minLength || len > maxLength; - } - - private String prepareNamePart(String name) { - if (name.length() > maxLength) { - return 'x' + Integer.toHexString(name.hashCode()); - } - return NameMapper.removeInvalidCharsMiddle(name); - } - - private static String makeHashName(String name, String invalidPrefix) { - return invalidPrefix + 'x' + Integer.toHexString(name.hashCode()); - } - - 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 (ClassNode cls : root.getClasses()) { - dumpClassAlias(cls); - } - } - - private String getPackageName(String packageName) { - PackageNode pkg = getPackageNode(packageName, false); - if (pkg != null) { - return pkg.getFullAlias(); - } - return packageName; - } - - private String getClassName(ClassInfo clsInfo) { - DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo); - if (deobfClsInfo != null) { - return deobfClsInfo.makeNameWithoutPkg(); - } - return getNameWithoutPackage(clsInfo); - } - - private String getClassFullName(ClassNode cls) { - ClassInfo clsInfo = cls.getClassInfo(); - DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo); - if (deobfClsInfo != null) { - return deobfClsInfo.getFullName(); - } - return getPackageName(clsInfo.getPackage()) + CLASS_NAME_SEPARATOR + getClassName(clsInfo); - } - - public Map getClsMap() { - return clsMap; - } - - public Map getFldMap() { - return fldMap; - } - - public Map getMthMap() { - return mthMap; - } - - public PackageNode getRootPackage() { - return rootPackage; - } - - private static boolean isR(ClassNode cls) { - if (!cls.getClassInfo().getShortName().equals("R")) { - return false; - } - if (!cls.getMethods().isEmpty() || !cls.getFields().isEmpty()) { - return false; - } - for (ClassNode inner : cls.getInnerClasses()) { - for (MethodNode m : inner.getMethods()) { - if (!m.getMethodInfo().isConstructor() && !m.getMethodInfo().isClassInit()) { - return false; - } - } - for (FieldNode field : cls.getFields()) { - ArgType type = field.getType(); - if (type != ArgType.INT && (!type.isArray() || type.getArrayElement() != ArgType.INT)) { - return false; - } - } - } - return true; - } -} diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java new file mode 100644 index 000000000..a743500bc --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java @@ -0,0 +1,74 @@ +package jadx.core.deobf; + +import jadx.api.JadxArgs; +import jadx.api.deobf.IAliasProvider; +import jadx.api.deobf.IRenameCondition; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.utils.exceptions.JadxException; + +public class DeobfuscatorVisitor extends AbstractVisitor { + + @Override + public void init(RootNode root) throws JadxException { + JadxArgs args = root.getArgs(); + if (!args.isDeobfuscationOn()) { + return; + } + DeobfPresets mapping = DeobfPresets.build(root); + if (args.getDeobfuscationMapFileMode().shouldRead()) { + if (mapping.load()) { + mapping.apply(root); + } + } + IAliasProvider aliasProvider = args.getAliasProvider(); + IRenameCondition renameCondition = args.getRenameCondition(); + mapping.initIndexes(aliasProvider); + process(root, renameCondition, aliasProvider); + } + + public static void process(RootNode root, IRenameCondition renameCondition, IAliasProvider aliasProvider) { + boolean pkgUpdated = false; + for (PackageNode pkg : root.getPackages()) { + if (renameCondition.shouldRename(pkg)) { + String alias = aliasProvider.forPackage(pkg); + if (alias != null) { + pkg.rename(alias, false); + pkgUpdated = true; + } + } + } + if (pkgUpdated) { + root.runPackagesUpdate(); + } + + for (ClassNode cls : root.getClasses()) { + if (renameCondition.shouldRename(cls)) { + String clsAlias = aliasProvider.forClass(cls); + if (clsAlias != null) { + cls.rename(clsAlias); + } + } + for (FieldNode fld : cls.getFields()) { + if (renameCondition.shouldRename(fld)) { + String fldAlias = aliasProvider.forField(fld); + if (fldAlias != null) { + fld.rename(fldAlias); + } + } + } + for (MethodNode mth : cls.getMethods()) { + if (renameCondition.shouldRename(mth)) { + String mthAlias = aliasProvider.forMethod(mth); + if (mthAlias != null) { + mth.rename(mthAlias); + } + } + } + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java b/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java deleted file mode 100644 index 82a847ca2..000000000 --- a/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java +++ /dev/null @@ -1,153 +0,0 @@ -package jadx.core.deobf; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.List; - -public class PackageNode { - - private static final char SEPARATOR_CHAR = '.'; - - private PackageNode parentPackage; - private List innerPackages = Collections.emptyList(); - - private final 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) { - Deque pp = getParentPackages(); - if (pp.isEmpty()) { - cachedPackageFullName = ""; - } else { - StringBuilder result = new StringBuilder(); - result.append(pp.pop().getName()); - while (!pp.isEmpty()) { - result.append(SEPARATOR_CHAR); - 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; - cachedPackageFullAlias = null; - } - - public boolean hasAlias() { - return packageAlias != null; - } - - public boolean hasAnyAlias() { - if (hasAlias()) { - return true; - } - if (parentPackage != this) { - return parentPackage.hasAnyAlias(); - } - return false; - } - - public String getFullAlias() { - if (cachedPackageFullAlias == null) { - Deque pp = getParentPackages(); - StringBuilder result = new StringBuilder(); - - if (!pp.isEmpty()) { - result.append(pp.pop().getAlias()); - while (!pp.isEmpty()) { - result.append(SEPARATOR_CHAR); - result.append(pp.pop().getAlias()); - } - } else { - result.append(this.getAlias()); - } - cachedPackageFullAlias = result.toString(); - } - return cachedPackageFullAlias; - } - - public PackageNode getParentPackage() { - return parentPackage; - } - - public List getInnerPackages() { - return innerPackages; - } - - 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 Deque getParentPackages() { - Deque pp = new ArrayDeque<>(); - - PackageNode currentPkg = this; - PackageNode parentPkg = currentPkg.getParentPackage(); - while (currentPkg != parentPkg) { - pp.push(currentPkg); - currentPkg = parentPkg; - parentPkg = currentPkg.getParentPackage(); - } - return pp; - } - - @Override - public String toString() { - if (packageAlias != null) { - return packageName + "[alias:" + packageAlias + "]"; - } - return packageName; - } -} diff --git a/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java b/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java new file mode 100644 index 000000000..7e267c5bb --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java @@ -0,0 +1,48 @@ +package jadx.core.deobf; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.JadxArgs; +import jadx.api.args.DeobfuscationMapFileMode; +import jadx.core.codegen.json.JsonMappingGen; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.utils.exceptions.JadxException; + +public class SaveDeobfMapping extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(SaveDeobfMapping.class); + + @Override + public void init(RootNode root) throws JadxException { + JadxArgs args = root.getArgs(); + if (args.isDeobfuscationOn() || !args.isJsonOutput()) { + saveMappings(root); + } + if (args.isJsonOutput()) { + JsonMappingGen.dump(root); + } + } + + private void saveMappings(RootNode root) { + DeobfuscationMapFileMode mode = root.getArgs().getDeobfuscationMapFileMode(); + if (!mode.shouldWrite()) { + return; + } + DeobfPresets mapping = DeobfPresets.build(root); + Path deobfMapFile = mapping.getDeobfMapFile(); + if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) { + return; + } + try { + mapping.clear(); + mapping.fill(root); + mapping.save(); + } catch (Exception e) { + LOG.error("Failed to save deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e); + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/TldHelper.java b/jadx-core/src/main/java/jadx/core/deobf/TldHelper.java index 32de4a34d..0f59cf2ef 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/TldHelper.java +++ b/jadx-core/src/main/java/jadx/core/deobf/TldHelper.java @@ -17,7 +17,7 @@ public class TldHelper { private static Set loadTldFile() { Set tldNames = new HashSet<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(Deobfuscator.class.getResourceAsStream("tld_3.txt")))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHelper.class.getResourceAsStream("tld_3.txt")))) { String line; while ((line = reader.readLine()) != null) { line = line.trim(); @@ -34,5 +34,4 @@ public class TldHelper { public static boolean contains(String name) { return TLD_SET.contains(name); } - } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index 181577405..75e802b7c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -44,6 +44,11 @@ public enum AFlag { THIS, SUPER, + /** + * Mark Android resources class + */ + ANDROID_R_CLASS, + /** * RegisterArg attribute for method arguments */ diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index 069d00e8e..e073fa4b9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -111,19 +111,12 @@ public final class ClassInfo implements Comparable { } public boolean hasAlias() { - if (alias != null) { + if (alias != null && !alias.getShortName().equals(getShortName())) { return true; } return parentClass != null && parentClass.hasAlias(); } - public boolean hasAliasPkg() { - if (alias != null) { - return !getPackage().equals(getAliasPkg()); - } - return parentClass != null && parentClass.hasAliasPkg(); - } - public void removeAlias() { this.alias = null; } @@ -160,8 +153,8 @@ public final class ClassInfo implements Comparable { private static String makeFullClsName(String pkg, String shortName, ClassInfo parentClass, boolean alias, boolean raw) { if (parentClass != null) { - String innerSep = raw ? "$" : "."; String parentFullName; + char innerSep = raw ? '$' : '.'; if (alias) { parentFullName = raw ? parentClass.makeAliasRawFullName() : parentClass.getAliasFullName(); } else { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 78a02d43c..c70096e6f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -572,6 +572,11 @@ public class ClassNode extends NotificationAttrNode parentClass = this; } + @Override + public void rename(String newName) { + clsInfo.changeShortName(newName); + } + @Override public void onParentPackageUpdate(PackageNode updatedPkg) { if (isInner()) { @@ -734,6 +739,15 @@ public class ClassNode extends NotificationAttrNode return clsInfo; } + public String getName() { + return clsInfo.getShortName(); + } + + public String getAlias() { + return clsInfo.getAliasShortName(); + } + + @Deprecated public String getShortName() { return clsInfo.getAliasShortName(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index be106befd..b4a5a244f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -72,6 +72,7 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode { return fieldInfo.getAlias(); } + @Override public void rename(String alias) { fieldInfo.setAlias(alias); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java index 730dab096..1c372a970 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java @@ -1,6 +1,8 @@ package jadx.core.dex.nodes; -public interface IDexNode { +import jadx.api.core.nodes.IRenameNode; + +public interface IDexNode extends IRenameNode { String typeName(); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index b5f87f330..3eebbd796 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -576,6 +576,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodNode, noCode = true; } + @Override public void rename(String newName) { MethodOverrideAttr overrideAttr = get(AType.METHOD_OVERRIDE); if (overrideAttr != null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java index ec7ab3a82..4e3ca6dd3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java @@ -2,6 +2,7 @@ package jadx.core.dex.nodes; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,25 +41,63 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable getSubPackages() { return subPackages; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index f7ba4f35d..26b280f21 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -81,6 +81,7 @@ public class RootNode implements IRootNode { private List classes = new ArrayList<>(); private final Map pkgMap = new HashMap<>(); + private final List packages = new ArrayList<>(); private ClspGraph clsp; @Nullable @@ -106,6 +107,15 @@ public class RootNode implements IRootNode { this.isProto = args.getInputFiles().size() > 0 && args.getInputFiles().get(0).getName().toLowerCase().endsWith(".aab"); } + public void init() { + if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) { + args.getAliasProvider().init(this); + } + if (args.isDeobfuscationOn()) { + args.getRenameCondition().init(this); + } + } + public void loadClasses(List loadedInputs) { for (ILoadResult loadedInput : loadedInputs) { loadedInput.visitClasses(cls -> { @@ -132,6 +142,9 @@ public class RootNode implements IRootNode { classes.sort(Comparator.comparing(ClassNode::getFullName)); // move inner classes initInnerClasses(); + + // sort packages + Collections.sort(packages); } private void addDummyClass(IClassData classData, Exception exc) { @@ -350,9 +363,7 @@ public class RootNode implements IRootNode { } public List getPackages() { - List list = new ArrayList<>(pkgMap.values()); - Collections.sort(list); - return list; + return packages; } public @Nullable PackageNode resolvePackage(String fullPkg) { @@ -365,6 +376,18 @@ public class RootNode implements IRootNode { public void addPackage(PackageNode pkg) { pkgMap.put(pkg.getPkgInfo().getFullName(), pkg); + packages.add(pkg); + } + + /** + * Update sub packages + */ + public void runPackagesUpdate() { + for (PackageNode pkg : getPackages()) { + if (pkg.isRoot()) { + pkg.updatePackages(); + } + } } @Nullable diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/KotlinMetadataRename.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/KotlinMetadataRename.java new file mode 100644 index 000000000..716e7a609 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/KotlinMetadataRename.java @@ -0,0 +1,25 @@ +package jadx.core.dex.visitors.rename; + +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.kotlin.ClsAliasPair; +import jadx.core.utils.kotlin.KotlinMetadataUtils; + +public class KotlinMetadataRename { + + public static void process(RootNode root) { + if (root.getArgs().isParseKotlinMetadata()) { + for (ClassNode cls : root.getClasses()) { + if (cls.contains(AFlag.DONT_RENAME)) { + continue; + } + ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls); + if (kotlinCls != null) { + cls.rename(kotlinCls.getName()); + cls.getPackageNode().rename(kotlinCls.getPkg()); + } + } + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java index a359e87b3..4bbbaa1d6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java @@ -10,9 +10,8 @@ import java.util.regex.Pattern; import org.jetbrains.annotations.Nullable; import jadx.api.JadxArgs; +import jadx.api.deobf.IAliasProvider; import jadx.core.Consts; -import jadx.core.codegen.json.JsonMappingGen; -import jadx.core.deobf.Deobfuscator; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -23,6 +22,7 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.AbstractVisitor; @@ -39,56 +39,56 @@ public class RenameVisitor extends AbstractVisitor { } private void process(RootNode root) { - Deobfuscator deobfuscator = new Deobfuscator(root); - JadxArgs args = root.getArgs(); + KotlinMetadataRename.process(root); + SourceFileRename.process(root); - if (args.isDeobfuscationOn()) { - deobfuscator.execute(); - } - - UserRenames.applyForNodes(root); - checkClasses(deobfuscator, root, args); - - if (args.isDeobfuscationOn() || !args.isJsonOutput()) { - deobfuscator.savePresets(); - deobfuscator.clear(); - } - if (args.isJsonOutput()) { - JsonMappingGen.dump(root); - } + UserRenames.apply(root); + checkNames(root); } - private static void checkClasses(Deobfuscator deobfuscator, RootNode root, JadxArgs args) { + private static void checkNames(RootNode root) { + JadxArgs args = root.getArgs(); + if (args.getRenameFlags().isEmpty()) { + return; + } + + IAliasProvider aliasProvider = args.getAliasProvider(); + List classes = root.getClasses(true); for (ClassNode cls : classes) { - checkClassName(deobfuscator, cls, args); - checkFields(deobfuscator, cls, args); - checkMethods(deobfuscator, cls, args); + checkClassName(aliasProvider, cls, args); + checkFields(aliasProvider, cls, args); + checkMethods(aliasProvider, cls, args); } if (!args.isFsCaseSensitive() && args.isRenameCaseSensitive()) { Set clsFullPaths = new HashSet<>(classes.size()); for (ClassNode cls : classes) { ClassInfo clsInfo = cls.getClassInfo(); if (!clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase())) { - String newShortName = deobfuscator.getClsAlias(cls); - clsInfo.changeShortName(newShortName); + clsInfo.changeShortName(aliasProvider.forClass(cls)); cls.addAttr(new RenameReasonAttr(cls).append("case insensitive filesystem")); clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase()); } } } - processRootPackages(deobfuscator, root, classes); + boolean pkgUpdated = false; + for (PackageNode pkg : root.getPackages()) { + pkgUpdated |= checkPackage(args, aliasProvider, pkg); + } + if (pkgUpdated) { + root.runPackagesUpdate(); + } + processRootPackages(aliasProvider, root, classes); } - private static void checkClassName(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) { + private static void checkClassName(IAliasProvider aliasProvider, ClassNode cls, JadxArgs args) { ClassInfo classInfo = cls.getClassInfo(); String clsName = classInfo.getAliasShortName(); String newShortName = fixClsShortName(args, clsName); if (newShortName == null) { // rename failed, use deobfuscator - String deobfName = deobfuscator.getClsAlias(cls); - classInfo.changeShortName(deobfName); + cls.rename(aliasProvider.forClass(cls)); cls.addAttr(new RenameReasonAttr(cls).notPrintable()); return; } @@ -101,32 +101,28 @@ public class RenameVisitor extends AbstractVisitor { ClassInfo parentClass = classInfo.getParentClass(); while (parentClass != null) { if (parentClass.getAliasShortName().equals(newShortName)) { - String clsAlias = deobfuscator.getClsAlias(cls); - classInfo.changeShortName(clsAlias); + cls.rename(aliasProvider.forClass(cls)); cls.addAttr(new RenameReasonAttr(cls).append("collision with other inner class name")); break; } parentClass = parentClass.getParentClass(); } } - checkPackage(deobfuscator, cls, classInfo, args); } - private static void checkPackage(Deobfuscator deobfuscator, ClassNode cls, ClassInfo classInfo, JadxArgs args) { - if (classInfo.isInner()) { - return; + private static boolean checkPackage(JadxArgs args, IAliasProvider aliasProvider, PackageNode pkg) { + if (args.isRenameValid() && pkg.getAliasPkgInfo().isDefaultPkg()) { + pkg.setFullAlias(Consts.DEFAULT_PACKAGE_NAME, false); + return true; } - String aliasPkg = classInfo.getAliasPkg(); - if (args.isRenameValid() && aliasPkg.isEmpty()) { - classInfo.changePkg(Consts.DEFAULT_PACKAGE_NAME); - cls.addAttr(new RenameReasonAttr(cls).append("default package")); - return; - } - String fullPkgAlias = deobfuscator.getPkgAlias(cls); - if (!fullPkgAlias.equals(aliasPkg)) { - classInfo.changePkg(fullPkgAlias); - cls.addAttr(new RenameReasonAttr(cls).append("invalid package")); + String pkgName = pkg.getAliasPkgInfo().getName(); + boolean notValid = args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName); + boolean notPrintable = args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName); + if (notValid || notPrintable) { + pkg.setLeafAlias(aliasProvider.forPackage(pkg), false); + return true; } + return false; } @Nullable @@ -157,7 +153,7 @@ public class RenameVisitor extends AbstractVisitor { return cleanClsName; } - private static void checkFields(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) { + private static void checkFields(IAliasProvider aliasProvider, ClassNode cls, JadxArgs args) { Set names = new HashSet<>(); for (FieldNode field : cls.getFields()) { FieldInfo fieldInfo = field.getFieldInfo(); @@ -166,7 +162,7 @@ public class RenameVisitor extends AbstractVisitor { boolean notValid = args.isRenameValid() && !NameMapper.isValidIdentifier(fieldName); boolean notPrintable = args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(fieldName); if (notUnique || notValid || notPrintable) { - deobfuscator.forceRenameField(field); + field.rename(aliasProvider.forField(field)); field.addAttr(new RenameReasonAttr(field, notValid, notPrintable)); if (notUnique) { field.addAttr(new RenameReasonAttr(field).append("collision with other field name")); @@ -175,21 +171,19 @@ public class RenameVisitor extends AbstractVisitor { } } - private static void checkMethods(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) { + private static void checkMethods(IAliasProvider aliasProvider, ClassNode cls, JadxArgs args) { List methods = new ArrayList<>(cls.getMethods().size()); for (MethodNode method : cls.getMethods()) { if (!method.getAccessFlags().isConstructor()) { methods.add(method); } } - for (MethodNode mth : methods) { String alias = mth.getAlias(); - boolean notValid = args.isRenameValid() && !NameMapper.isValidIdentifier(alias); boolean notPrintable = args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(alias); if (notValid || notPrintable) { - deobfuscator.forceRenameMethod(mth); + mth.rename(aliasProvider.forMethod(mth)); mth.addAttr(new RenameReasonAttr(mth, notValid, notPrintable)); } } @@ -199,7 +193,7 @@ public class RenameVisitor extends AbstractVisitor { for (MethodNode mth : methods) { String signature = mth.getMethodInfo().makeSignature(true, false); if (!names.add(signature) && canRename(mth)) { - deobfuscator.forceRenameMethod(mth); + mth.rename(aliasProvider.forMethod(mth)); mth.addAttr(new RenameReasonAttr("collision with other method in class")); } } @@ -223,8 +217,8 @@ public class RenameVisitor extends AbstractVisitor { return true; } - private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List classes) { - Set rootPkgs = collectRootPkgs(classes); + private static void processRootPackages(IAliasProvider aliasProvider, RootNode root, List classes) { + Set rootPkgs = collectRootPkgs(root); root.getCacheStorage().setRootPkgs(rootPkgs); if (root.getArgs().isRenameValid()) { @@ -232,7 +226,7 @@ public class RenameVisitor extends AbstractVisitor { for (ClassNode cls : classes) { for (FieldNode field : cls.getFields()) { if (rootPkgs.contains(field.getAlias())) { - deobfuscator.forceRenameField(field); + field.rename(aliasProvider.forField(field)); field.addAttr(new RenameReasonAttr("collision with root package name")); } } @@ -240,33 +234,16 @@ public class RenameVisitor extends AbstractVisitor { } } - private static Set collectRootPkgs(List classes) { - Set fullPkgs = new HashSet<>(); - for (ClassNode cls : classes) { - fullPkgs.add(cls.getClassInfo().getAliasPkg()); - } + private static Set collectRootPkgs(RootNode root) { Set rootPkgs = new HashSet<>(); - for (String pkg : fullPkgs) { - String rootPkg = getRootPkg(pkg); - if (rootPkg != null) { - rootPkgs.add(rootPkg); + for (PackageNode pkg : root.getPackages()) { + if (pkg.isRoot()) { + rootPkgs.add(pkg.getPkgInfo().getName()); } } return rootPkgs; } - @Nullable - private static String getRootPkg(String pkg) { - if (pkg.isEmpty()) { - return null; - } - int dotPos = pkg.indexOf('.'); - if (dotPos < 0) { - return pkg; - } - return pkg.substring(0, dotPos); - } - @Override public String toString() { return "RenameVisitor"; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/SourceFileRename.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/SourceFileRename.java new file mode 100644 index 000000000..39d795f00 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/SourceFileRename.java @@ -0,0 +1,51 @@ +package jadx.core.dex.visitors.rename; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.attributes.JadxAttrType; +import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; +import jadx.core.deobf.NameMapper; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.StringUtils; + +public class SourceFileRename { + + public static void process(RootNode root) { + if (root.getArgs().isUseSourceNameAsClassAlias()) { + for (ClassNode cls : root.getClasses()) { + if (cls.contains(AFlag.DONT_RENAME)) { + continue; + } + String alias = getAliasFromSourceFile(cls); + if (alias != null) { + cls.rename(alias); + } + } + } + } + + @Nullable + private static String getAliasFromSourceFile(ClassNode cls) { + SourceFileAttr sourceFileAttr = cls.get(JadxAttrType.SOURCE_FILE); + if (sourceFileAttr == null) { + return null; + } + if (cls.getClassInfo().isInner()) { + return null; + } + String name = sourceFileAttr.getFileName(); + name = StringUtils.removeSuffix(name, ".java"); + name = StringUtils.removeSuffix(name, ".kt"); + if (!NameMapper.isValidAndPrintable(name)) { + return null; + } + ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name); + if (otherCls != null) { + return null; + } + cls.remove(JadxAttrType.SOURCE_FILE); + return name; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/UserRenames.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/UserRenames.java index 2b5cb028f..e5f60b129 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/UserRenames.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/UserRenames.java @@ -3,7 +3,6 @@ package jadx.core.dex.visitors.rename; import java.util.List; import java.util.stream.Collectors; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,12 +16,13 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; import jadx.core.dex.nodes.RootNode; public class UserRenames { private static final Logger LOG = LoggerFactory.getLogger(UserRenames.class); - public static void applyForNodes(RootNode root) { + public static void apply(RootNode root) { ICodeData codeData = root.getArgs().getCodeData(); if (codeData == null || codeData.getRenames().isEmpty()) { return; @@ -51,7 +51,7 @@ public class UserRenames { IJavaNodeRef nodeRef = rename.getNodeRef(); switch (nodeRef.getType()) { case CLASS: - cls.getClassInfo().changeShortName(rename.getNewName()); + cls.rename(rename.getNewName()); break; case FIELD: @@ -59,7 +59,7 @@ public class UserRenames { if (fieldNode == null) { LOG.warn("Field reference not found: {}", nodeRef); } else { - fieldNode.getFieldInfo().setAlias(rename.getNewName()); + fieldNode.rename(rename.getNewName()); } break; @@ -77,39 +77,17 @@ public class UserRenames { } } - // TODO: Very inefficient!!! Add PackageInfo class to build package hierarchy private static void applyPkgRenames(RootNode root, List renames) { - List classes = root.getClasses(false); renames.stream() .filter(r -> r.getNodeRef().getType() == IJavaNodeRef.RefType.PKG) .forEach(pkgRename -> { String pkgFullName = pkgRename.getNodeRef().getDeclaringClass(); - String pkgFullNameDot = pkgFullName + "."; - for (ClassNode cls : classes) { - ClassInfo clsInfo = cls.getClassInfo(); - String pkg = clsInfo.getPackage(); - if (pkg.equals(pkgFullName)) { - clsInfo.changePkg(cutLastPkgPart(clsInfo.getAliasPkg()) + '.' + pkgRename.getNewName()); - } else if (pkg.startsWith(pkgFullNameDot)) { - clsInfo.changePkg(rebuildPkgMiddle(clsInfo.getAliasPkg(), pkgFullName, pkgRename.getNewName())); - } + PackageNode pkgNode = root.resolvePackage(pkgFullName); + if (pkgNode == null) { + LOG.warn("Package for rename not found: {}", pkgFullName); + } else { + pkgNode.rename(pkgRename.getNewName()); } }); } - - @NotNull - private static String cutLastPkgPart(String pkgFullName) { - int lastDotIndex = pkgFullName.lastIndexOf('.'); - if (lastDotIndex == -1) { - return pkgFullName; - } - return pkgFullName.substring(0, lastDotIndex); - } - - private static String rebuildPkgMiddle(String aliasPkg, String renameOriginPkg, String newName) { - String[] aliasParts = aliasPkg.split("\\."); - String[] renameParts = renameOriginPkg.split("\\."); - aliasParts[renameParts.length - 1] = newName; - return String.join(".", aliasParts); - } } diff --git a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java index c2b8ca609..2377aaee9 100644 --- a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java @@ -336,6 +336,13 @@ public class StringUtils { return WORD_SEPARATORS.indexOf(chr) != -1; } + public static String removeSuffix(String str, String suffix) { + if (str.endsWith(suffix)) { + return str.substring(0, str.length() - suffix.length()); + } + return str; + } + public static String getDateText() { return new SimpleDateFormat("HH:mm:ss").format(new Date()); } diff --git a/jadx-core/src/main/java/jadx/core/deobf/ClsAliasPair.java b/jadx-core/src/main/java/jadx/core/utils/kotlin/ClsAliasPair.java similarity index 91% rename from jadx-core/src/main/java/jadx/core/deobf/ClsAliasPair.java rename to jadx-core/src/main/java/jadx/core/utils/kotlin/ClsAliasPair.java index bdd7eb86c..3d7fe9f20 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/ClsAliasPair.java +++ b/jadx-core/src/main/java/jadx/core/utils/kotlin/ClsAliasPair.java @@ -1,4 +1,4 @@ -package jadx.core.deobf; +package jadx.core.utils.kotlin; public class ClsAliasPair { private final String pkg; diff --git a/jadx-core/src/main/java/jadx/core/utils/kotlin/KotlinMetadataUtils.java b/jadx-core/src/main/java/jadx/core/utils/kotlin/KotlinMetadataUtils.java index 2eba05b0f..fa05dc305 100644 --- a/jadx-core/src/main/java/jadx/core/utils/kotlin/KotlinMetadataUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/kotlin/KotlinMetadataUtils.java @@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory; import jadx.api.plugins.input.data.annotations.EncodedType; import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.IAnnotation; -import jadx.core.deobf.ClsAliasPair; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.info.ClassInfo; diff --git a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestInheritedMethodRename.java b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestInheritedMethodRename.java index 4ff38eb6b..600dee685 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestInheritedMethodRename.java +++ b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestInheritedMethodRename.java @@ -34,8 +34,8 @@ public class TestInheritedMethodRename extends IntegrationTest { assertThat(getClassNode(TestCls.class)) .code() - .containsOne("public void m0call() {") + .containsOne("public void m1call() {") .doesNotContain(".call();") - .containsOne(".m0call();"); + .containsOne(".m1call();"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestKotlinMetadata.java b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestKotlinMetadata.java index c35dc206a..f08fc0705 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestKotlinMetadata.java +++ b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestKotlinMetadata.java @@ -43,7 +43,6 @@ public class TestKotlinMetadata extends SmaliTest { private void prepareArgs(boolean parseKotlinMetadata) { enableDeobfuscation(); args.setDeobfuscationMinLength(100); // rename everything - args.setDeobfuscationForceSave(true); getArgs().setParseKotlinMetadata(parseKotlinMetadata); disableCompilation(); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitiveChecks.java b/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitiveChecks.java index 58e1e4137..5b05e3c48 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitiveChecks.java +++ b/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitiveChecks.java @@ -29,7 +29,7 @@ public class TestCaseSensitiveChecks extends SmaliTest { for (ClassNode cls : classes) { assertThat(cls.getPackage(), is(Consts.DEFAULT_PACKAGE_NAME)); } - long namesCount = classes.stream().map(cls -> cls.getShortName().toLowerCase()).distinct().count(); + long namesCount = classes.stream().map(cls -> cls.getAlias().toLowerCase()).distinct().count(); assertThat(namesCount, is(2L)); } @@ -41,7 +41,7 @@ public class TestCaseSensitiveChecks extends SmaliTest { for (ClassNode cls : classes) { assertThat(cls.getPackage(), is(Consts.DEFAULT_PACKAGE_NAME)); } - List names = classes.stream().map(ClassNode::getShortName).collect(Collectors.toList()); + List names = classes.stream().map(ClassNode::getAlias).collect(Collectors.toList()); assertThat(names, Matchers.containsInAnyOrder("A", "a")); } @@ -54,7 +54,7 @@ public class TestCaseSensitiveChecks extends SmaliTest { assertThat(cls.getPackage(), not(emptyString())); assertThat(cls.getPackage(), not(Consts.DEFAULT_PACKAGE_NAME)); } - long namesCount = classes.stream().map(cls -> cls.getShortName().toLowerCase()).distinct().count(); + long namesCount = classes.stream().map(cls -> cls.getAlias().toLowerCase()).distinct().count(); assertThat(namesCount, is(2L)); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/names/TestReservedPackageNames.java b/jadx-core/src/test/java/jadx/tests/integration/names/TestReservedPackageNames.java index db0d05049..7659ff949 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/names/TestReservedPackageNames.java +++ b/jadx-core/src/test/java/jadx/tests/integration/names/TestReservedPackageNames.java @@ -1,5 +1,6 @@ package jadx.tests.integration.names; +import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; @@ -42,16 +43,11 @@ public class TestReservedPackageNames extends SmaliTest { @Test public void testRenameDisabled() { - args.setRenameCaseSensitive(false); - args.setRenameValid(false); - args.setRenamePrintable(false); - disableCompilation(); - - List clsList = loadFromSmaliFiles(); - for (ClassNode cls : clsList) { + args.setRenameFlags(Collections.emptySet()); + for (ClassNode cls : loadFromSmaliFiles()) { String code = cls.getCode().toString(); - if (cls.getShortName().equals("A")) { + if (cls.getAlias().equals("A")) { assertThat(code, containsString("package do.if;")); } } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 353a5fdd3..09215b751 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -255,20 +255,6 @@ public class JadxSettingsWindow extends JDialog { needReload(); }); - JCheckBox deobfSourceAlias = new JCheckBox(); - deobfSourceAlias.setSelected(settings.isDeobfuscationUseSourceNameAsAlias()); - deobfSourceAlias.addItemListener(e -> { - settings.setDeobfuscationUseSourceNameAsAlias(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - }); - - JCheckBox deobfKotlinMetadata = new JCheckBox(); - deobfKotlinMetadata.setSelected(settings.isDeobfuscationParseKotlinMetadata()); - deobfKotlinMetadata.addItemListener(e -> { - settings.setDeobfuscationParseKotlinMetadata(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - }); - JComboBox resNamesSource = new JComboBox<>(ResourceNameSource.values()); resNamesSource.setSelectedItem(settings.getResourceNameSource()); resNamesSource.addActionListener(e -> { @@ -290,14 +276,11 @@ public class JadxSettingsWindow extends JDialog { deobfGroup.addRow(NLS.str("preferences.deobfuscation_on"), deobfOn); deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLenSpinner); deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLenSpinner); - deobfGroup.addRow(NLS.str("preferences.deobfuscation_source_alias"), deobfSourceAlias); - deobfGroup.addRow(NLS.str("preferences.deobfuscation_kotlin_metadata"), deobfKotlinMetadata); deobfGroup.addRow(NLS.str("preferences.deobfuscation_res_name_source"), resNamesSource); deobfGroup.addRow(NLS.str("preferences.deobfuscation_map_file_mode"), deobfMapFileModeCB); deobfGroup.end(); - Collection connectedComponents = - Arrays.asList(minLenSpinner, maxLenSpinner, deobfSourceAlias, deobfKotlinMetadata); + Collection connectedComponents = Arrays.asList(minLenSpinner, maxLenSpinner); deobfOn.addItemListener(e -> enableComponentList(connectedComponents, e.getStateChange() == ItemEvent.SELECTED)); enableComponentList(connectedComponents, settings.isDeobfuscationOn()); return deobfGroup; @@ -325,10 +308,26 @@ public class JadxSettingsWindow extends JDialog { needReload(); }); + JCheckBox deobfSourceAlias = new JCheckBox(); + deobfSourceAlias.setSelected(settings.isDeobfuscationUseSourceNameAsAlias()); + deobfSourceAlias.addItemListener(e -> { + settings.setDeobfuscationUseSourceNameAsAlias(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + }); + + JCheckBox deobfKotlinMetadata = new JCheckBox(); + deobfKotlinMetadata.setSelected(settings.isDeobfuscationParseKotlinMetadata()); + deobfKotlinMetadata.addItemListener(e -> { + settings.setDeobfuscationParseKotlinMetadata(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + }); + SettingsGroup group = new SettingsGroup(NLS.str("preferences.rename")); group.addRow(NLS.str("preferences.rename_case"), renameCaseSensitive); group.addRow(NLS.str("preferences.rename_valid"), renameValid); group.addRow(NLS.str("preferences.rename_printable"), renamePrintable); + group.addRow(NLS.str("preferences.deobfuscation_source_alias"), deobfSourceAlias); + group.addRow(NLS.str("preferences.deobfuscation_kotlin_metadata"), deobfKotlinMetadata); return group; } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/core/nodes/IRenameNode.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/core/nodes/IRenameNode.java new file mode 100644 index 000000000..ee454928a --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/core/nodes/IRenameNode.java @@ -0,0 +1,6 @@ +package jadx.api.core.nodes; + +public interface IRenameNode { + + void rename(String newName); +} diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt index bcfba99bf..636d1f2e1 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt @@ -1,6 +1,8 @@ package jadx.plugins.script.runtime.data import jadx.api.core.nodes.IRootNode +import jadx.core.dex.attributes.AFlag +import jadx.core.dex.attributes.IAttributeNode import jadx.core.dex.nodes.IDexNode import jadx.core.dex.nodes.RootNode import jadx.plugins.script.runtime.JadxScriptInstance @@ -20,29 +22,33 @@ class RenamePass(private val jadx: JadxScriptInstance) { override fun init(root: IRootNode) { val rootNode = root as RootNode for (pkgNode in rootNode.packages) { - makeNewName.invoke(pkgNode.pkgInfo.name, pkgNode)?.let { - pkgNode.rename(it) - } + rename(makeNewName, pkgNode, pkgNode.pkgInfo.name) } for (cls in rootNode.classes) { - makeNewName.invoke(cls.classInfo.shortName, cls)?.let { - cls.classInfo.changeShortName(it) - } + rename(makeNewName, cls, cls.name) for (mth in cls.methods) { - if (mth.isConstructor) { - continue - } - makeNewName.invoke(mth.name, mth)?.let { - mth.rename(it) + if (!mth.isConstructor) { + rename(makeNewName, mth, mth.name) } } for (fld in cls.fields) { - makeNewName.invoke(fld.name, fld)?.let { - fld.fieldInfo.alias = it - } + rename(makeNewName, fld, fld.name) } } } + + private inline fun rename( + makeNewName: (String, IDexNode) -> String?, + node: T, + name: String + ) { + if (node is IAttributeNode && node.contains(AFlag.DONT_RENAME)) { + return + } + makeNewName.invoke(name, node)?.let { + node.rename(it) + } + } }) } }