diff --git a/README.md b/README.md index c827dcc75d7..4d1395a5446 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,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-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.* android.os.* androidx.core.os.* androidx.annotation.* --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) diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index df7ff1cfdb2..1e67a14ef6b 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -110,7 +110,7 @@ public class JadxArgs implements Closeable { /** * List of classes and packages (ends with '.*') to exclude from deobfuscation */ - private List deobfuscationWhitelist = DeobfWhitelist.DEFAULT_LIST; + private List deobfuscationWhitelist = new ArrayList<>(DeobfWhitelist.DEFAULT_LIST); /** * Nodes alias provider for deobfuscator and rename visitor diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 1079f2c82ce..dfad61f8940 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -118,7 +118,6 @@ public void load() { 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/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 12b48e39a3d..b42e426b83e 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -13,6 +13,7 @@ import jadx.api.CommentsLevel; import jadx.api.JadxArgs; import jadx.core.deobf.DeobfuscatorVisitor; +import jadx.core.deobf.InitRenameProviders; import jadx.core.deobf.SaveDeobfMapping; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.visitors.AnonymousClassVisitor; @@ -102,6 +103,7 @@ public static List getPreDecompilePassesList() { passes.add(new CollectConstValues()); // rename and deobfuscation + passes.add(new InitRenameProviders()); passes.add(new DeobfuscatorVisitor()); passes.add(new SourceFileRename()); passes.add(new RenameVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/deobf/InitRenameProviders.java b/jadx-core/src/main/java/jadx/core/deobf/InitRenameProviders.java new file mode 100644 index 00000000000..ac880ae0d0c --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/InitRenameProviders.java @@ -0,0 +1,20 @@ +package jadx.core.deobf; + +import jadx.api.JadxArgs; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.utils.exceptions.JadxException; + +public class InitRenameProviders extends AbstractVisitor { + + @Override + public void init(RootNode root) throws JadxException { + JadxArgs args = root.getArgs(); + if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) { + args.getAliasProvider().init(root); + } + if (args.isDeobfuscationOn()) { + args.getRenameCondition().init(root); + } + } +} 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 index 68712609ff8..2fad9257579 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/conditions/DeobfWhitelist.java +++ b/jadx-core/src/main/java/jadx/core/deobf/conditions/DeobfWhitelist.java @@ -5,39 +5,81 @@ import java.util.List; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + 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.Utils; public class DeobfWhitelist extends AbstractDeobfCondition { + private static final Logger LOG = LoggerFactory.getLogger(DeobfWhitelist.class); public static final List DEFAULT_LIST = Arrays.asList( - "android.support.v4.*", - "android.support.v7.*", - "android.support.v4.os.*", - "android.support.annotation.Px", + "android.support.*", + "android.os.*", "androidx.core.os.*", - "androidx.annotation.Px"); + "androidx.annotation.*"); public static final String DEFAULT_STR = Utils.listToString(DEFAULT_LIST, " "); private final Set packages = new HashSet<>(); - private final Set classes = new HashSet<>(); + private final Set classes = new HashSet<>(); + private boolean reportMissingItems = false; @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); - } + List excludeList = root.getArgs().getDeobfuscationWhitelist(); + reportMissingItems = !excludeList.equals(DEFAULT_LIST); + for (String name : excludeList) { + if (name.isEmpty()) { + continue; + } + if (name.endsWith(".*")) { + excludePackage(root, name.substring(0, name.length() - 2)); + } else { + excludeClass(root, name); + } + } + LOG.debug("Excluded from deobfuscation: {} packages, {} classes", packages.size(), classes.size()); + } + + private void excludeClass(RootNode root, String clsFullName) { + ClassNode cls = root.resolveClass(clsFullName); + if (cls == null) { + if (reportMissingItems) { + LOG.info("Can't exclude from deobfuscation: class '{}' not found", clsFullName); + } + return; + } + excludeClsNode(cls); + } + + private void excludeClsNode(ClassNode cls) { + classes.add(cls); + cls.addInfoComment("Class excluded from deobfuscation"); + } + + private void excludePackage(RootNode root, String fullPkgName) { + PackageNode pkg = root.resolvePackage(fullPkgName); + if (pkg == null) { + if (reportMissingItems) { + LOG.info("Can't exclude from deobfuscation: package '{}' not found", fullPkgName); } + return; } + excludePkgNode(pkg); + } + + private void excludePkgNode(PackageNode pkg) { + packages.add(pkg.getFullName()); + pkg.getClasses().forEach(this::excludeClsNode); + pkg.getSubPackages().forEach(this::excludePkgNode); } @Override @@ -50,9 +92,19 @@ public Action check(PackageNode pkg) { @Override public Action check(ClassNode cls) { - if (classes.contains(cls.getClassInfo().getFullName())) { + if (classes.contains(cls)) { return Action.FORBID_RENAME; } return Action.NO_ACTION; } + + @Override + public Action check(FieldNode fld) { + return check(fld.getParentClass()); + } + + @Override + public Action check(MethodNode mth) { + return check(mth.getParentClass()); + } } 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 45570d96355..c7f2bb8a82a 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 @@ -109,15 +109,6 @@ public RootNode(JadxArgs args) { this.typeUtils = new TypeUtils(this); } - 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 (ICodeLoader codeLoader : loadedInputs) { codeLoader.visitClasses(cls -> { diff --git a/jadx-core/src/test/java/jadx/tests/integration/deobf/a/TestNegativeRenameCondition.java b/jadx-core/src/test/java/jadx/tests/integration/deobf/a/TestNegativeRenameCondition.java index 50a7377afaf..ef80e4d383f 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/deobf/a/TestNegativeRenameCondition.java +++ b/jadx-core/src/test/java/jadx/tests/integration/deobf/a/TestNegativeRenameCondition.java @@ -1,9 +1,14 @@ package jadx.tests.integration.deobf.a; import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.Test; +import jadx.api.deobf.IDeobfCondition; +import jadx.api.deobf.impl.CombineDeobfConditions; +import jadx.core.deobf.conditions.AvoidClsAndPkgNamesCollision; +import jadx.core.deobf.conditions.JadxRenameConditions; import jadx.tests.api.IntegrationTest; import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; @@ -34,6 +39,11 @@ public void test() { // disable all renaming options args.setRenameFlags(Collections.emptySet()); + // disable rename by collision between class and package names + List list = JadxRenameConditions.buildDefaultDeobfConditions(); + list.removeIf(c -> c.getClass().equals(AvoidClsAndPkgNamesCollision.class)); + args.setRenameCondition(CombineDeobfConditions.combine(list)); + assertThat(getClassNode(TestCls.class)) .code() .doesNotContain("renamed from") 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 697adb6bb38..d14b58b3bb3 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -35,6 +35,7 @@ import jadx.api.args.UserRenamesMappingsMode; import jadx.cli.JadxCLIArgs; import jadx.cli.LogHelper; +import jadx.core.deobf.conditions.DeobfWhitelist; import jadx.gui.cache.code.CodeCacheMode; import jadx.gui.cache.usage.UsageCacheMode; import jadx.gui.settings.data.ShortcutsWrapper; @@ -53,7 +54,7 @@ public class JadxSettings extends JadxCLIArgs { private static final Path USER_HOME = Paths.get(System.getProperty("user.home")); private static final int RECENT_PROJECTS_COUNT = 30; - private static final int CURRENT_SETTINGS_VERSION = 20; + private static final int CURRENT_SETTINGS_VERSION = 21; private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont(); @@ -805,6 +806,10 @@ private void upgradeSettings(int fromVersion) { tabDndGhostType = TabDndGhostType.OUTLINE; fromVersion++; } + if (fromVersion == 20) { + deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR; + fromVersion++; + } if (fromVersion != CURRENT_SETTINGS_VERSION) { LOG.warn("Incorrect settings upgrade. Expected version: {}, got: {}", CURRENT_SETTINGS_VERSION, fromVersion); }