diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index cc0b7cb22..01fb832eb 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -64,6 +64,9 @@ public class JadxCLIArgs implements IJadxArgs { @Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map") protected boolean deobfuscationForceSave = false; + @Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias") + protected boolean deobfuscationUseSourceNameAsAlias = false; + @Parameter(names = {"-h", "--help"}, description = "print this help", help = true) protected boolean printHelp = false; @@ -242,4 +245,9 @@ public class JadxCLIArgs implements IJadxArgs { public boolean isDeobfuscationForceSave() { return deobfuscationForceSave; } + + @Override + public boolean useSourceNameAsClassAlias() { + return deobfuscationUseSourceNameAsAlias; + } } diff --git a/jadx-core/src/main/java/jadx/api/DefaultJadxArgs.java b/jadx-core/src/main/java/jadx/api/DefaultJadxArgs.java index 3b922fcf8..c692d16ad 100644 --- a/jadx-core/src/main/java/jadx/api/DefaultJadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/DefaultJadxArgs.java @@ -68,4 +68,9 @@ public class DefaultJadxArgs implements IJadxArgs { public boolean isDeobfuscationForceSave() { return false; } + + @Override + public boolean useSourceNameAsClassAlias() { + return false; + } } diff --git a/jadx-core/src/main/java/jadx/api/IJadxArgs.java b/jadx-core/src/main/java/jadx/api/IJadxArgs.java index c5bcfa34d..a0fbc0f72 100644 --- a/jadx-core/src/main/java/jadx/api/IJadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/IJadxArgs.java @@ -28,4 +28,6 @@ public interface IJadxArgs { int getDeobfuscationMaxLength(); boolean isDeobfuscationForceSave(); + + boolean useSourceNameAsClassAlias(); } diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java index 16ce67f7c..4a8bad03b 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -6,13 +6,17 @@ import jadx.core.dex.attributes.nodes.SourceFileAttr; 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.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -40,11 +44,16 @@ public class Deobfuscator { private final Map fldMap = new HashMap(); private final Map mthMap = new HashMap(); + private final Map ovrdMap = new HashMap(); + private final List ovrd = new ArrayList(); + private final PackageNode rootPackage = new PackageNode(""); private final Set pkgSet = new TreeSet(); private final int maxLength; private final int minLength; + private final boolean useSourceNameAsAlias; + private int pkgIndex = 0; private int clsIndex = 0; private int fldIndex = 0; @@ -56,6 +65,7 @@ public class Deobfuscator { this.minLength = args.getDeobfuscationMinLength(); this.maxLength = args.getDeobfuscationMaxLength(); + this.useSourceNameAsAlias = args.useSourceNameAsClassAlias(); this.deobfPresets = new DeobfPresets(this, deobfMapFile); } @@ -95,6 +105,32 @@ public class Deobfuscator { processClass(dexNode, cls); } } + postProcess(); + } + + private void postProcess() { + int id = 1; + for (OverridedMethodsNode o : ovrd) { + + Iterator it = o.getMethods().iterator(); + if (it.hasNext()) { + MethodInfo mth = it.next(); + + if (mth.isRenamed() && !mth.isAliasFromPreset()) { + mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName()))); + } + String firstMethodAlias = mth.getAlias(); + + while (it.hasNext()) { + mth = it.next(); + if (!mth.getAlias().equals(firstMethodAlias)) { + mth.setAlias(firstMethodAlias); + } + } + } + + id++; + } } void clear() { @@ -102,6 +138,98 @@ public class Deobfuscator { clsMap.clear(); fldMap.clear(); mthMap.clear(); + + ovrd.clear(); + ovrdMap.clear(); + } + + @Nullable + private static ClassNode resolveOverridingInternal(DexNode dex, ClassNode cls, String signature, + Set overrideSet, ClassNode rootClass) { + ClassNode result = null; + + for (MethodNode m : cls.getMethods()) { + if (m.getMethodInfo().getShortId().startsWith(signature)) { + result = cls; + if (!overrideSet.contains(m.getMethodInfo())) { + overrideSet.add(m.getMethodInfo()); + } + break; + } + } + + ArgType superClass = cls.getSuperClass(); + if (superClass != null) { + ClassNode superNode = dex.resolveClass(superClass); + if (superNode != null) { + ClassNode clsWithMth = resolveOverridingInternal(dex, superNode, signature, overrideSet, rootClass); + if (clsWithMth != null) { + if ((result != null) && (result != cls)) { + if (clsWithMth != result) { + LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'", + signature, + result.getFullName(), clsWithMth.getFullName(), + rootClass.getFullName())); + } + } else { + result = clsWithMth; + } + } + } + } + + for (ArgType iFaceType : cls.getInterfaces()) { + ClassNode iFaceNode = dex.resolveClass(iFaceType); + if (iFaceNode != null) { + ClassNode clsWithMth = resolveOverridingInternal(dex, iFaceNode, signature, overrideSet, rootClass); + if (clsWithMth != null) { + if ((result != null) && (result != cls)) { + if (clsWithMth != result) { + LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'", + signature, + result.getFullName(), clsWithMth.getFullName(), + rootClass.getFullName())); + } + } else { + result = clsWithMth; + } + } + } + } + + return result; + } + + private void resolveOverriding(DexNode dex, ClassNode cls, MethodNode mth) { + Set overrideSet = new HashSet(); + resolveOverridingInternal(dex, cls, mth.getMethodInfo().makeSignature(false), overrideSet, cls); + + if (overrideSet.size() > 1) { + OverridedMethodsNode overrideNode = null; + for (MethodInfo _mth : overrideSet) { + if (ovrdMap.containsKey(_mth)) { + overrideNode = ovrdMap.get(_mth); + break; + } + } + + if (overrideNode == null) { + overrideNode = new OverridedMethodsNode(overrideSet); + ovrd.add(overrideNode); + } + + for (MethodInfo _mth : overrideSet) { + if (!ovrdMap.containsKey(_mth)) { + ovrdMap.put(_mth, overrideNode); + if (!overrideNode.contains(_mth)) { + overrideNode.add(_mth); + } + } + } + } else { + overrideSet.clear(); + overrideSet = null; + } } private void processClass(DexNode dex, ClassNode cls) { @@ -123,6 +251,10 @@ public class Deobfuscator { if (alias != null) { methodInfo.setAlias(alias); } + + if (mth.isVirtual()) { + resolveOverriding(dex, cls, mth); + } } } @@ -212,7 +344,12 @@ public class Deobfuscator { private String makeClsAlias(ClassNode cls) { ClassInfo classInfo = cls.getClassInfo(); - String alias = getAliasFromSourceFile(cls); + String alias = null; + + if (this.useSourceNameAsAlias) { + alias = getAliasFromSourceFile(cls); + } + if (alias == null) { String clsName = classInfo.getShortName(); alias = String.format("C%04d%s", clsIndex++, makeName(clsName)); @@ -269,6 +406,7 @@ public class Deobfuscator { alias = deobfPresets.getForMth(methodInfo); if (alias != null) { mthMap.put(methodInfo, alias); + methodInfo.setAliasFromPreset(true); return alias; } if (shouldRename(mth.getName())) { diff --git a/jadx-core/src/main/java/jadx/core/deobf/OverridedMethodsNode.java b/jadx-core/src/main/java/jadx/core/deobf/OverridedMethodsNode.java new file mode 100644 index 000000000..90062cf9d --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/OverridedMethodsNode.java @@ -0,0 +1,26 @@ +package jadx.core.deobf; + +import jadx.core.dex.info.MethodInfo; + +import java.util.Set; + +/* package */ class OverridedMethodsNode { + + private Set methods; + + public OverridedMethodsNode(Set methodsSet) { + methods = methodsSet; + } + + public boolean contains(MethodInfo mth) { + return methods.contains(mth); + } + + public void add(MethodInfo mth) { + methods.add(mth); + } + + public Set getMethods() { + return methods; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java index ef25003d4..8f9bb8993 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java @@ -18,11 +18,13 @@ public final class MethodInfo { private final ClassInfo declClass; private final String shortId; private String alias; + private boolean aliasFromPreset; private MethodInfo(DexNode dex, int mthIndex) { MethodId mthId = dex.getMethodId(mthIndex); name = dex.getString(mthId.getNameIndex()); alias = name; + aliasFromPreset = false; declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex()); ProtoId proto = dex.getProtoId(mthId.getProtoIndex()); @@ -109,6 +111,14 @@ public final class MethodInfo { return !name.equals(alias); } + public boolean isAliasFromPreset() { + return aliasFromPreset; + } + + public void setAliasFromPreset(boolean value) { + aliasFromPreset = value; + } + @Override public int hashCode() { int result = declClass.hashCode(); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 6a77ffce2..beb92d2a6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -86,10 +86,10 @@ public class ClassNode extends LineAttrNode implements ILoadable { fields = new ArrayList(fieldsCount); for (Method mth : clsData.getDirectMethods()) { - methods.add(new MethodNode(this, mth)); + methods.add(new MethodNode(this, mth, false)); } for (Method mth : clsData.getVirtualMethods()) { - methods.add(new MethodNode(this, mth)); + methods.add(new MethodNode(this, mth, true)); } for (Field f : clsData.getStaticFields()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 1a4fc9c65..3ab19d41a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -56,6 +56,7 @@ public class MethodNode extends LineAttrNode implements ILoadable { private int codeSize; private int debugInfoOffset; private boolean noCode; + private boolean methodIsVirtual; private ArgType retType; private RegisterArg thisArg; @@ -71,12 +72,13 @@ public class MethodNode extends LineAttrNode implements ILoadable { private List exceptionHandlers = Collections.emptyList(); private List loops = Collections.emptyList(); - public MethodNode(ClassNode classNode, Method mthData) { + public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) { this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex()); this.parentClass = classNode; this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD); this.noCode = mthData.getCodeOffset() == 0; this.methodData = noCode ? null : mthData; + this.methodIsVirtual = isVirtual; } @Override @@ -538,6 +540,10 @@ public class MethodNode extends LineAttrNode implements ILoadable { return result; } + public boolean isVirtual() { + return methodIsVirtual; + } + public int getRegsCount() { return regsCount; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java index 043e1ee71..9e3013025 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java @@ -30,7 +30,13 @@ public class RenameVisitor extends AbstractVisitor { @Override public void init(RootNode root) { IJadxArgs args = root.getArgs(); - File deobfMapFile = new File(args.getOutDir(), "deobf_map.jobf"); + + final String firstInputFileName = root.getDexNodes().get(0).getInputFile().getFile().getAbsolutePath(); + final String inputPath = org.apache.commons.io.FilenameUtils.getFullPathNoEndSeparator( + firstInputFileName); + final String inputName = org.apache.commons.io.FilenameUtils.getBaseName(firstInputFileName); + + File deobfMapFile = new File(inputPath, inputName + ".jobf"); deobfuscator = new Deobfuscator(args, root.getDexNodes(), deobfMapFile); boolean deobfuscationOn = args.isDeobfuscationOn(); if (deobfuscationOn) { diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 82f0d4d02..8b13d6434 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -132,6 +132,10 @@ public class JadxSettings extends JadxCLIArgs { this.deobfuscationForceSave = deobfuscationForceSave; } + public void setUseSourceNameAsClassAlias(boolean useSourceNameAsAlias) { + this.deobfuscationUseSourceNameAsAlias = useSourceNameAsAlias; + } + public Font getFont() { if (fontStr.isEmpty()) { return DEFAULT_FONT; diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index bc13a73cb..37c405f91 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -141,11 +141,21 @@ public class JadxSettingsWindow extends JDialog { } }); + JCheckBox deobfSourceAlias = new JCheckBox(); + deobfSourceAlias.setSelected(settings.useSourceNameAsClassAlias()); + deobfSourceAlias.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setUseSourceNameAsClassAlias(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + SettingsGroup deobfGroup = new SettingsGroup(NLS.str("preferences.deobfuscation")); deobfGroup.addRow(NLS.str("preferences.deobfuscation_on"), deobfOn); deobfGroup.addRow(NLS.str("preferences.deobfuscation_force"), deobfForce); deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLen); deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLen); + deobfGroup.addRow(NLS.str("preferences.deobfuscation_source_alias"), deobfSourceAlias); deobfGroup.end(); return deobfGroup; } diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 69148c3cd..958db49cd 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -62,6 +62,7 @@ preferences.deobfuscation_on=Enable deobfuscation preferences.deobfuscation_force=Force rewrite deobfuscation map file preferences.deobfuscation_min_len=Minimum name length preferences.deobfuscation_max_len=Maximum name length +preferences.deobfuscation_source_alias=Use source file name as class name alias preferences.save=Save preferences.cancel=Cancel