refactor: split and simplify deobfuscator
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<String, IDexNode> predicate;
|
||||
|
||||
public AnyRenameCondition(BiPredicate<String, IDexNode> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<IDexTreeVisitor> 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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<>();
|
||||
|
||||
private final PackageNode rootPackage = new PackageNode("");
|
||||
private final Set<String> pkgSet = new TreeSet<>();
|
||||
private final Set<String> reservedClsNames = new HashSet<>();
|
||||
|
||||
private final NavigableSet<MethodNode> 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<String, String> 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 <b>create</b> 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<ClassInfo, DeobfClsInfo> getClsMap() {
|
||||
return clsMap;
|
||||
}
|
||||
|
||||
public Map<FieldInfo, String> getFldMap() {
|
||||
return fldMap;
|
||||
}
|
||||
|
||||
public Map<MethodInfo, String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<PackageNode> 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<PackageNode> 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<PackageNode> 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<PackageNode> 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<PackageNode> getParentPackages() {
|
||||
Deque<PackageNode> 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public class TldHelper {
|
||||
|
||||
private static Set<String> loadTldFile() {
|
||||
Set<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ public enum AFlag {
|
||||
THIS,
|
||||
SUPER,
|
||||
|
||||
/**
|
||||
* Mark Android resources class
|
||||
*/
|
||||
ANDROID_R_CLASS,
|
||||
|
||||
/**
|
||||
* RegisterArg attribute for method arguments
|
||||
*/
|
||||
|
||||
@@ -111,19 +111,12 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
}
|
||||
|
||||
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<ClassInfo> {
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return fieldInfo.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String alias) {
|
||||
fieldInfo.setAlias(alias);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Package
|
||||
}
|
||||
|
||||
private static @Nullable PackageNode getParentPkg(RootNode root, PackageInfo pgkInfo) {
|
||||
return root.resolvePackage(pgkInfo.getParentPkg());
|
||||
PackageInfo parentPkg = pgkInfo.getParentPkg();
|
||||
if (parentPkg == null) {
|
||||
return null;
|
||||
}
|
||||
return getOrBuild(root, parentPkg.getFullName());
|
||||
}
|
||||
|
||||
private PackageNode(RootNode root, @Nullable PackageNode parentPkg, PackageInfo pkgInfo) {
|
||||
this.root = root;
|
||||
this.parentPkg = parentPkg;
|
||||
this.pkgInfo = pkgInfo;
|
||||
this.aliasPkgInfo = pkgInfo;
|
||||
}
|
||||
|
||||
public void rename(String alias) {
|
||||
rename(alias, true);
|
||||
@Override
|
||||
public void rename(String newName) {
|
||||
rename(newName, true);
|
||||
}
|
||||
|
||||
public void rename(String alias, boolean runUpdates) {
|
||||
public void rename(String newName, boolean runUpdates) {
|
||||
String alias;
|
||||
boolean isFullAlias;
|
||||
if (newName.indexOf('/') != -1) {
|
||||
alias = newName.replace('/', '.');
|
||||
isFullAlias = true;
|
||||
} else if (newName.endsWith(".")) {
|
||||
// treat as full pkg, remove ending dot
|
||||
alias = newName.substring(0, newName.length() - 1);
|
||||
isFullAlias = true;
|
||||
} else {
|
||||
alias = newName;
|
||||
isFullAlias = alias.indexOf('.') != -1;
|
||||
}
|
||||
if (isFullAlias) {
|
||||
setFullAlias(alias, runUpdates);
|
||||
} else {
|
||||
setLeafAlias(alias, runUpdates);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLeafAlias(String alias, boolean runUpdates) {
|
||||
if (pkgInfo.getName().equals(alias)) {
|
||||
aliasPkgInfo = pkgInfo;
|
||||
return;
|
||||
} else {
|
||||
aliasPkgInfo = PackageInfo.fromShortName(root, getParentAliasPkgInfo(), alias);
|
||||
}
|
||||
if (runUpdates) {
|
||||
updatePackages(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFullAlias(String fullAlias, boolean runUpdates) {
|
||||
if (pkgInfo.getFullName().equals(fullAlias)) {
|
||||
aliasPkgInfo = pkgInfo;
|
||||
} else {
|
||||
aliasPkgInfo = PackageInfo.fromFullPkg(root, fullAlias);
|
||||
}
|
||||
aliasPkgInfo = PackageInfo.fromShortName(root, getParentAliasPkgInfo(), alias);
|
||||
if (runUpdates) {
|
||||
updatePackages(this);
|
||||
}
|
||||
@@ -91,6 +130,20 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
|
||||
return aliasPkgInfo;
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
if (pkgInfo == aliasPkgInfo) {
|
||||
return false;
|
||||
}
|
||||
return !pkgInfo.getName().equals(aliasPkgInfo.getName());
|
||||
}
|
||||
|
||||
public boolean hasParentAlias() {
|
||||
if (pkgInfo == aliasPkgInfo) {
|
||||
return false;
|
||||
}
|
||||
return !Objects.equals(pkgInfo.getParentPkg(), aliasPkgInfo.getParentPkg());
|
||||
}
|
||||
|
||||
public PackageNode getParentPkg() {
|
||||
return parentPkg;
|
||||
}
|
||||
@@ -99,6 +152,14 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
|
||||
return parentPkg == null ? null : parentPkg.aliasPkgInfo;
|
||||
}
|
||||
|
||||
public boolean isRoot() {
|
||||
return parentPkg == null;
|
||||
}
|
||||
|
||||
public boolean isLeaf() {
|
||||
return subPackages.isEmpty();
|
||||
}
|
||||
|
||||
public List<PackageNode> getSubPackages() {
|
||||
return subPackages;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ public class RootNode implements IRootNode {
|
||||
private List<ClassNode> classes = new ArrayList<>();
|
||||
|
||||
private final Map<String, PackageNode> pkgMap = new HashMap<>();
|
||||
private final List<PackageNode> 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<ILoadResult> 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<PackageNode> getPackages() {
|
||||
List<PackageNode> 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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ClassNode> 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<String> 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<String> 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<MethodNode> 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<ClassNode> classes) {
|
||||
Set<String> rootPkgs = collectRootPkgs(classes);
|
||||
private static void processRootPackages(IAliasProvider aliasProvider, RootNode root, List<ClassNode> classes) {
|
||||
Set<String> 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<String> collectRootPkgs(List<ClassNode> classes) {
|
||||
Set<String> fullPkgs = new HashSet<>();
|
||||
for (ClassNode cls : classes) {
|
||||
fullPkgs.add(cls.getClassInfo().getAliasPkg());
|
||||
}
|
||||
private static Set<String> collectRootPkgs(RootNode root) {
|
||||
Set<String> 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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ICodeRename> renames) {
|
||||
List<ClassNode> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.core.deobf;
|
||||
package jadx.core.utils.kotlin;
|
||||
|
||||
public class ClsAliasPair {
|
||||
private final String pkg;
|
||||
@@ -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;
|
||||
|
||||
@@ -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();");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<String> names = classes.stream().map(ClassNode::getShortName).collect(Collectors.toList());
|
||||
List<String> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ClassNode> 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;"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ResourceNameSource> 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<JComponent> connectedComponents =
|
||||
Arrays.asList(minLenSpinner, maxLenSpinner, deobfSourceAlias, deobfKotlinMetadata);
|
||||
Collection<JComponent> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package jadx.api.core.nodes;
|
||||
|
||||
public interface IRenameNode {
|
||||
|
||||
void rename(String newName);
|
||||
}
|
||||
+20
-14
@@ -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 <T : IDexNode> 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user