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 {
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;
......
......@@ -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;
}
......
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<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
private final IJadxArgs args;
private final File deobfMapFile;
@NotNull
private final List<DexNode> 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<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.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 <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)) {
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;
}
if (shouldRename(classInfo.getShortName())) {
String alias = makeClsAlias(cls);
clsMap.put(classInfo, new DeobfClsInfo(cls, pkg, alias));
}
}
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);
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<String> pkgSet = new TreeSet<String>();
private final Set<String> pkgSet = new TreeSet<String>();
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<String> 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<String, String>();
......@@ -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) {
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<String> list = new ArrayList<String>();
// 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();
......
......@@ -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<String> RESERVED_NAMES = new HashSet<String>(
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();
}
}
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<ClassNode> innerClasses = 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 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<ClassNode> getInnerClasses() {
return innerClasses;
}
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty()) {
innerClasses = new ArrayList<ClassNode>();
}
innerClasses.add(cls);
}
public void addInnerPackage(PackageNode pkg) {
if (innerPackages.isEmpty()) {
innerPackages = new ArrayList<PackageNode>();
......@@ -120,7 +101,6 @@ public class PackageNode {
break;
}
}
return result;
}
......@@ -140,7 +120,6 @@ public class PackageNode {
currentP = parentP;
parentP = currentP.getParentPackage();
}
return pp;
}
}
......@@ -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() {
......
......@@ -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);
}
......
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