refactor: split and simplify deobfuscator

This commit is contained in:
Skylot
2022-07-31 16:16:10 +01:00
parent bc7300bd01
commit d4927db52b
38 changed files with 899 additions and 1074 deletions
@@ -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,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);
}
@@ -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)
}
}
})
}
}