Commit 99d831c4 authored by Skylot's avatar Skylot

core: use source file information for deobfuscation, fix code style issues

parent a532287d
...@@ -57,7 +57,7 @@ public final class JadxCLIArgs implements IJadxArgs { ...@@ -57,7 +57,7 @@ public final class JadxCLIArgs implements IJadxArgs {
protected int deobfuscationMinLength = 2; protected int deobfuscationMinLength = 2;
@Parameter(names = {"--deobf-max"}, description = "max length of name") @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") @Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
protected boolean deobfuscationForceSave = false; protected boolean deobfuscationForceSave = false;
......
...@@ -130,6 +130,7 @@ public class ClassGen { ...@@ -130,6 +130,7 @@ public class ClassGen {
annotationGen.addForClass(clsCode); annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls); insertSourceFileInfo(clsCode, cls);
insertRenameInfo(clsCode, cls);
clsCode.startLine(af.makeString()); clsCode.startLine(af.makeString());
if (af.isInterface()) { if (af.isInterface()) {
if (af.isAnnotation()) { if (af.isAnnotation()) {
...@@ -547,6 +548,13 @@ public class ClassGen { ...@@ -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() { public ClassGen getParentGen() {
return parentGen == null ? this : parentGen; return parentGen == null ? this : parentGen;
} }
......
package jadx.core.deobf; package jadx.core.deobf;
import jadx.api.IJadxArgs; 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.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.DexNode;
...@@ -16,6 +18,7 @@ import java.util.Set; ...@@ -16,6 +18,7 @@ import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -31,17 +34,18 @@ public class Deobfuscator { ...@@ -31,17 +34,18 @@ public class Deobfuscator {
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>(); private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
private final IJadxArgs args; private final IJadxArgs args;
private final File deobfMapFile; private final File deobfMapFile;
@NotNull
private final List<DexNode> dexNodes; private final List<DexNode> dexNodes;
private int maxLength = 40; private final int maxLength;
private int minLength = 2; private final int minLength;
private int pkgIndex = 0; private int pkgIndex = 0;
private int clsIndex = 0; private int clsIndex = 0;
private PackageNode rootPackage = new PackageNode(""); private final PackageNode rootPackage = new PackageNode("");
private Map<String, String> preLoadClsMap = Collections.emptyMap(); private Map<String, String> preLoadClsMap = Collections.emptyMap();
public Deobfuscator(IJadxArgs args, List<DexNode> dexNodes, File deobfMapFile) { public Deobfuscator(IJadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
this.args = args; this.args = args;
this.dexNodes = dexNodes; this.dexNodes = dexNodes;
this.deobfMapFile = deobfMapFile; this.deobfMapFile = deobfMapFile;
...@@ -75,7 +79,7 @@ public class Deobfuscator { ...@@ -75,7 +79,7 @@ public class Deobfuscator {
} }
} }
public void process() { private void process() {
preProcess(); preProcess();
if (DEBUG) { if (DEBUG) {
dumpAlias(); dumpAlias();
...@@ -87,7 +91,9 @@ public class Deobfuscator { ...@@ -87,7 +91,9 @@ public class Deobfuscator {
for (ClassNode classNode : dexNode.getClasses()) { for (ClassNode classNode : dexNode.getClasses()) {
ClassInfo clsInfo = classNode.getClassInfo(); ClassInfo clsInfo = classNode.getClassInfo();
String fullName = getClassFullName(clsInfo); String fullName = getClassFullName(clsInfo);
clsInfo.rename(dexNode, fullName); if (!fullName.equals(clsInfo.getFullName())) {
clsInfo.rename(dexNode, fullName);
}
} }
} }
} }
...@@ -99,7 +105,7 @@ public class Deobfuscator { ...@@ -99,7 +105,7 @@ public class Deobfuscator {
* @param create if {@code true} then will create all absent objects * @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} * @return package node object or {@code null} if no package found and <b>create</b> set to {@code false}
*/ */
public PackageNode getPackageNode(String fullPkgName, boolean create) { private PackageNode getPackageNode(String fullPkgName, boolean create) {
if (fullPkgName.isEmpty() || fullPkgName.equals(classNameSeparator)) { if (fullPkgName.isEmpty() || fullPkgName.equals(classNameSeparator)) {
return rootPackage; return rootPackage;
} }
...@@ -118,23 +124,24 @@ public class Deobfuscator { ...@@ -118,23 +124,24 @@ public class Deobfuscator {
} }
parentNode = result; parentNode = result;
result = result.getInnerPackageByName(pkgName); result = result.getInnerPackageByName(pkgName);
if ((result == null) && (create)) { if (result == null && create) {
result = new PackageNode(pkgName); result = new PackageNode(pkgName);
parentNode.addInnerPackage(result); parentNode.addInnerPackage(result);
} }
} while (!fullPkgName.isEmpty() && (result != null)); } while (!fullPkgName.isEmpty() && result != null);
return result; return result;
} }
private final class DeobfClsInfo { private final class DeobfClsInfo {
public ClassNode cls; public final ClassNode cls;
public PackageNode pkg; public final PackageNode pkg;
public String alias; public final String alias;
public DeobfClsInfo(ClassNode cls, PackageNode pkg) { public DeobfClsInfo(ClassNode cls, PackageNode pkg, String alias) {
this.cls = cls; this.cls = cls;
this.pkg = pkg; this.pkg = pkg;
this.alias = alias;
} }
public String makeNameWithoutPkg() { public String makeNameWithoutPkg() {
...@@ -152,7 +159,7 @@ public class Deobfuscator { ...@@ -152,7 +159,7 @@ public class Deobfuscator {
prefix = ""; prefix = "";
} }
return prefix + ((this.alias != null) ? this.alias : this.cls.getShortName()); return prefix + (this.alias != null ? this.alias : this.cls.getShortName());
} }
public String getFullName() { public String getFullName() {
...@@ -160,7 +167,7 @@ public class Deobfuscator { ...@@ -160,7 +167,7 @@ public class Deobfuscator {
} }
} }
public String getNameWithoutPackage(ClassInfo clsInfo) { private String getNameWithoutPackage(ClassInfo clsInfo) {
String prefix; String prefix;
ClassInfo parentClsInfo = clsInfo.getParentClass(); ClassInfo parentClsInfo = clsInfo.getParentClass();
if (parentClsInfo != null) { if (parentClsInfo != null) {
...@@ -178,39 +185,52 @@ public class Deobfuscator { ...@@ -178,39 +185,52 @@ public class Deobfuscator {
} }
private void doClass(ClassNode cls) { private void doClass(ClassNode cls) {
final String pkgFullName = cls.getClassInfo().getPackage(); ClassInfo classInfo = cls.getClassInfo();
String pkgFullName = classInfo.getPackage();
PackageNode pkg = getPackageNode(pkgFullName, true); PackageNode pkg = getPackageNode(pkgFullName, true);
doPkg(pkg, pkgFullName); doPkg(pkg, pkgFullName);
if (preLoadClsMap.containsKey(cls.getClassInfo().getFullName())) { String fullName = classInfo.getFullName();
DeobfClsInfo clsInfo = new DeobfClsInfo(cls, pkg); if (preLoadClsMap.containsKey(fullName)) {
clsInfo.alias = preLoadClsMap.get(cls.getFullName()); String alias = preLoadClsMap.get(fullName);
clsMap.put(cls.getClassInfo(), clsInfo); clsMap.put(classInfo, new DeobfClsInfo(cls, pkg, alias));
return; return;
} }
if (clsMap.containsKey(classInfo)) {
if (clsMap.containsKey(cls.getClassInfo())) {
return; return;
} }
if (shouldRename(classInfo.getShortName())) {
String alias = makeClsAlias(cls);
clsMap.put(classInfo, new DeobfClsInfo(cls, pkg, alias));
}
}
final String className = cls.getClassInfo().getShortName(); private String makeClsAlias(ClassNode cls) {
if (shouldRename(className)) { SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
DeobfClsInfo clsInfo = new DeobfClsInfo(cls, pkg); if (sourceFileAttr != null) {
clsInfo.alias = String.format("C%04d%s", clsIndex++, short4LongName(className)); String name = sourceFileAttr.getFileName();
clsMap.put(cls.getClassInfo(), clsInfo); 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) { private String short4LongName(String name) {
if (name.length() > maxLength) { if (name.length() > maxLength) {
return "x" + Integer.toHexString(name.hashCode()); return "x" + Integer.toHexString(name.hashCode());
} else {
return name;
} }
return name;
} }
private Set<String> pkgSet = new TreeSet<String>(); private final Set<String> pkgSet = new TreeSet<String>();
private void doPkg(PackageNode pkg, String fullName) { private void doPkg(PackageNode pkg, String fullName) {
if (pkgSet.contains(fullName)) { if (pkgSet.contains(fullName)) {
...@@ -218,7 +238,7 @@ public class Deobfuscator { ...@@ -218,7 +238,7 @@ public class Deobfuscator {
} }
pkgSet.add(fullName); 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(); PackageNode parentPkg = pkg.getParentPackage();
while (!parentPkg.getName().isEmpty()) { while (!parentPkg.getName().isEmpty()) {
if (!parentPkg.hasAlias()) { if (!parentPkg.hasAlias()) {
...@@ -235,11 +255,9 @@ public class Deobfuscator { ...@@ -235,11 +255,9 @@ public class Deobfuscator {
} }
private void preProcess() { private void preProcess() {
if (dexNodes != null) { for (DexNode dexNode : dexNodes) {
for (DexNode dexNode : dexNodes) { for (ClassNode cls : dexNode.getClasses()) {
for (ClassNode cls : dexNode.getClasses()) { doClass(cls);
doClass(cls);
}
} }
} }
} }
...@@ -273,24 +291,21 @@ public class Deobfuscator { ...@@ -273,24 +291,21 @@ public class Deobfuscator {
* *
* @throws IOException * @throws IOException
*/ */
public void load() throws IOException { private void load() throws IOException {
if (!deobfMapFile.exists()) { if (!deobfMapFile.exists()) {
return; return;
} }
List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET); List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
for (String l : lines) { for (String l : lines) {
l = l.trim();
if (l.startsWith("p ")) { if (l.startsWith("p ")) {
final String rule = l.substring(2); String va[] = splitAndTrim(l);
final String va[] = rule.split("=");
if (va.length == 2) { if (va.length == 2) {
PackageNode pkg = getPackageNode(va[0], true); PackageNode pkg = getPackageNode(va[0], true);
pkg.setAlias(va[1]); pkg.setAlias(va[1]);
} }
} else if (l.startsWith("c ")) { } else if (l.startsWith("c ")) {
final String rule = l.substring(2); String va[] = splitAndTrim(l);
final String va[] = rule.split("=");
if (va.length == 2) { if (va.length == 2) {
if (preLoadClsMap.isEmpty()) { if (preLoadClsMap.isEmpty()) {
preLoadClsMap = new HashMap<String, String>(); preLoadClsMap = new HashMap<String, String>();
...@@ -301,6 +316,14 @@ public class Deobfuscator { ...@@ -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<String> list, String prefix, PackageNode node) { private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
for (PackageNode pp : node.getInnerPackages()) { for (PackageNode pp : node.getInnerPackages()) {
dfsPackageName(list, prefix + '.' + node.getName(), pp); dfsPackageName(list, prefix + '.' + node.getName(), pp);
...@@ -313,7 +336,7 @@ public class Deobfuscator { ...@@ -313,7 +336,7 @@ public class Deobfuscator {
/** /**
* Saves DefaultDeobfuscator presets * Saves DefaultDeobfuscator presets
*/ */
public void save() throws IOException { private void save() throws IOException {
List<String> list = new ArrayList<String>(); List<String> list = new ArrayList<String>();
// packages // packages
for (PackageNode p : rootPackage.getInnerPackages()) { for (PackageNode p : rootPackage.getInnerPackages()) {
...@@ -327,7 +350,8 @@ public class Deobfuscator { ...@@ -327,7 +350,8 @@ public class Deobfuscator {
// classes // classes
for (DeobfClsInfo deobfClsInfo : clsMap.values()) { for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
if (deobfClsInfo.alias != null) { 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); Collections.sort(list);
...@@ -335,7 +359,7 @@ public class Deobfuscator { ...@@ -335,7 +359,7 @@ public class Deobfuscator {
list.clear(); list.clear();
} }
public String getPackageName(String packageName) { private String getPackageName(String packageName) {
final PackageNode pkg = getPackageNode(packageName, false); final PackageNode pkg = getPackageNode(packageName, false);
if (pkg != null) { if (pkg != null) {
return pkg.getFullAlias(); return pkg.getFullAlias();
...@@ -343,7 +367,7 @@ public class Deobfuscator { ...@@ -343,7 +367,7 @@ public class Deobfuscator {
return packageName; return packageName;
} }
public String getClassName(ClassInfo clsInfo) { private String getClassName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo); final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) { if (deobfClsInfo != null) {
return deobfClsInfo.makeNameWithoutPkg(); return deobfClsInfo.makeNameWithoutPkg();
...@@ -351,11 +375,11 @@ public class Deobfuscator { ...@@ -351,11 +375,11 @@ public class Deobfuscator {
return getNameWithoutPackage(clsInfo); return getNameWithoutPackage(clsInfo);
} }
public String getClassFullName(ClassNode cls) { private String getClassFullName(ClassNode cls) {
return getClassFullName(cls.getClassInfo()); return getClassFullName(cls.getClassInfo());
} }
public String getClassFullName(ClassInfo clsInfo) { private String getClassFullName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo); final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) { if (deobfClsInfo != null) {
return deobfClsInfo.getFullName(); return deobfClsInfo.getFullName();
......
...@@ -3,9 +3,16 @@ package jadx.core.deobf; ...@@ -3,9 +3,16 @@ package jadx.core.deobf;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
public class NameMapper { 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<String> RESERVED_NAMES = new HashSet<String>( private static final Set<String> RESERVED_NAMES = new HashSet<String>(
Arrays.asList(new String[]{ Arrays.asList(new String[]{
"abstract", "abstract",
...@@ -68,4 +75,11 @@ public class NameMapper { ...@@ -68,4 +75,11 @@ public class NameMapper {
return RESERVED_NAMES.contains(str); 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();
}
} }
package jadx.core.deobf; package jadx.core.deobf;
import jadx.core.dex.nodes.ClassNode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -10,12 +8,11 @@ import java.util.Stack; ...@@ -10,12 +8,11 @@ import java.util.Stack;
public class PackageNode { public class PackageNode {
private PackageNode parentPackage; private PackageNode parentPackage;
private List<ClassNode> innerClasses = Collections.emptyList();
private List<PackageNode> innerPackages = Collections.emptyList(); private List<PackageNode> 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 packageAlias;
private String cachedPackageFullName; private String cachedPackageFullName;
...@@ -40,10 +37,8 @@ public class PackageNode { ...@@ -40,10 +37,8 @@ public class PackageNode {
result.append(separatorChar); result.append(separatorChar);
result.append(pp.pop().getName()); result.append(pp.pop().getName());
} }
cachedPackageFullName = result.toString(); cachedPackageFullName = result.toString();
} }
return cachedPackageFullName; return cachedPackageFullName;
} }
...@@ -51,7 +46,6 @@ public class PackageNode { ...@@ -51,7 +46,6 @@ public class PackageNode {
if (packageAlias != null) { if (packageAlias != null) {
return packageAlias; return packageAlias;
} }
return packageName; return packageName;
} }
...@@ -60,7 +54,7 @@ public class PackageNode { ...@@ -60,7 +54,7 @@ public class PackageNode {
} }
public boolean hasAlias() { public boolean hasAlias() {
return (packageAlias != null); return packageAlias != null;
} }
public String getFullAlias() { public String getFullAlias() {
...@@ -72,10 +66,8 @@ public class PackageNode { ...@@ -72,10 +66,8 @@ public class PackageNode {
result.append(separatorChar); result.append(separatorChar);
result.append(pp.pop().getAlias()); result.append(pp.pop().getAlias());
} }
cachedPackageFullAlias = result.toString(); cachedPackageFullAlias = result.toString();
} }
return cachedPackageFullAlias; return cachedPackageFullAlias;
} }
...@@ -87,17 +79,6 @@ public class PackageNode { ...@@ -87,17 +79,6 @@ public class PackageNode {
return innerPackages; return innerPackages;
} }
public List<ClassNode> getInnerClasses() {
return innerClasses;
}
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty()) {
innerClasses = new ArrayList<ClassNode>();
}
innerClasses.add(cls);
}
public void addInnerPackage(PackageNode pkg) { public void addInnerPackage(PackageNode pkg) {
if (innerPackages.isEmpty()) { if (innerPackages.isEmpty()) {
innerPackages = new ArrayList<PackageNode>(); innerPackages = new ArrayList<PackageNode>();
...@@ -120,7 +101,6 @@ public class PackageNode { ...@@ -120,7 +101,6 @@ public class PackageNode {
break; break;
} }
} }
return result; return result;
} }
...@@ -140,7 +120,6 @@ public class PackageNode { ...@@ -140,7 +120,6 @@ public class PackageNode {
currentP = parentP; currentP = parentP;
parentP = currentP.getParentPackage(); parentP = currentP.getParentPackage();
} }
return pp; return pp;
} }
} }
...@@ -62,7 +62,14 @@ public final class ClassInfo { ...@@ -62,7 +62,14 @@ public final class ClassInfo {
} }
public void rename(DexNode dex, String fullName) { 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() { public ClassInfo getAlias() {
......
...@@ -112,8 +112,10 @@ public class ClassNode extends LineAttrNode implements ILoadable { ...@@ -112,8 +112,10 @@ public class ClassNode extends LineAttrNode implements ILoadable {
int sfIdx = cls.getSourceFileIndex(); int sfIdx = cls.getSourceFileIndex();
if (sfIdx != DexNode.NO_INDEX) { if (sfIdx != DexNode.NO_INDEX) {
String fileName = dex.getString(sfIdx); String fileName = dex.getString(sfIdx);
if (!clsInfo.getFullName().contains(fileName.replace(".java", "")) if (clsInfo != null
&& !fileName.equals("SourceFile")) { && !clsInfo.getFullName().contains(fileName.replace(".java", ""))
&& !fileName.equals("SourceFile")
&& !fileName.equals("\"")) {
this.addAttr(new SourceFileAttr(fileName)); this.addAttr(new SourceFileAttr(fileName));
LOG.debug("Class '{}' compiled from '{}'", this, fileName); LOG.debug("Class '{}' compiled from '{}'", this, fileName);
} }
......
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',
]
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment