core: use source file information for deobfuscation, fix code style issues
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<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;
|
||||
}
|
||||
|
||||
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<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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user