提交 a3b961e7 编写于 作者: S Skylot

core: fix method deobfuscation (#241)

上级 0e4c8df4
......@@ -29,3 +29,4 @@ jadx-output/
*.log
*.cfg
jadx-core/src/test/java/jadx/tests/external/
......@@ -435,7 +435,7 @@ public class ClassGen {
}
public void useClass(CodeWriter code, ArgType type) {
useClass(code, ClassInfo.extCls(cls.dex(), type));
useClass(code, ClassInfo.extCls(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
......
......@@ -612,7 +612,7 @@ public class InsnGen {
MethodInfo callMth = insn.getCallMth();
// inline method
MethodNode callMthNode = mth.dex().deepResolveMethod(callMth);
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
if (callMthNode != null) {
if (inlineMethod(callMthNode, insn, code)) {
return;
......
......@@ -159,7 +159,7 @@ public class NameGen {
if (alias != null) {
return alias;
}
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
ClassInfo extClsInfo = ClassInfo.extCls(mth.root(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
......
package jadx.core.deobf;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
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;
......@@ -26,6 +14,18 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
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;
public class Deobfuscator {
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
......@@ -89,7 +89,7 @@ public class Deobfuscator {
private void preProcess() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
doClass(cls);
preProcessClass(cls);
}
}
}
......@@ -101,7 +101,7 @@ public class Deobfuscator {
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
processClass(dexNode, cls);
processClass(cls);
}
}
postProcess();
......@@ -128,7 +128,6 @@ public class Deobfuscator {
mth.setAlias(aliasToUse);
mth.setAliasFromPreset(aliasFromPreset);
}
id++;
}
}
......@@ -145,15 +144,13 @@ public class Deobfuscator {
@Nullable
private static ClassNode resolveOverridingInternal(DexNode dex, ClassNode cls, String signature,
Set<MethodInfo> overrideSet, ClassNode rootClass) {
Set<MethodInfo> 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());
}
overrideSet.add(m.getMethodInfo());
break;
}
}
......@@ -196,13 +193,14 @@ public class Deobfuscator {
}
}
}
return result;
}
private void resolveOverriding(DexNode dex, ClassNode cls, MethodNode mth) {
private void resolveOverriding(MethodNode mth) {
Set<MethodInfo> overrideSet = new HashSet<>();
resolveOverridingInternal(dex, cls, mth.getMethodInfo().makeSignature(false), overrideSet, cls);
String mthSignature = mth.getMethodInfo().makeSignature(false);
ClassNode cls = mth.getParentClass();
resolveOverridingInternal(mth.dex(), cls, mthSignature, overrideSet, cls);
if (overrideSet.size() > 1) {
OverridedMethodsNode overrideNode = null;
for (MethodInfo _mth : overrideSet) {
......@@ -223,34 +221,42 @@ public class Deobfuscator {
}
}
}
} else {
overrideSet.clear();
}
overrideSet.clear();
}
private void processClass(DexNode dex, ClassNode cls) {
private void processClass(ClassNode cls) {
ClassInfo clsInfo = cls.getClassInfo();
String fullName = getClassFullName(clsInfo);
if (!fullName.equals(clsInfo.getFullName())) {
clsInfo.rename(dex, fullName);
clsInfo.rename(cls.dex().root(), fullName);
}
for (FieldNode field : cls.getFields()) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
fieldInfo.setAlias(alias);
}
renameField(field);
}
for (MethodNode mth : cls.getMethods()) {
MethodInfo methodInfo = mth.getMethodInfo();
String alias = getMethodAlias(mth);
if (alias != null) {
methodInfo.setAlias(alias);
}
renameMethod(mth);
}
for (ClassNode innerCls : cls.getInnerClasses()) {
processClass(innerCls);
}
}
if (mth.isVirtual()) {
resolveOverriding(dex, cls, mth);
}
public void renameField(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
fieldInfo.setAlias(alias);
}
}
public void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
mth.getMethodInfo().setAlias(alias);
}
if (mth.isVirtual()) {
resolveOverriding(mth);
}
}
......@@ -311,7 +317,7 @@ public class Deobfuscator {
return prefix + clsInfo.getShortName();
}
private void doClass(ClassNode cls) {
private void preProcessClass(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String pkgFullName = classInfo.getPackage();
PackageNode pkg = getPackageNode(pkgFullName, true);
......@@ -320,13 +326,14 @@ public class Deobfuscator {
String alias = deobfPresets.getForCls(classInfo);
if (alias != null) {
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
return;
}
if (clsMap.containsKey(classInfo)) {
return;
} else {
if (!clsMap.containsKey(classInfo)
&& shouldRename(classInfo.getShortName())) {
makeClsAlias(cls);
}
}
if (shouldRename(classInfo.getShortName())) {
makeClsAlias(cls);
for (ClassNode innerCls : cls.getInnerClasses()) {
preProcessClass(innerCls);
}
}
......@@ -361,21 +368,33 @@ public class Deobfuscator {
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.isValidIdentifier(name)
&& !NameMapper.isReserved(name)) {
// TODO: check if no class with this name exists or already renamed
cls.remove(AType.SOURCE_FILE);
return name;
if (!NameMapper.isValidIdentifier(name) || NameMapper.isReserved(name)) {
return null;
}
return null;
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
if (deobfClsInfo.getAlias().equals(name)) {
return null;
}
}
ClassNode otherCls = cls.dex().root().searchClassByName(cls.getPackage() + "." + name);
if (otherCls != null) {
return null;
}
cls.remove(AType.SOURCE_FILE);
return name;
}
@Nullable
public String getFieldAlias(FieldNode field) {
private String getFieldAlias(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = fldMap.get(fieldInfo);
if (alias != null) {
......@@ -393,7 +412,7 @@ public class Deobfuscator {
}
@Nullable
public String getMethodAlias(MethodNode mth) {
private String getMethodAlias(MethodNode mth) {
MethodInfo methodInfo = mth.getMethodInfo();
String alias = mthMap.get(methodInfo);
if (alias != null) {
......
package jadx.core.dex.info;
import java.io.File;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.File;
public final class ClassInfo {
private final ArgType type;
......@@ -17,50 +18,50 @@ public final class ClassInfo {
// class info after rename (deobfuscation)
private ClassInfo alias;
private ClassInfo(DexNode dex, ArgType type) {
this(dex, type, true);
private ClassInfo(RootNode root, ArgType type) {
this(root, type, true);
}
private ClassInfo(DexNode dex, ArgType type, boolean inner) {
private ClassInfo(RootNode root, ArgType type, boolean inner) {
if (!type.isObject() || type.isGeneric()) {
throw new JadxRuntimeException("Not class type: " + type);
}
this.type = type;
this.alias = this;
splitNames(dex, inner);
splitNames(root, inner);
}
public static ClassInfo fromType(DexNode dex, ArgType type) {
public static ClassInfo fromType(RootNode root, ArgType type) {
if (type.isArray()) {
type = ArgType.OBJECT;
}
ClassInfo cls = dex.root().getInfoStorage().getCls(type);
ClassInfo cls = root.getInfoStorage().getCls(type);
if (cls != null) {
return cls;
}
cls = new ClassInfo(dex, type);
return dex.root().getInfoStorage().putCls(cls);
cls = new ClassInfo(root, type);
return root.getInfoStorage().putCls(cls);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX) {
return null;
}
return fromType(dex, dex.getType(clsIndex));
return fromType(dex.root(), dex.getType(clsIndex));
}
public static ClassInfo fromName(DexNode dex, String clsName) {
return fromType(dex, ArgType.object(clsName));
public static ClassInfo fromName(RootNode root, String clsName) {
return fromType(root, ArgType.object(clsName));
}
public static ClassInfo extCls(DexNode dex, ArgType type) {
ClassInfo classInfo = fromName(dex, type.getObject());
public static ClassInfo extCls(RootNode root, ArgType type) {
ClassInfo classInfo = fromName(root, type.getObject());
return classInfo.alias;
}
public void rename(DexNode dex, String fullName) {
ClassInfo newAlias = new ClassInfo(dex, ArgType.object(fullName), isInner());
public void rename(RootNode root, String fullName) {
ClassInfo newAlias = new ClassInfo(root, ArgType.object(fullName), isInner());
if (!alias.getFullName().equals(newAlias.getFullName())) {
this.alias = newAlias;
}
......@@ -74,7 +75,7 @@ public final class ClassInfo {
return alias;
}
private void splitNames(DexNode dex, boolean canBeInner) {
private void splitNames(RootNode root, boolean canBeInner) {
String fullObjectName = type.getObject();
String clsName;
int dot = fullObjectName.lastIndexOf('.');
......@@ -93,7 +94,7 @@ public final class ClassInfo {
parClsName = clsName.substring(0, sep);
}
parentClass = fromName(dex, parClsName);
parentClass = fromName(root, parClsName);
clsName = clsName.substring(sep + 1);
} else {
parentClass = null;
......@@ -111,10 +112,10 @@ public final class ClassInfo {
}
public String getFullPath() {
ClassInfo alias = getAlias();
return alias.getPackage().replace('.', File.separatorChar)
ClassInfo usedAlias = getAlias();
return usedAlias.getPackage().replace('.', File.separatorChar)
+ File.separatorChar
+ alias.getNameWithoutPackage().replace('.', '_');
+ usedAlias.getNameWithoutPackage().replace('.', '_');
}
public String getFullName() {
......@@ -160,8 +161,8 @@ public final class ClassInfo {
return parentClass != null;
}
public void notInner(DexNode dex) {
splitNames(dex, false);
public void notInner(RootNode root) {
splitNames(root, false);
}
public ArgType getType() {
......
......@@ -65,7 +65,7 @@ public class DexNode implements IDexNode {
ClassNode parent = resolveClass(clsInfo.getParentClass());
if (parent == null) {
clsMap.remove(clsInfo);
clsInfo.notInner(cls.dex());
clsInfo.notInner(root);
clsMap.put(clsInfo, cls);
} else {
parent.addInnerClass(cls);
......@@ -78,16 +78,21 @@ public class DexNode implements IDexNode {
}
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
ClassNode resolveClassLocal(ClassInfo clsInfo) {
return clsMap.get(clsInfo);
}
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
return root.resolveClass(clsInfo);
}
@Nullable
public ClassNode resolveClass(@NotNull ArgType type) {
if (type.isGeneric()) {
type = ArgType.object(type.getObject());
}
return resolveClass(ClassInfo.fromType(this, type));
return resolveClass(ClassInfo.fromType(root, type));
}
@Nullable
......@@ -99,20 +104,8 @@ public class DexNode implements IDexNode {
return null;
}
/**
* Search method in class hierarchy.
*/
@Nullable
public MethodNode deepResolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls == null) {
return null;
}
return deepResolveMethod(cls, mth.makeSignature(false));
}
@Nullable
private MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) {
MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) {
for (MethodNode m : cls.getMethods()) {
if (m.getMethodInfo().getShortId().startsWith(signature)) {
return m;
......
......@@ -5,6 +5,7 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -17,6 +18,7 @@ import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.InfoStorage;
import jadx.core.dex.info.MethodInfo;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
......@@ -144,10 +146,10 @@ public class RootNode {
return classes;
}
public ClassNode searchClassByName(String fullName) {
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
for (DexNode dexNode : dexNodes) {
ClassInfo clsInfo = ClassInfo.fromName(dexNode, fullName);
ClassNode cls = dexNode.resolveClass(clsInfo);
ClassNode cls = dexNode.resolveClassLocal(clsInfo);
if (cls != null) {
return cls;
}
......@@ -155,6 +157,12 @@ public class RootNode {
return null;
}
@Nullable
public ClassNode searchClassByName(String fullName) {
ClassInfo clsInfo = ClassInfo.fromName(this, fullName);
return resolveClass(clsInfo);
}
public List<ClassNode> searchClassByShortName(String shortName) {
List<ClassNode> list = new ArrayList<>();
for (DexNode dexNode : dexNodes) {
......@@ -167,6 +175,15 @@ public class RootNode {
return list;
}
@Nullable
public MethodNode deepResolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls == null) {
return null;
}
return cls.dex().deepResolveMethod(cls, mth.makeSignature(false));
}
public List<DexNode> getDexNodes() {
return dexNodes;
}
......
......@@ -63,7 +63,7 @@ public class ClassModifier extends AbstractVisitor {
// remove fields if it is synthetic and type is a outer class
for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
ClassInfo clsInfo = ClassInfo.fromType(cls.dex(), field.getType());
ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType());
ClassNode fieldsCls = cls.dex().resolveClass(clsInfo);
ClassInfo parentClass = cls.getClassInfo().getParentClass();
if (fieldsCls != null
......
......@@ -96,7 +96,7 @@ public class DependencyCollector extends AbstractVisitor {
private static void addDep(DexNode dex, Set<ClassNode> depList, ArgType type) {
if (type != null) {
if (type.isObject()) {
addDep(dex, depList, ClassInfo.fromName(dex, type.getObject()));
addDep(dex, depList, ClassInfo.fromName(dex.root(), type.getObject()));
ArgType[] genericTypes = type.getGenericTypes();
if (type.isGeneric() && genericTypes != null) {
for (ArgType argType : genericTypes) {
......
......@@ -71,7 +71,7 @@ public class RenameVisitor extends AbstractVisitor {
if (!clsNames.add(clsFileName.toLowerCase())) {
String newShortName = deobfuscator.getClsAlias(cls);
String newFullName = classInfo.makeFullClsName(newShortName, true);
classInfo.rename(cls.dex(), newFullName);
classInfo.rename(cls.root(), newFullName);
clsNames.add(classInfo.getAlias().getFullPath().toLowerCase());
}
}
......@@ -89,12 +89,12 @@ public class RenameVisitor extends AbstractVisitor {
newShortName = "C" + clsName;
}
if (newShortName != null) {
classInfo.rename(cls.dex(), classInfo.makeFullClsName(newShortName, true));
classInfo.rename(cls.root(), classInfo.makeFullClsName(newShortName, true));
}
if (classInfo.getAlias().getPackage().isEmpty()) {
String fullName = classInfo.makeFullClsName(classInfo.getAlias().getShortName(), true);
String newFullName = Consts.DEFAULT_PACKAGE_NAME + "." + fullName;
classInfo.rename(cls.dex(), newFullName);
classInfo.rename(cls.root(), newFullName);
}
}
......@@ -103,7 +103,7 @@ public class RenameVisitor extends AbstractVisitor {
for (FieldNode field : cls.getFields()) {
FieldInfo fieldInfo = field.getFieldInfo();
if (!names.add(fieldInfo.getAlias())) {
fieldInfo.setAlias(deobfuscator.makeFieldAlias(field));
deobfuscator.renameField(field);
}
}
}
......@@ -114,22 +114,10 @@ public class RenameVisitor extends AbstractVisitor {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
MethodInfo methodInfo = mth.getMethodInfo();
String signature = makeMethodSignature(methodInfo);
String signature = mth.getMethodInfo().makeSignature(false);
if (!names.add(signature)) {
methodInfo.setAlias(deobfuscator.makeMethodAlias(mth));
deobfuscator.renameMethod(mth);
}
}
}
private static String makeMethodSignature(MethodInfo methodInfo) {
StringBuilder signature = new StringBuilder();
signature.append(methodInfo.getAlias());
signature.append('(');
for (ArgType arg : methodInfo.getArgumentsTypes()) {
signature.append(TypeGen.signature(arg));
}
signature.append(')');
return signature.toString();
}
}
......@@ -55,8 +55,7 @@ public class AndroidResourcesUtils {
if (dexNodes.isEmpty()) {
return null;
}
DexNode firstDex = dexNodes.get(0);
ClassInfo r = ClassInfo.fromName(firstDex, clsName);
return new ClassNode(firstDex, r);
ClassInfo r = ClassInfo.fromName(root, clsName);
return new ClassNode(dexNodes.get(0), r);
}
}
......@@ -8,6 +8,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
......@@ -74,6 +76,16 @@ public class FileUtils {
return temp;
}
public static File createTempDir(String suffix) {
try {
Path path = Files.createTempDirectory("jadx-tmp-" + System.nanoTime() + "-" + suffix);
path.toFile().deleteOnExit();
return path.toFile();
} catch (IOException e) {
throw new JadxRuntimeException("Failed to create temp directory with suffix: " + suffix);
}
}
public static void copyStream(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[READ_BUFFER_SIZE];
while (true) {
......
......@@ -48,7 +48,7 @@ public abstract class IntegrationTest extends TestUtils {
private static final String TEST_DIRECTORY = "src/test/java";
private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY;
private JadxArgs args;
protected JadxArgs args;
protected boolean deleteTmpFiles = true;
protected boolean withDebugInfo = true;
......@@ -113,27 +113,31 @@ public abstract class IntegrationTest extends TestUtils {
return cls;
}
private void decompile(JadxDecompiler jadx, ClassNode cls) {
protected void decompile(JadxDecompiler jadx, ClassNode cls) {
List<IDexTreeVisitor> passes = Jadx.getPassesList(jadx.getArgs());
ProcessClass.process(cls, passes, new CodeGen());
}
private void decompileWithoutUnload(JadxDecompiler d, ClassNode cls) {
protected void decompileWithoutUnload(JadxDecompiler d, ClassNode cls) {
cls.load();
List<IDexTreeVisitor> passes = Jadx.getPassesList(d.getArgs());
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
}
generateClsCode(cls);
// don't unload class
}
protected void generateClsCode(ClassNode cls) {
try {
new CodeGen().visit(cls);
} catch (CodegenException e) {
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
// don't unload class
}
private static void checkCode(ClassNode cls) {
protected static void checkCode(ClassNode cls) {
assertTrue("Inconsistent cls: " + cls,
!cls.contains(AFlag.INCONSISTENT_CODE) && !cls.contains(AType.JADX_ERROR));
for (MethodNode mthNode : cls.getMethods()) {
......@@ -370,6 +374,13 @@ public abstract class IntegrationTest extends TestUtils {
this.unloadCls = false;
}
protected void enableDeobfuscation() {
args.setDeobfuscationOn(true);
args.setDeobfuscationForceSave(true);
args.setDeobfuscationMinLength(2);
args.setDeobfuscationMaxLength(64);
}
// Use only for debug purpose
@Deprecated
protected void setOutputCFG() {
......
package jadx.tests.integration.deobf;
import org.junit.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestMthRename extends IntegrationTest {
public static class TestCls {
public static abstract class TestAbstractCls {
public abstract void a();
}
public void test(TestAbstractCls a) {
a.a();
}
}
@Test
public void test() {
noDebugInfo();
enableDeobfuscation();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsString("public abstract void m0a();"));
assertThat(code, not(containsString("public abstract void a();")));
assertThat(code, containsString(".m0a();"));
assertThat(code, not(containsString(".a();")));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册