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 76bdb1053..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,6 +44,9 @@ 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(); @@ -98,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() { @@ -105,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) { @@ -126,6 +251,10 @@ public class Deobfuscator { if (alias != null) { methodInfo.setAlias(alias); } + + if (mth.isVirtual()) { + resolveOverriding(dex, cls, mth); + } } } @@ -277,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; }