diff --git a/README.md b/README.md index 67570f544..ca24307a8 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ options: --deobf - activate deobfuscation --deobf-min - min length of name, renamed if shorter, default: 3 --deobf-max - max length of name, renamed if longer, default: 64 + --deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px --deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension --deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file: 'read' - read if found, don't save (default) @@ -131,7 +132,6 @@ options: 'auto' - automatically select best name (default) 'resources' - use resources names 'code' - use R class fields names - --deobf-whitelist - list of ':' separated packages (suffix '.*') and class names that will not be deobfuscated --use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply --rename-flags - fix options (comma-separated list of): 'case' - fix case sensitivity issues (according to --fs-case-sensitive option), diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index b8c4fdc4c..102c6e4a1 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -2,6 +2,7 @@ package jadx.cli; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.List; @@ -27,6 +28,7 @@ import jadx.api.args.GeneratedRenamesMappingFileMode; import jadx.api.args.IntegerFormat; import jadx.api.args.ResourceNameSource; import jadx.api.args.UserRenamesMappingsMode; +import jadx.core.deobf.conditions.DeobfWhitelist; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.files.FileUtils; @@ -137,9 +139,11 @@ public class JadxCLIArgs { @Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer") protected int deobfuscationMaxLength = 64; - @Parameter(names = { "--deobf-whitelist}" }, description = "debfucation whitelist") - protected String deobfuscationWhitelist = - "android.support.v4.*:android.support.v7.*:android.support.v4.os.*:android.support.annotation.Px:androidx.core.os.*:androidx.annotation.Px"; + @Parameter( + names = { "--deobf-whitelist" }, + description = "space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation" + ) + protected String deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR; @Parameter( names = { "--deobf-cfg-file" }, @@ -320,7 +324,7 @@ public class JadxCLIArgs { args.setGeneratedRenamesMappingFileMode(generatedRenamesMappingFileMode); args.setDeobfuscationMinLength(deobfuscationMinLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength); - args.setDeobfuscationWhitelist(deobfuscationWhitelist); + args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" "))); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames); args.setResourceNameSource(resourceNameSource); @@ -448,8 +452,8 @@ public class JadxCLIArgs { return deobfuscationMaxLength; } - public String getDeobfuscationWhitelist() { - return deobfuscationWhitelist; + public String getDeobfuscationWhitelistStr() { + return deobfuscationWhitelistStr; } public String getGeneratedRenamesMappingFile() { diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index d14674cb0..36edb9f73 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -32,7 +32,8 @@ import jadx.api.plugins.loader.JadxPluginLoader; import jadx.api.usage.IUsageInfoCache; import jadx.api.usage.impl.InMemoryUsageInfoCache; import jadx.core.deobf.DeobfAliasProvider; -import jadx.core.deobf.DeobfCondition; +import jadx.core.deobf.conditions.DeobfWhitelist; +import jadx.core.deobf.conditions.JadxRenameConditions; import jadx.core.plugins.PluginContext; import jadx.core.utils.files.FileUtils; @@ -103,7 +104,10 @@ public class JadxArgs implements Closeable { private int deobfuscationMinLength = 0; private int deobfuscationMaxLength = Integer.MAX_VALUE; - private String deobfuscationWhitelist = ""; + /** + * List of classes and packages (ends with '.*') to exclude from deobfuscation + */ + private List deobfuscationWhitelist = DeobfWhitelist.DEFAULT_LIST; /** * Nodes alias provider for deobfuscator and rename visitor @@ -113,7 +117,7 @@ public class JadxArgs implements Closeable { /** * Condition to rename node in deobfuscator */ - private IRenameCondition renameCondition = new DeobfCondition(); + private IRenameCondition renameCondition = JadxRenameConditions.buildDefault(); private boolean escapeUnicode = false; private boolean replaceConsts = true; @@ -436,11 +440,11 @@ public class JadxArgs implements Closeable { this.deobfuscationMaxLength = deobfuscationMaxLength; } - public String getDeobfuscationWhitelist() { + public List getDeobfuscationWhitelist() { return this.deobfuscationWhitelist; } - public void setDeobfuscationWhitelist(String deobfuscationWhitelist) { + public void setDeobfuscationWhitelist(List deobfuscationWhitelist) { this.deobfuscationWhitelist = deobfuscationWhitelist; } @@ -678,7 +682,7 @@ public class JadxArgs implements Closeable { public String makeCodeArgsHash(@Nullable JadxDecompiler decompiler) { String argStr = "args:" + decompilationMode + useImports + showInconsistentCode + inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda - + deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + + deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationWhitelist + resourceNameSource + useKotlinMethodsForVarNames + insertDebugLines + extractFinally diff --git a/jadx-core/src/main/java/jadx/api/deobf/IDeobfCondition.java b/jadx-core/src/main/java/jadx/api/deobf/IDeobfCondition.java new file mode 100644 index 000000000..c4d9c923f --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/deobf/IDeobfCondition.java @@ -0,0 +1,31 @@ +package jadx.api.deobf; + +import jadx.api.deobf.impl.CombineDeobfConditions; +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; + +/** + * Utility interface to simplify merging several rename conditions to build {@link IRenameCondition} + * instance with {@link CombineDeobfConditions#combine(IDeobfCondition...)}. + */ +public interface IDeobfCondition { + + enum Action { + NO_ACTION, + FORCE_RENAME, + FORBID_RENAME, + } + + void init(RootNode root); + + Action check(PackageNode pkg); + + Action check(ClassNode cls); + + Action check(FieldNode fld); + + Action check(MethodNode mth); +} diff --git a/jadx-core/src/main/java/jadx/api/deobf/impl/CombineDeobfConditions.java b/jadx-core/src/main/java/jadx/api/deobf/impl/CombineDeobfConditions.java new file mode 100644 index 000000000..ddd404e01 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/deobf/impl/CombineDeobfConditions.java @@ -0,0 +1,73 @@ +package jadx.api.deobf.impl; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +import jadx.api.deobf.IDeobfCondition; +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 CombineDeobfConditions implements IRenameCondition { + + public static IRenameCondition combine(List conditions) { + return new CombineDeobfConditions(conditions); + } + + public static IRenameCondition combine(IDeobfCondition... conditions) { + return new CombineDeobfConditions(Arrays.asList(conditions)); + } + + private final List conditions; + + private CombineDeobfConditions(List conditions) { + if (conditions == null || conditions.isEmpty()) { + throw new IllegalArgumentException("Conditions list can't be empty"); + } + this.conditions = conditions; + } + + private boolean combineFunc(Function check) { + for (IDeobfCondition c : conditions) { + switch (check.apply(c)) { + case NO_ACTION: + // ignore + break; + case FORCE_RENAME: + return true; + case FORBID_RENAME: + return false; + } + } + return false; + } + + @Override + public void init(RootNode root) { + conditions.forEach(c -> c.init(root)); + } + + @Override + public boolean shouldRename(PackageNode pkg) { + return combineFunc(c -> c.check(pkg)); + } + + @Override + public boolean shouldRename(ClassNode cls) { + return combineFunc(c -> c.check(cls)); + } + + @Override + public boolean shouldRename(FieldNode fld) { + return combineFunc(c -> c.check(fld)); + } + + @Override + public boolean shouldRename(MethodNode mth) { + return combineFunc(c -> c.check(mth)); + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfCondition.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfCondition.java deleted file mode 100644 index 241256f97..000000000 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfCondition.java +++ /dev/null @@ -1,103 +0,0 @@ -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/DeobfWhitelist.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfWhitelist.java deleted file mode 100644 index de9455623..000000000 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfWhitelist.java +++ /dev/null @@ -1,78 +0,0 @@ -package jadx.core.deobf; - -import java.util.ArrayList; -import java.util.List; - -import jadx.api.JadxArgs; -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 DeobfWhitelist implements IRenameCondition { - - private static DeobfWhitelist whitelist = null; - - private final List packages = new ArrayList<>(); - - private final List classes = new ArrayList<>(); - - public static DeobfWhitelist getWhitelist() { - if (whitelist == null) { - whitelist = new DeobfWhitelist(); - } - return whitelist; - } - - @Override - public void init(RootNode root) { - packages.clear(); - classes.clear(); - JadxArgs args = root.getArgs(); - String whitelistStr = args.getDeobfuscationWhitelist(); - String[] whitelisteItems = whitelistStr.split(":"); - for (String whitelistItem : whitelisteItems) { - if (!whitelistItem.isEmpty()) { - if (whitelistItem.endsWith(".*")) { - packages.add(whitelistItem.substring(0, whitelistItem.length() - 2)); - } else { - classes.add(whitelistItem); - } - } - } - } - - @Override - public boolean shouldRename(PackageNode pkg) { - String fullname = pkg.getPkgInfo().getFullName(); - for (String p : packages) { - if (fullname.equals(p)) { - return false; - } - } - return true; - } - - @Override - public boolean shouldRename(ClassNode cls) { - String fullname = cls.getFullName(); - for (String c : classes) { - if (fullname.equals(c)) { - return false; - } - } - 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/core/deobf/DeobfuscatorVisitor.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java index 0f189c565..7366779e2 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java @@ -19,8 +19,6 @@ public class DeobfuscatorVisitor extends AbstractVisitor { if (!args.isDeobfuscationOn()) { return; } - DeobfWhitelist whitelist = DeobfWhitelist.getWhitelist(); - whitelist.init(root); DeobfPresets mapping = DeobfPresets.build(root); if (args.getGeneratedRenamesMappingFileMode().shouldRead()) { if (mapping.load()) { @@ -34,11 +32,9 @@ public class DeobfuscatorVisitor extends AbstractVisitor { } public static void process(RootNode root, IRenameCondition renameCondition, IAliasProvider aliasProvider) { - DeobfWhitelist whitelist = DeobfWhitelist.getWhitelist(); - boolean pkgUpdated = false; for (PackageNode pkg : root.getPackages()) { - if (whitelist.shouldRename(pkg) && renameCondition.shouldRename(pkg)) { + if (renameCondition.shouldRename(pkg)) { String alias = aliasProvider.forPackage(pkg); if (alias != null) { pkg.rename(alias, false); diff --git a/jadx-core/src/main/java/jadx/core/deobf/TldHelper.java b/jadx-core/src/main/java/jadx/core/deobf/TldHelper.java deleted file mode 100644 index 0f59cf2ef..000000000 --- a/jadx-core/src/main/java/jadx/core/deobf/TldHelper.java +++ /dev/null @@ -1,37 +0,0 @@ -package jadx.core.deobf; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.util.HashSet; -import java.util.Set; - -import jadx.core.utils.exceptions.JadxRuntimeException; - -/** - * Provides a list of all top level domains with 3 characters and less, - * so we can exclude them from deobfuscation. - */ -public class TldHelper { - - private static final Set TLD_SET = loadTldFile(); - - private static Set loadTldFile() { - Set tldNames = new HashSet<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHelper.class.getResourceAsStream("tld_3.txt")))) { - String line; - while ((line = reader.readLine()) != null) { - line = line.trim(); - if (!line.startsWith("#") && !line.isEmpty()) { - tldNames.add(line); - } - } - return tldNames; - } catch (Exception e) { - throw new JadxRuntimeException("Failed to load top level domain list tld_3.txt", e); - } - } - - public static boolean contains(String name) { - return TLD_SET.contains(name); - } -} diff --git a/jadx-core/src/main/java/jadx/core/deobf/conditions/AbstractDeobfCondition.java b/jadx-core/src/main/java/jadx/core/deobf/conditions/AbstractDeobfCondition.java new file mode 100644 index 000000000..4dab4d4a5 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/AbstractDeobfCondition.java @@ -0,0 +1,35 @@ +package jadx.core.deobf.conditions; + +import jadx.api.deobf.IDeobfCondition; +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 abstract class AbstractDeobfCondition implements IDeobfCondition { + + @Override + public void init(RootNode root) { + } + + @Override + public Action check(PackageNode pkg) { + return Action.NO_ACTION; + } + + @Override + public Action check(ClassNode cls) { + return Action.NO_ACTION; + } + + @Override + public Action check(FieldNode fld) { + return Action.NO_ACTION; + } + + @Override + public Action check(MethodNode mth) { + return Action.NO_ACTION; + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/conditions/AvoidClsAndPkgNamesCollision.java b/jadx-core/src/main/java/jadx/core/deobf/conditions/AvoidClsAndPkgNamesCollision.java new file mode 100644 index 000000000..41c0b0d15 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/AvoidClsAndPkgNamesCollision.java @@ -0,0 +1,29 @@ +package jadx.core.deobf.conditions; + +import java.util.HashSet; +import java.util.Set; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; + +public class AvoidClsAndPkgNamesCollision extends AbstractDeobfCondition { + + private final Set avoidClsNames = new HashSet<>(); + + @Override + public void init(RootNode root) { + avoidClsNames.clear(); + for (PackageNode pkg : root.getPackages()) { + avoidClsNames.add(pkg.getName()); + } + } + + @Override + public Action check(ClassNode cls) { + if (avoidClsNames.contains(cls.getAlias())) { + return Action.FORCE_RENAME; + } + return Action.NO_ACTION; + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/conditions/BaseDeobfCondition.java b/jadx-core/src/main/java/jadx/core/deobf/conditions/BaseDeobfCondition.java new file mode 100644 index 000000000..74736e8f5 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/BaseDeobfCondition.java @@ -0,0 +1,49 @@ +package jadx.core.deobf.conditions; + +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.PackageNode; + +/** + * Disable deobfuscation for nodes: + * - with 'DONT_RENAME' flag + * - already renamed + */ +public class BaseDeobfCondition extends AbstractDeobfCondition { + + @Override + public Action check(PackageNode pkg) { + if (pkg.contains(AFlag.DONT_RENAME) || pkg.hasAlias()) { + return Action.FORBID_RENAME; + } + return Action.NO_ACTION; + } + + @Override + public Action check(ClassNode cls) { + if (cls.contains(AFlag.DONT_RENAME) || cls.getClassInfo().hasAlias()) { + return Action.FORBID_RENAME; + } + return Action.NO_ACTION; + } + + @Override + public Action check(MethodNode mth) { + if (mth.contains(AFlag.DONT_RENAME) + || mth.getMethodInfo().hasAlias() + || mth.isConstructor()) { + return Action.FORBID_RENAME; + } + return Action.NO_ACTION; + } + + @Override + public Action check(FieldNode fld) { + if (fld.contains(AFlag.DONT_RENAME) || fld.getFieldInfo().hasAlias()) { + return Action.FORBID_RENAME; + } + return Action.NO_ACTION; + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/conditions/DeobfLengthCondition.java b/jadx-core/src/main/java/jadx/core/deobf/conditions/DeobfLengthCondition.java new file mode 100644 index 000000000..ebc868470 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/DeobfLengthCondition.java @@ -0,0 +1,50 @@ +package jadx.core.deobf.conditions; + +import jadx.api.JadxArgs; +import jadx.api.deobf.IDeobfCondition; +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 DeobfLengthCondition implements IDeobfCondition { + + private int minLength; + private int maxLength; + + @Override + public void init(RootNode root) { + JadxArgs args = root.getArgs(); + this.minLength = args.getDeobfuscationMinLength(); + this.maxLength = args.getDeobfuscationMaxLength(); + } + + private Action checkName(String s) { + int len = s.length(); + if (len < minLength || len > maxLength) { + return Action.FORCE_RENAME; + } + return Action.NO_ACTION; + } + + @Override + public Action check(PackageNode pkg) { + return checkName(pkg.getName()); + } + + @Override + public Action check(ClassNode cls) { + return checkName(cls.getName()); + } + + @Override + public Action check(FieldNode fld) { + return checkName(fld.getName()); + } + + @Override + public Action check(MethodNode mth) { + return checkName(mth.getName()); + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/conditions/DeobfWhitelist.java b/jadx-core/src/main/java/jadx/core/deobf/conditions/DeobfWhitelist.java new file mode 100644 index 000000000..68712609f --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/DeobfWhitelist.java @@ -0,0 +1,58 @@ +package jadx.core.deobf.conditions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.PackageNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.Utils; + +public class DeobfWhitelist extends AbstractDeobfCondition { + + public static final List DEFAULT_LIST = Arrays.asList( + "android.support.v4.*", + "android.support.v7.*", + "android.support.v4.os.*", + "android.support.annotation.Px", + "androidx.core.os.*", + "androidx.annotation.Px"); + + public static final String DEFAULT_STR = Utils.listToString(DEFAULT_LIST, " "); + + private final Set packages = new HashSet<>(); + private final Set classes = new HashSet<>(); + + @Override + public void init(RootNode root) { + packages.clear(); + classes.clear(); + for (String whitelistItem : root.getArgs().getDeobfuscationWhitelist()) { + if (!whitelistItem.isEmpty()) { + if (whitelistItem.endsWith(".*")) { + packages.add(whitelistItem.substring(0, whitelistItem.length() - 2)); + } else { + classes.add(whitelistItem); + } + } + } + } + + @Override + public Action check(PackageNode pkg) { + if (packages.contains(pkg.getPkgInfo().getFullName())) { + return Action.FORBID_RENAME; + } + return Action.NO_ACTION; + } + + @Override + public Action check(ClassNode cls) { + if (classes.contains(cls.getClassInfo().getFullName())) { + return Action.FORBID_RENAME; + } + return Action.NO_ACTION; + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/conditions/ExcludeAndroidRClass.java b/jadx-core/src/main/java/jadx/core/deobf/conditions/ExcludeAndroidRClass.java new file mode 100644 index 000000000..f3030ecbc --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/ExcludeAndroidRClass.java @@ -0,0 +1,45 @@ +package jadx.core.deobf.conditions; + +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; + +public class ExcludeAndroidRClass extends AbstractDeobfCondition { + + @Override + public Action check(ClassNode cls) { + if (isR(cls.getTopParentClass())) { + return Action.FORBID_RENAME; + } + return Action.NO_ACTION; + } + + 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/conditions/ExcludePackageWithTLDNames.java b/jadx-core/src/main/java/jadx/core/deobf/conditions/ExcludePackageWithTLDNames.java new file mode 100644 index 000000000..62a755bb3 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/ExcludePackageWithTLDNames.java @@ -0,0 +1,42 @@ +package jadx.core.deobf.conditions; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Set; +import java.util.stream.Collectors; + +import jadx.core.dex.nodes.PackageNode; +import jadx.core.utils.exceptions.JadxRuntimeException; + +/** + * Provides a list of all top level domains with 3 characters and less, + * so we can exclude them from deobfuscation. + */ +public class ExcludePackageWithTLDNames extends AbstractDeobfCondition { + + /** + * Lazy load TLD set + */ + private static class TldHolder { + private static final Set TLD_SET = loadTldFile(); + } + + private static Set loadTldFile() { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tld_3.txt")))) { + return reader.lines() + .map(String::trim) + .filter(line -> !line.startsWith("#") && !line.isEmpty()) + .collect(Collectors.toSet()); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to load top level domain list file: tld_3.txt", e); + } + } + + @Override + public Action check(PackageNode pkg) { + if (TldHolder.TLD_SET.contains(pkg.getName())) { + return Action.FORBID_RENAME; + } + return Action.NO_ACTION; + } +} diff --git a/jadx-core/src/main/java/jadx/core/deobf/conditions/JadxRenameConditions.java b/jadx-core/src/main/java/jadx/core/deobf/conditions/JadxRenameConditions.java new file mode 100644 index 000000000..e3702f782 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/JadxRenameConditions.java @@ -0,0 +1,30 @@ +package jadx.core.deobf.conditions; + +import java.util.ArrayList; +import java.util.List; + +import jadx.api.deobf.IDeobfCondition; +import jadx.api.deobf.IRenameCondition; +import jadx.api.deobf.impl.CombineDeobfConditions; + +public class JadxRenameConditions { + + /** + * This method provides a mutable list of default deobfuscation conditions used by jadx. + * To build {@link IRenameCondition} use {@link CombineDeobfConditions#combine(List)} method. + */ + public static List buildDefaultDeobfConditions() { + List list = new ArrayList<>(); + list.add(new BaseDeobfCondition()); + list.add(new DeobfWhitelist()); + list.add(new ExcludePackageWithTLDNames()); + list.add(new ExcludeAndroidRClass()); + list.add(new AvoidClsAndPkgNamesCollision()); + list.add(new DeobfLengthCondition()); + return list; + } + + public static IRenameCondition buildDefault() { + return CombineDeobfConditions.combine(buildDefaultDeobfConditions()); + } +} 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 66c70744e..c9fdc43fb 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 @@ -130,6 +130,14 @@ public class PackageNode extends LineAttrNode } } + public String getName() { + return pkgInfo.getName(); + } + + public String getFullName() { + return pkgInfo.getFullName(); + } + public PackageInfo getPkgInfo() { return pkgInfo; } diff --git a/jadx-core/src/main/java/jadx/core/utils/BetterName.java b/jadx-core/src/main/java/jadx/core/utils/BetterName.java index 0f88676e2..d0c292861 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BetterName.java +++ b/jadx-core/src/main/java/jadx/core/utils/BetterName.java @@ -9,7 +9,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.deobf.NameMapper; -import jadx.core.deobf.TldHelper; public class BetterName { private static final Logger LOG = LoggerFactory.getLogger(BetterName.class); @@ -43,9 +42,6 @@ public class BetterName { if (NameMapper.isValidIdentifier(str)) { rating += 50; } - if (TldHelper.contains(str)) { - rating += 20; - } if (str.contains("_")) { // rare in obfuscated names rating += 100; diff --git a/jadx-core/src/main/resources/jadx/core/deobf/tld_3.txt b/jadx-core/src/main/resources/jadx/core/deobf/conditions/tld_3.txt similarity index 100% rename from jadx-core/src/main/resources/jadx/core/deobf/tld_3.txt rename to jadx-core/src/main/resources/jadx/core/deobf/conditions/tld_3.txt diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 610f4f2bc..7fe73ef84 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -369,6 +369,10 @@ public class JadxSettings extends JadxCLIArgs { this.deobfuscationMaxLength = deobfuscationMaxLength; } + public void setDeobfuscationWhitelistStr(String value) { + this.deobfuscationWhitelistStr = value; + } + public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode mode) { this.generatedRenamesMappingFileMode = mode; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java index 6aa8a8b14..044d5c610 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java @@ -238,13 +238,13 @@ public class JadxSettingsWindow extends JDialog { JButton editWhitelistedEntities = new JButton(NLS.str("preferences.excludedPackages.button")); editWhitelistedEntities.addActionListener(event -> { - - String oldEWhitelistedEntities = settings.getDeobfuscationWhitelist(); - String result = JOptionPane.showInputDialog(this, NLS.str("preferences.deobfuscation_whitelist.editDialog"), - settings.getDeobfuscationWhitelist()); + String prevWhitelistedEntities = settings.getDeobfuscationWhitelistStr(); + String result = JOptionPane.showInputDialog(this, + NLS.str("preferences.deobfuscation_whitelist.editDialog"), + prevWhitelistedEntities); if (result != null) { - settings.setExcludedPackages(result); - if (!oldEWhitelistedEntities.equals(result)) { + settings.setDeobfuscationWhitelistStr(result); + if (!prevWhitelistedEntities.equals(result)) { needReload(); } }