From 99d831c498d02f66ef0626a986b90d56fbe22fbc Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 8 Mar 2015 17:37:24 +0300 Subject: [PATCH] core: use source file information for deobfuscation, fix code style issues --- .../src/main/java/jadx/cli/JadxCLIArgs.java | 2 +- .../main/java/jadx/core/codegen/ClassGen.java | 8 ++ .../java/jadx/core/deobf/Deobfuscator.java | 126 +++++++++++------- .../main/java/jadx/core/deobf/NameMapper.java | 14 ++ .../java/jadx/core/deobf/PackageNode.java | 27 +--- .../java/jadx/core/dex/info/ClassInfo.java | 9 +- .../java/jadx/core/dex/nodes/ClassNode.java | 6 +- .../groovy/jadx/tests/TestNameMapper.groovy | 39 ++++++ 8 files changed, 152 insertions(+), 79 deletions(-) create mode 100644 jadx-core/src/test/groovy/jadx/tests/TestNameMapper.groovy diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 9d7ea6e5d..54fa704a0 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -57,7 +57,7 @@ public final class JadxCLIArgs implements IJadxArgs { protected int deobfuscationMinLength = 2; @Parameter(names = {"--deobf-max"}, description = "max length of name") - protected int deobfuscationMaxLength = 40; + protected int deobfuscationMaxLength = 64; @Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map") protected boolean deobfuscationForceSave = false; diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index bd9161f10..e10974220 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -130,6 +130,7 @@ public class ClassGen { annotationGen.addForClass(clsCode); insertSourceFileInfo(clsCode, cls); + insertRenameInfo(clsCode, cls); clsCode.startLine(af.makeString()); if (af.isInterface()) { if (af.isAnnotation()) { @@ -547,6 +548,13 @@ public class ClassGen { } } + private void insertRenameInfo(CodeWriter code, ClassNode cls) { + ClassInfo classInfo = cls.getClassInfo(); + if (classInfo.isRenamed()) { + code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */"); + } + } + public ClassGen getParentGen() { return parentGen == null ? this : parentGen; } 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 93e2f107b..6c62c7dcb 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -1,6 +1,8 @@ package jadx.core.deobf; import jadx.api.IJadxArgs; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.DexNode; @@ -16,6 +18,7 @@ import java.util.Set; import java.util.TreeSet; import org.apache.commons.io.FileUtils; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,17 +34,18 @@ public class Deobfuscator { private final Map clsMap = new HashMap(); private final IJadxArgs args; private final File deobfMapFile; + @NotNull private final List dexNodes; - private int maxLength = 40; - private int minLength = 2; + private final int maxLength; + private final int minLength; private int pkgIndex = 0; private int clsIndex = 0; - private PackageNode rootPackage = new PackageNode(""); + private final PackageNode rootPackage = new PackageNode(""); private Map preLoadClsMap = Collections.emptyMap(); - public Deobfuscator(IJadxArgs args, List dexNodes, File deobfMapFile) { + public Deobfuscator(IJadxArgs args, @NotNull List dexNodes, File deobfMapFile) { this.args = args; this.dexNodes = dexNodes; this.deobfMapFile = deobfMapFile; @@ -75,7 +79,7 @@ public class Deobfuscator { } } - public void process() { + private void process() { preProcess(); if (DEBUG) { dumpAlias(); @@ -87,7 +91,9 @@ public class Deobfuscator { for (ClassNode classNode : dexNode.getClasses()) { ClassInfo clsInfo = classNode.getClassInfo(); String fullName = getClassFullName(clsInfo); - clsInfo.rename(dexNode, fullName); + if (!fullName.equals(clsInfo.getFullName())) { + clsInfo.rename(dexNode, fullName); + } } } } @@ -99,7 +105,7 @@ public class Deobfuscator { * @param create if {@code true} then will create all absent objects * @return package node object or {@code null} if no package found and create set to {@code false} */ - public PackageNode getPackageNode(String fullPkgName, boolean create) { + private PackageNode getPackageNode(String fullPkgName, boolean create) { if (fullPkgName.isEmpty() || fullPkgName.equals(classNameSeparator)) { return rootPackage; } @@ -118,23 +124,24 @@ public class Deobfuscator { } parentNode = result; result = result.getInnerPackageByName(pkgName); - if ((result == null) && (create)) { + if (result == null && create) { result = new PackageNode(pkgName); parentNode.addInnerPackage(result); } - } while (!fullPkgName.isEmpty() && (result != null)); + } while (!fullPkgName.isEmpty() && result != null); return result; } private final class DeobfClsInfo { - public ClassNode cls; - public PackageNode pkg; - public String alias; + public final ClassNode cls; + public final PackageNode pkg; + public final String alias; - public DeobfClsInfo(ClassNode cls, PackageNode pkg) { + public DeobfClsInfo(ClassNode cls, PackageNode pkg, String alias) { this.cls = cls; this.pkg = pkg; + this.alias = alias; } public String makeNameWithoutPkg() { @@ -152,7 +159,7 @@ public class Deobfuscator { prefix = ""; } - return prefix + ((this.alias != null) ? this.alias : this.cls.getShortName()); + return prefix + (this.alias != null ? this.alias : this.cls.getShortName()); } public String getFullName() { @@ -160,7 +167,7 @@ public class Deobfuscator { } } - public String getNameWithoutPackage(ClassInfo clsInfo) { + private String getNameWithoutPackage(ClassInfo clsInfo) { String prefix; ClassInfo parentClsInfo = clsInfo.getParentClass(); if (parentClsInfo != null) { @@ -178,39 +185,52 @@ public class Deobfuscator { } private void doClass(ClassNode cls) { - final String pkgFullName = cls.getClassInfo().getPackage(); - + ClassInfo classInfo = cls.getClassInfo(); + String pkgFullName = classInfo.getPackage(); PackageNode pkg = getPackageNode(pkgFullName, true); doPkg(pkg, pkgFullName); - if (preLoadClsMap.containsKey(cls.getClassInfo().getFullName())) { - DeobfClsInfo clsInfo = new DeobfClsInfo(cls, pkg); - clsInfo.alias = preLoadClsMap.get(cls.getFullName()); - clsMap.put(cls.getClassInfo(), clsInfo); + String fullName = classInfo.getFullName(); + if (preLoadClsMap.containsKey(fullName)) { + String alias = preLoadClsMap.get(fullName); + clsMap.put(classInfo, new DeobfClsInfo(cls, pkg, alias)); return; } - - if (clsMap.containsKey(cls.getClassInfo())) { + if (clsMap.containsKey(classInfo)) { return; } - - final String className = cls.getClassInfo().getShortName(); - if (shouldRename(className)) { - DeobfClsInfo clsInfo = new DeobfClsInfo(cls, pkg); - clsInfo.alias = String.format("C%04d%s", clsIndex++, short4LongName(className)); - clsMap.put(cls.getClassInfo(), clsInfo); + if (shouldRename(classInfo.getShortName())) { + String alias = makeClsAlias(cls); + clsMap.put(classInfo, new DeobfClsInfo(cls, pkg, alias)); } } + private String makeClsAlias(ClassNode cls) { + SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE); + if (sourceFileAttr != null) { + String name = sourceFileAttr.getFileName(); + if (name.endsWith(".java")) { + name = name.substring(0, name.length() - ".java".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; + } + } + String clsName = cls.getClassInfo().getShortName(); + return String.format("C%04d%s", clsIndex++, short4LongName(clsName)); + } + private String short4LongName(String name) { if (name.length() > maxLength) { return "x" + Integer.toHexString(name.hashCode()); - } else { - return name; } + return name; } - private Set pkgSet = new TreeSet(); + private final Set pkgSet = new TreeSet(); private void doPkg(PackageNode pkg, String fullName) { if (pkgSet.contains(fullName)) { @@ -218,7 +238,7 @@ public class Deobfuscator { } pkgSet.add(fullName); - // doPkg for all parent packages except root that not hasAlisas + // doPkg for all parent packages except root that not hasAliases PackageNode parentPkg = pkg.getParentPackage(); while (!parentPkg.getName().isEmpty()) { if (!parentPkg.hasAlias()) { @@ -235,11 +255,9 @@ public class Deobfuscator { } private void preProcess() { - if (dexNodes != null) { - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - doClass(cls); - } + for (DexNode dexNode : dexNodes) { + for (ClassNode cls : dexNode.getClasses()) { + doClass(cls); } } } @@ -273,24 +291,21 @@ public class Deobfuscator { * * @throws IOException */ - public void load() throws IOException { + private void load() throws IOException { if (!deobfMapFile.exists()) { return; } List lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET); for (String l : lines) { + l = l.trim(); if (l.startsWith("p ")) { - final String rule = l.substring(2); - final String va[] = rule.split("="); - + String va[] = splitAndTrim(l); if (va.length == 2) { PackageNode pkg = getPackageNode(va[0], true); pkg.setAlias(va[1]); } } else if (l.startsWith("c ")) { - final String rule = l.substring(2); - final String va[] = rule.split("="); - + String va[] = splitAndTrim(l); if (va.length == 2) { if (preLoadClsMap.isEmpty()) { preLoadClsMap = new HashMap(); @@ -301,6 +316,14 @@ public class Deobfuscator { } } + private static String[] splitAndTrim(String str) { + String[] v = str.substring(2).split("="); + for (int i = 0; i < v.length; i++) { + v[i] = v[i].trim(); + } + return v; + } + private static void dfsPackageName(List list, String prefix, PackageNode node) { for (PackageNode pp : node.getInnerPackages()) { dfsPackageName(list, prefix + '.' + node.getName(), pp); @@ -313,7 +336,7 @@ public class Deobfuscator { /** * Saves DefaultDeobfuscator presets */ - public void save() throws IOException { + private void save() throws IOException { List list = new ArrayList(); // packages for (PackageNode p : rootPackage.getInnerPackages()) { @@ -327,7 +350,8 @@ public class Deobfuscator { // classes for (DeobfClsInfo deobfClsInfo : clsMap.values()) { if (deobfClsInfo.alias != null) { - list.add(String.format("c %s=%s", deobfClsInfo.cls.getFullName(), deobfClsInfo.alias)); + list.add(String.format("c %s=%s", + deobfClsInfo.cls.getClassInfo().getFullName(), deobfClsInfo.alias)); } } Collections.sort(list); @@ -335,7 +359,7 @@ public class Deobfuscator { list.clear(); } - public String getPackageName(String packageName) { + private String getPackageName(String packageName) { final PackageNode pkg = getPackageNode(packageName, false); if (pkg != null) { return pkg.getFullAlias(); @@ -343,7 +367,7 @@ public class Deobfuscator { return packageName; } - public String getClassName(ClassInfo clsInfo) { + private String getClassName(ClassInfo clsInfo) { final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo); if (deobfClsInfo != null) { return deobfClsInfo.makeNameWithoutPkg(); @@ -351,11 +375,11 @@ public class Deobfuscator { return getNameWithoutPackage(clsInfo); } - public String getClassFullName(ClassNode cls) { + private String getClassFullName(ClassNode cls) { return getClassFullName(cls.getClassInfo()); } - public String getClassFullName(ClassInfo clsInfo) { + private String getClassFullName(ClassInfo clsInfo) { final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo); if (deobfClsInfo != null) { return deobfClsInfo.getFullName(); diff --git a/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java b/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java index f2c1a3026..5c1a224b8 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java +++ b/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java @@ -3,9 +3,16 @@ package jadx.core.deobf; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.regex.Pattern; public class NameMapper { + private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile( + "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); + + private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile( + "(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER); + private static final Set RESERVED_NAMES = new HashSet( Arrays.asList(new String[]{ "abstract", @@ -68,4 +75,11 @@ public class NameMapper { return RESERVED_NAMES.contains(str); } + public static boolean isValidIdentifier(String str) { + return VALID_JAVA_IDENTIFIER.matcher(str).matches(); + } + + public static boolean isValidFullIdentifier(String str) { + return VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches(); + } } diff --git a/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java b/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java index 1d9b79df4..8028bbcf9 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java +++ b/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java @@ -1,7 +1,5 @@ package jadx.core.deobf; -import jadx.core.dex.nodes.ClassNode; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -10,12 +8,11 @@ import java.util.Stack; public class PackageNode { private PackageNode parentPackage; - private List innerClasses = Collections.emptyList(); private List innerPackages = Collections.emptyList(); - public static final char separatorChar = '.'; + private static final char separatorChar = '.'; - private String packageName; + private final String packageName; private String packageAlias; private String cachedPackageFullName; @@ -40,10 +37,8 @@ public class PackageNode { result.append(separatorChar); result.append(pp.pop().getName()); } - cachedPackageFullName = result.toString(); } - return cachedPackageFullName; } @@ -51,7 +46,6 @@ public class PackageNode { if (packageAlias != null) { return packageAlias; } - return packageName; } @@ -60,7 +54,7 @@ public class PackageNode { } public boolean hasAlias() { - return (packageAlias != null); + return packageAlias != null; } public String getFullAlias() { @@ -72,10 +66,8 @@ public class PackageNode { result.append(separatorChar); result.append(pp.pop().getAlias()); } - cachedPackageFullAlias = result.toString(); } - return cachedPackageFullAlias; } @@ -87,17 +79,6 @@ public class PackageNode { return innerPackages; } - public List getInnerClasses() { - return innerClasses; - } - - public void addInnerClass(ClassNode cls) { - if (innerClasses.isEmpty()) { - innerClasses = new ArrayList(); - } - innerClasses.add(cls); - } - public void addInnerPackage(PackageNode pkg) { if (innerPackages.isEmpty()) { innerPackages = new ArrayList(); @@ -120,7 +101,6 @@ public class PackageNode { break; } } - return result; } @@ -140,7 +120,6 @@ public class PackageNode { currentP = parentP; parentP = currentP.getParentPackage(); } - return pp; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index a78749620..78e368de3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -62,7 +62,14 @@ public final class ClassInfo { } public void rename(DexNode dex, String fullName) { - this.alias = new ClassInfo(dex, ArgType.object(fullName), isInner()); + ClassInfo newAlias = new ClassInfo(dex, ArgType.object(fullName), isInner()); + if (!alias.getFullName().equals(newAlias.getFullName())) { + this.alias = newAlias; + } + } + + public boolean isRenamed() { + return alias != this; } public ClassInfo getAlias() { 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 c73087162..08d8a0ae4 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 @@ -112,8 +112,10 @@ public class ClassNode extends LineAttrNode implements ILoadable { int sfIdx = cls.getSourceFileIndex(); if (sfIdx != DexNode.NO_INDEX) { String fileName = dex.getString(sfIdx); - if (!clsInfo.getFullName().contains(fileName.replace(".java", "")) - && !fileName.equals("SourceFile")) { + if (clsInfo != null + && !clsInfo.getFullName().contains(fileName.replace(".java", "")) + && !fileName.equals("SourceFile") + && !fileName.equals("\"")) { this.addAttr(new SourceFileAttr(fileName)); LOG.debug("Class '{}' compiled from '{}'", this, fileName); } diff --git a/jadx-core/src/test/groovy/jadx/tests/TestNameMapper.groovy b/jadx-core/src/test/groovy/jadx/tests/TestNameMapper.groovy new file mode 100644 index 000000000..ecd9b429c --- /dev/null +++ b/jadx-core/src/test/groovy/jadx/tests/TestNameMapper.groovy @@ -0,0 +1,39 @@ +package jadx.tests + +import spock.lang.Specification + +import static jadx.core.deobf.NameMapper.isValidFullIdentifier + +class TestNameMapper extends Specification { + + def "test is Valid Full Identifier"() { + expect: + isValidFullIdentifier(valid) + where: + valid << [ + 'C', + 'Cc', + 'b.C', + 'b.Cc', + 'aAa.b.Cc', + 'a.b.Cc', + 'a.b.C_c', + 'a.b.C$c', + 'a.b.C9' + ] + } + + def "test is not Valid Full Identifier"() { + expect: + !isValidFullIdentifier(invalid) + where: + invalid << [ + '', + '5', + '7A', + '.C', + 'b.9C', + 'b..C', + ] + } +}