refactor(deobf): split deobfuscation conditions (#2040)

This commit is contained in:
Skylot
2023-11-05 20:00:22 +00:00
parent f7002c7fad
commit a989fa7e64
22 changed files with 482 additions and 246 deletions
+1 -1
View File
@@ -120,6 +120,7 @@ options:
--deobf - activate deobfuscation --deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3 --deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64 --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 - 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: --deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
'read' - read if found, don't save (default) 'read' - read if found, don't save (default)
@@ -131,7 +132,6 @@ options:
'auto' - automatically select best name (default) 'auto' - automatically select best name (default)
'resources' - use resources names 'resources' - use resources names
'code' - use R class fields 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 --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): --rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option), 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
@@ -2,6 +2,7 @@ package jadx.cli;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -27,6 +28,7 @@ import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.IntegerFormat; import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource; import jadx.api.args.ResourceNameSource;
import jadx.api.args.UserRenamesMappingsMode; import jadx.api.args.UserRenamesMappingsMode;
import jadx.core.deobf.conditions.DeobfWhitelist;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; 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") @Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
protected int deobfuscationMaxLength = 64; protected int deobfuscationMaxLength = 64;
@Parameter(names = { "--deobf-whitelist}" }, description = "debfucation whitelist") @Parameter(
protected String deobfuscationWhitelist = names = { "--deobf-whitelist" },
"android.support.v4.*:android.support.v7.*:android.support.v4.os.*:android.support.annotation.Px:androidx.core.os.*:androidx.annotation.Px"; description = "space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation"
)
protected String deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR;
@Parameter( @Parameter(
names = { "--deobf-cfg-file" }, names = { "--deobf-cfg-file" },
@@ -320,7 +324,7 @@ public class JadxCLIArgs {
args.setGeneratedRenamesMappingFileMode(generatedRenamesMappingFileMode); args.setGeneratedRenamesMappingFileMode(generatedRenamesMappingFileMode);
args.setDeobfuscationMinLength(deobfuscationMinLength); args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setDeobfuscationWhitelist(deobfuscationWhitelist); args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" ")));
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames); args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setResourceNameSource(resourceNameSource); args.setResourceNameSource(resourceNameSource);
@@ -448,8 +452,8 @@ public class JadxCLIArgs {
return deobfuscationMaxLength; return deobfuscationMaxLength;
} }
public String getDeobfuscationWhitelist() { public String getDeobfuscationWhitelistStr() {
return deobfuscationWhitelist; return deobfuscationWhitelistStr;
} }
public String getGeneratedRenamesMappingFile() { public String getGeneratedRenamesMappingFile() {
+10 -6
View File
@@ -32,7 +32,8 @@ import jadx.api.plugins.loader.JadxPluginLoader;
import jadx.api.usage.IUsageInfoCache; import jadx.api.usage.IUsageInfoCache;
import jadx.api.usage.impl.InMemoryUsageInfoCache; import jadx.api.usage.impl.InMemoryUsageInfoCache;
import jadx.core.deobf.DeobfAliasProvider; 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.plugins.PluginContext;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -103,7 +104,10 @@ public class JadxArgs implements Closeable {
private int deobfuscationMinLength = 0; private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE; private int deobfuscationMaxLength = Integer.MAX_VALUE;
private String deobfuscationWhitelist = ""; /**
* List of classes and packages (ends with '.*') to exclude from deobfuscation
*/
private List<String> deobfuscationWhitelist = DeobfWhitelist.DEFAULT_LIST;
/** /**
* Nodes alias provider for deobfuscator and rename visitor * Nodes alias provider for deobfuscator and rename visitor
@@ -113,7 +117,7 @@ public class JadxArgs implements Closeable {
/** /**
* Condition to rename node in deobfuscator * Condition to rename node in deobfuscator
*/ */
private IRenameCondition renameCondition = new DeobfCondition(); private IRenameCondition renameCondition = JadxRenameConditions.buildDefault();
private boolean escapeUnicode = false; private boolean escapeUnicode = false;
private boolean replaceConsts = true; private boolean replaceConsts = true;
@@ -436,11 +440,11 @@ public class JadxArgs implements Closeable {
this.deobfuscationMaxLength = deobfuscationMaxLength; this.deobfuscationMaxLength = deobfuscationMaxLength;
} }
public String getDeobfuscationWhitelist() { public List<String> getDeobfuscationWhitelist() {
return this.deobfuscationWhitelist; return this.deobfuscationWhitelist;
} }
public void setDeobfuscationWhitelist(String deobfuscationWhitelist) { public void setDeobfuscationWhitelist(List<String> deobfuscationWhitelist) {
this.deobfuscationWhitelist = deobfuscationWhitelist; this.deobfuscationWhitelist = deobfuscationWhitelist;
} }
@@ -678,7 +682,7 @@ public class JadxArgs implements Closeable {
public String makeCodeArgsHash(@Nullable JadxDecompiler decompiler) { public String makeCodeArgsHash(@Nullable JadxDecompiler decompiler) {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda + inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationWhitelist
+ resourceNameSource + resourceNameSource
+ useKotlinMethodsForVarNames + useKotlinMethodsForVarNames
+ insertDebugLines + extractFinally + insertDebugLines + extractFinally
@@ -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);
}
@@ -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<IDeobfCondition> conditions) {
return new CombineDeobfConditions(conditions);
}
public static IRenameCondition combine(IDeobfCondition... conditions) {
return new CombineDeobfConditions(Arrays.asList(conditions));
}
private final List<IDeobfCondition> conditions;
private CombineDeobfConditions(List<IDeobfCondition> conditions) {
if (conditions == null || conditions.isEmpty()) {
throw new IllegalArgumentException("Conditions list can't be empty");
}
this.conditions = conditions;
}
private boolean combineFunc(Function<IDeobfCondition, IDeobfCondition.Action> 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));
}
}
@@ -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<String> 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;
}
}
@@ -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<String> packages = new ArrayList<>();
private final List<String> 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;
}
}
@@ -19,8 +19,6 @@ public class DeobfuscatorVisitor extends AbstractVisitor {
if (!args.isDeobfuscationOn()) { if (!args.isDeobfuscationOn()) {
return; return;
} }
DeobfWhitelist whitelist = DeobfWhitelist.getWhitelist();
whitelist.init(root);
DeobfPresets mapping = DeobfPresets.build(root); DeobfPresets mapping = DeobfPresets.build(root);
if (args.getGeneratedRenamesMappingFileMode().shouldRead()) { if (args.getGeneratedRenamesMappingFileMode().shouldRead()) {
if (mapping.load()) { if (mapping.load()) {
@@ -34,11 +32,9 @@ public class DeobfuscatorVisitor extends AbstractVisitor {
} }
public static void process(RootNode root, IRenameCondition renameCondition, IAliasProvider aliasProvider) { public static void process(RootNode root, IRenameCondition renameCondition, IAliasProvider aliasProvider) {
DeobfWhitelist whitelist = DeobfWhitelist.getWhitelist();
boolean pkgUpdated = false; boolean pkgUpdated = false;
for (PackageNode pkg : root.getPackages()) { for (PackageNode pkg : root.getPackages()) {
if (whitelist.shouldRename(pkg) && renameCondition.shouldRename(pkg)) { if (renameCondition.shouldRename(pkg)) {
String alias = aliasProvider.forPackage(pkg); String alias = aliasProvider.forPackage(pkg);
if (alias != null) { if (alias != null) {
pkg.rename(alias, false); pkg.rename(alias, false);
@@ -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<String> TLD_SET = loadTldFile();
private static Set<String> loadTldFile() {
Set<String> 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);
}
}
@@ -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;
}
}
@@ -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<String> 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;
}
}
@@ -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;
}
}
@@ -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());
}
}
@@ -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<String> 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<String> packages = new HashSet<>();
private final Set<String> 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;
}
}
@@ -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;
}
}
@@ -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<String> TLD_SET = loadTldFile();
}
private static Set<String> 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;
}
}
@@ -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<IDeobfCondition> buildDefaultDeobfConditions() {
List<IDeobfCondition> 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());
}
}
@@ -130,6 +130,14 @@ public class PackageNode extends LineAttrNode
} }
} }
public String getName() {
return pkgInfo.getName();
}
public String getFullName() {
return pkgInfo.getFullName();
}
public PackageInfo getPkgInfo() { public PackageInfo getPkgInfo() {
return pkgInfo; return pkgInfo;
} }
@@ -9,7 +9,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.core.deobf.NameMapper; import jadx.core.deobf.NameMapper;
import jadx.core.deobf.TldHelper;
public class BetterName { public class BetterName {
private static final Logger LOG = LoggerFactory.getLogger(BetterName.class); private static final Logger LOG = LoggerFactory.getLogger(BetterName.class);
@@ -43,9 +42,6 @@ public class BetterName {
if (NameMapper.isValidIdentifier(str)) { if (NameMapper.isValidIdentifier(str)) {
rating += 50; rating += 50;
} }
if (TldHelper.contains(str)) {
rating += 20;
}
if (str.contains("_")) { if (str.contains("_")) {
// rare in obfuscated names // rare in obfuscated names
rating += 100; rating += 100;
@@ -369,6 +369,10 @@ public class JadxSettings extends JadxCLIArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength; this.deobfuscationMaxLength = deobfuscationMaxLength;
} }
public void setDeobfuscationWhitelistStr(String value) {
this.deobfuscationWhitelistStr = value;
}
public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode mode) { public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode mode) {
this.generatedRenamesMappingFileMode = mode; this.generatedRenamesMappingFileMode = mode;
} }
@@ -238,13 +238,13 @@ public class JadxSettingsWindow extends JDialog {
JButton editWhitelistedEntities = new JButton(NLS.str("preferences.excludedPackages.button")); JButton editWhitelistedEntities = new JButton(NLS.str("preferences.excludedPackages.button"));
editWhitelistedEntities.addActionListener(event -> { editWhitelistedEntities.addActionListener(event -> {
String prevWhitelistedEntities = settings.getDeobfuscationWhitelistStr();
String oldEWhitelistedEntities = settings.getDeobfuscationWhitelist(); String result = JOptionPane.showInputDialog(this,
String result = JOptionPane.showInputDialog(this, NLS.str("preferences.deobfuscation_whitelist.editDialog"), NLS.str("preferences.deobfuscation_whitelist.editDialog"),
settings.getDeobfuscationWhitelist()); prevWhitelistedEntities);
if (result != null) { if (result != null) {
settings.setExcludedPackages(result); settings.setDeobfuscationWhitelistStr(result);
if (!oldEWhitelistedEntities.equals(result)) { if (!prevWhitelistedEntities.equals(result)) {
needReload(); needReload();
} }
} }