mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 12:50:02 +00:00
core: added deobfuscation feature (basic functionality)
This commit is contained in:
parent
883429fa47
commit
30138f7a38
@ -50,6 +50,18 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
|
||||
protected boolean verbose = false;
|
||||
|
||||
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
|
||||
protected boolean deobfuscationOn = false;
|
||||
|
||||
@Parameter(names = {"--deobf-min"}, description = "min length of name")
|
||||
protected int deobfuscationMinLength = 2;
|
||||
|
||||
@Parameter(names = {"--deobf-max"}, description = "max length of name")
|
||||
protected int deobfuscationMaxLength = 40;
|
||||
|
||||
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
|
||||
protected boolean deobfuscationForceSave = false;
|
||||
|
||||
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
@ -209,4 +221,24 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ dependencies {
|
||||
runtime files(jadxClasspath)
|
||||
|
||||
compile files('lib/dx-1.10.jar')
|
||||
compile 'commons-io:commons-io:2.4'
|
||||
compile 'org.ow2.asm:asm:5.0.3'
|
||||
compile 'com.intellij:annotations:12.0'
|
||||
|
||||
|
@ -48,4 +48,24 @@ public class DefaultJadxArgs implements IJadxArgs {
|
||||
public boolean isSkipSources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return Integer.MIN_VALUE+1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return Integer.MAX_VALUE-1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -20,4 +20,12 @@ public interface IJadxArgs {
|
||||
boolean isSkipResources();
|
||||
|
||||
boolean isSkipSources();
|
||||
|
||||
boolean isDeobfuscationOn();
|
||||
|
||||
int getDeobfuscationMinLength();
|
||||
|
||||
int getDeobfuscationMaxLength();
|
||||
|
||||
boolean isDeobfuscationForceSave();
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package jadx.api;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.deobf.DefaultDeobfuscator;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
@ -253,6 +255,51 @@ public final class JadxDecompiler {
|
||||
root = new RootNode();
|
||||
LOG.info("loading ...");
|
||||
root.load(inputFiles);
|
||||
|
||||
if (args.isDeobfuscationOn()) {
|
||||
final String firstInputFileName = inputFiles.get(0).getFile().getAbsolutePath();
|
||||
final String inputPath = org.apache.commons.io.FilenameUtils.getFullPathNoEndSeparator(
|
||||
firstInputFileName);
|
||||
final String inputName = org.apache.commons.io.FilenameUtils.getBaseName(firstInputFileName);
|
||||
|
||||
final File deobfuscationMapFile = new File(inputPath, inputName + ".jobf");
|
||||
|
||||
DefaultDeobfuscator deobfuscator = new DefaultDeobfuscator();
|
||||
|
||||
if (deobfuscationMapFile.exists()) {
|
||||
try {
|
||||
deobfuscator.load(deobfuscationMapFile);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'",
|
||||
deobfuscationMapFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
deobfuscator.setInputData(root.getDexNodes());
|
||||
deobfuscator.setMinNameLength(args.getDeobfuscationMinLength());
|
||||
deobfuscator.setMaxNameLength(args.getDeobfuscationMaxLength());
|
||||
|
||||
deobfuscator.process();
|
||||
|
||||
try {
|
||||
if (deobfuscationMapFile.exists()) {
|
||||
if (args.isDeobfuscationForceSave()) {
|
||||
deobfuscator.save(deobfuscationMapFile);
|
||||
} else {
|
||||
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf=rewrite-cfg'" +
|
||||
" to rewrite it", deobfuscationMapFile.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
deobfuscator.save(deobfuscationMapFile);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'",
|
||||
deobfuscationMapFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
Deobfuscator.setDeobfuscator(deobfuscator);
|
||||
}
|
||||
|
||||
root.loadResources(getResources());
|
||||
root.initAppResClass();
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@ -150,16 +151,16 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return cls.getShortName();
|
||||
return Deobfuscator.instance().getClassShortName(cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return cls.getFullName();
|
||||
return Deobfuscator.instance().getClassFullName(cls);
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return cls.getPackage();
|
||||
return Deobfuscator.instance().getPackageName(cls.getPackage());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -202,6 +203,6 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFullName();
|
||||
return cls.getFullName() + "[ " + getFullName() + " ]";
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -9,7 +11,7 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
private final List<JavaClass> classes;
|
||||
|
||||
JavaPackage(String name, List<JavaClass> classes) {
|
||||
this.name = name;
|
||||
this.name = Deobfuscator.instance().getPackageName(name);
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package jadx.core.codegen;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
@ -80,14 +81,14 @@ public class ClassGen {
|
||||
|
||||
CodeWriter clsCode = new CodeWriter();
|
||||
if (!"".equals(cls.getPackage())) {
|
||||
clsCode.add("package ").add(cls.getPackage()).add(';');
|
||||
clsCode.add("package ").add(Deobfuscator.instance().getPackageName(cls.getPackage())).add(';');
|
||||
clsCode.newLine();
|
||||
}
|
||||
int importsCount = imports.size();
|
||||
if (importsCount != 0) {
|
||||
List<String> sortImports = new ArrayList<String>(importsCount);
|
||||
for (ClassInfo ic : imports) {
|
||||
sortImports.add(ic.getFullName());
|
||||
sortImports.add(Deobfuscator.instance().getClassFullName(ic));
|
||||
}
|
||||
Collections.sort(sortImports);
|
||||
|
||||
@ -142,7 +143,7 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("class ");
|
||||
}
|
||||
clsCode.add(cls.getShortName());
|
||||
clsCode.add(Deobfuscator.instance().getClassShortName(cls));
|
||||
|
||||
addGenericMap(clsCode, cls.getGenericMap());
|
||||
clsCode.add(' ');
|
||||
@ -453,7 +454,8 @@ public class ClassGen {
|
||||
if (fallback) {
|
||||
return fullName;
|
||||
}
|
||||
String shortName = classInfo.getShortName();
|
||||
fullName = Deobfuscator.instance().getClassFullName(classInfo);
|
||||
String shortName = Deobfuscator.instance().getClassShortName(classInfo);
|
||||
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
|
||||
return shortName;
|
||||
} else {
|
||||
@ -474,7 +476,7 @@ public class ClassGen {
|
||||
return fullName;
|
||||
}
|
||||
if (classInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = classInfo.getNameWithoutPackage();
|
||||
fullName = Deobfuscator.instance().getClassName(classInfo);
|
||||
}
|
||||
for (ClassInfo importCls : getImports()) {
|
||||
if (!importCls.equals(classInfo)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
@ -85,7 +86,7 @@ public class MethodGen {
|
||||
code.add(' ');
|
||||
}
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
code.add(Deobfuscator.instance().getClassShortName(classGen.getClassNode())); // constructor
|
||||
} else {
|
||||
classGen.useType(code, mth.getReturnType());
|
||||
code.add(' ');
|
||||
|
415
jadx-core/src/main/java/jadx/core/deobf/DefaultDeobfuscator.java
Normal file
415
jadx-core/src/main/java/jadx/core/deobf/DefaultDeobfuscator.java
Normal file
@ -0,0 +1,415 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DefaultDeobfuscator implements IDeobfuscator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultDeobfuscator.class);
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static final char classNameSepearator = '.';
|
||||
|
||||
private int maxLength = 40;
|
||||
private int minLength = 2;
|
||||
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
|
||||
private List<DexNode> dexNodes;
|
||||
private static PackageNode rootPackage = new PackageNode("");
|
||||
|
||||
private static final String MAP_FILE_CHARSET = "UTF-8";
|
||||
|
||||
/**
|
||||
* Gets package node for full package name
|
||||
*
|
||||
* @param fullPkgName full package name
|
||||
* @param _creat if {@code true} then will create all absent objects
|
||||
*
|
||||
* @return package node object or {@code null} if no package found and <b>_creat</b> set to {@code false}
|
||||
*/
|
||||
public static PackageNode getPackageNode(String fullPkgName, boolean _creat) {
|
||||
if (fullPkgName.isEmpty() || fullPkgName.equals(classNameSepearator)) {
|
||||
return rootPackage;
|
||||
}
|
||||
|
||||
PackageNode result = rootPackage;
|
||||
PackageNode parentNode;
|
||||
do {
|
||||
String pkgName;
|
||||
int idx = fullPkgName.indexOf(classNameSepearator);
|
||||
|
||||
if (idx > -1) {
|
||||
pkgName = fullPkgName.substring(0, idx);
|
||||
fullPkgName = fullPkgName.substring(idx+1);
|
||||
} else {
|
||||
pkgName = fullPkgName;
|
||||
fullPkgName = "";
|
||||
}
|
||||
|
||||
parentNode = result;
|
||||
result = result.getInnerPackageByName(pkgName);
|
||||
if ((result == null) && (_creat)) {
|
||||
result = new PackageNode(pkgName);
|
||||
parentNode.addInnerPackage(result);
|
||||
}
|
||||
} while (!fullPkgName.isEmpty() && (result != null));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class DefaultDeobfuscatorClassInfo {
|
||||
public ClassNode cls;
|
||||
public PackageNode pkg;
|
||||
public String alias;
|
||||
|
||||
public DefaultDeobfuscatorClassInfo(ClassNode cls, PackageNode pkg) {
|
||||
this.cls = cls;
|
||||
this.pkg = pkg;
|
||||
}
|
||||
|
||||
public String getNameWithoutPackage(DefaultDeobfuscatorClassInfo deobfClsInfo) {
|
||||
final ClassNode clsNode = deobfClsInfo.cls;
|
||||
String prefix;
|
||||
ClassNode parentClass = clsNode.getParentClass();
|
||||
if (parentClass != clsNode) {
|
||||
DefaultDeobfuscatorClassInfo parentDeobfClassInfo = DefaultDeobfuscator.clsMap.get(parentClass.getClassInfo());
|
||||
|
||||
if (parentDeobfClassInfo != null) {
|
||||
prefix = getNameWithoutPackage(parentDeobfClassInfo) + DefaultDeobfuscator.classNameSepearator;
|
||||
} else {
|
||||
prefix = DefaultDeobfuscator.getNameWithoutPackage(parentClass.getClassInfo()) + DefaultDeobfuscator.classNameSepearator;
|
||||
}
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
return prefix + ((deobfClsInfo.alias != null) ? deobfClsInfo.alias : deobfClsInfo.cls.getShortName());
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return pkg.getFullAlias() + DefaultDeobfuscator.classNameSepearator + getNameWithoutPackage(this);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> preloadClsMap = Collections.emptyMap();
|
||||
private static Map<ClassInfo, DefaultDeobfuscatorClassInfo> clsMap = new HashMap<ClassInfo, DefaultDeobfuscatorClassInfo>();
|
||||
|
||||
public static String getNameWithoutPackage(ClassInfo clsInfo) {
|
||||
String prefix;
|
||||
ClassInfo parentClsInfo = clsInfo.getParentClass();
|
||||
if (parentClsInfo != null) {
|
||||
DefaultDeobfuscatorClassInfo parentDeobfClsInfo = DefaultDeobfuscator.clsMap.get(parentClsInfo);
|
||||
|
||||
if (parentDeobfClsInfo != null) {
|
||||
prefix = parentDeobfClsInfo.getNameWithoutPackage(parentDeobfClsInfo) + DefaultDeobfuscator.classNameSepearator;
|
||||
} else {
|
||||
prefix = getNameWithoutPackage(parentClsInfo) + DefaultDeobfuscator.classNameSepearator;
|
||||
}
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
return prefix + clsInfo.getShortName();
|
||||
}
|
||||
|
||||
private void doClass(ClassNode cls) {
|
||||
final String pkgFullName = cls.getPackage();
|
||||
|
||||
PackageNode pkg = getPackageNode(pkgFullName, true);
|
||||
doPkg(pkg, pkgFullName);
|
||||
|
||||
if (preloadClsMap.containsKey(cls.getFullName())) {
|
||||
DefaultDeobfuscatorClassInfo clsInfo = new DefaultDeobfuscatorClassInfo(cls, pkg);
|
||||
clsInfo.alias = preloadClsMap.get(cls.getFullName());
|
||||
clsMap.put(cls.getClassInfo(), clsInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clsMap.containsKey(cls)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String className = cls.getShortName();
|
||||
if (shouldRename(className)) {
|
||||
DefaultDeobfuscatorClassInfo clsInfo = new DefaultDeobfuscatorClassInfo(cls, pkg);
|
||||
|
||||
clsInfo.alias = String.format("C%04d%s", clsIndex++, short4LongName(className));
|
||||
clsMap.put(cls.getClassInfo(), clsInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private String short4LongName(String name) {
|
||||
if (name.length() > maxLength) {
|
||||
return "x" + Integer.toHexString(name.hashCode());
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> pkgSet = new TreeSet<String>();
|
||||
|
||||
private void doPkg(PackageNode pkg, String fullName) {
|
||||
if (pkgSet.contains(fullName)) {
|
||||
return;
|
||||
}
|
||||
pkgSet.add(fullName);
|
||||
|
||||
// doPkg for all parent packages except root that not hasAlisas
|
||||
PackageNode parentPkg = pkg.getParentPackage();
|
||||
while (!parentPkg.getName().isEmpty()) {
|
||||
if (!parentPkg.hasAlias()) {
|
||||
doPkg(parentPkg, parentPkg.getFullName());
|
||||
}
|
||||
parentPkg = parentPkg.getParentPackage();
|
||||
}
|
||||
|
||||
final String pkgName = pkg.getName();
|
||||
if (shouldRename(pkgName) && !pkg.hasAlias()) {
|
||||
final String pkgAlias = String.format("p%03d%s", pkgIndex++, short4LongName(pkgName));
|
||||
pkg.setAlias(pkgAlias);
|
||||
}
|
||||
}
|
||||
|
||||
private void preprocess() {
|
||||
if (dexNodes != null) {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
doClass(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRename(String s) {
|
||||
return s.length() > maxLength || s.length() < minLength || NameMapper.isReserved(s);
|
||||
}
|
||||
|
||||
private void dumpClassAlias(ClassNode cls) {
|
||||
PackageNode pkg = getPackageNode(cls.getPackage(), false);
|
||||
|
||||
if (pkg != null) {
|
||||
if (!cls.getFullName().equals(getClassFullName(cls))) {
|
||||
LOG.info("Alias name for class '{}' is '{}'", cls.getFullName(), getClassFullName(cls));
|
||||
}
|
||||
} else {
|
||||
LOG.error("Can't find package node for '{}'", cls.getPackage());
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpAlias() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
dumpClassAlias(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets input data for processing
|
||||
*
|
||||
* @param nodes
|
||||
*
|
||||
* @return @{code this}
|
||||
*/
|
||||
public DefaultDeobfuscator setInputData(List<DexNode> nodes) {
|
||||
this.dexNodes = nodes;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets minimum name length, if name length lesser than value,
|
||||
* DefaultDeobfuscator will work
|
||||
*
|
||||
* @param value
|
||||
*
|
||||
* @return @{code this}
|
||||
*/
|
||||
public DefaultDeobfuscator setMinNameLength(int value) {
|
||||
this.minLength = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets maximum name length, if name length greater than value,
|
||||
* DefaultDeobfuscator will work
|
||||
*
|
||||
* @param value
|
||||
*
|
||||
* @return @{code this}
|
||||
*/
|
||||
public DefaultDeobfuscator setMaxNameLength(int value) {
|
||||
this.maxLength = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads DefaultDeobfuscator presets
|
||||
*
|
||||
* @param config
|
||||
* @throws IOException
|
||||
*/
|
||||
public void load(File mapFile) throws IOException {
|
||||
if (mapFile.exists()) {
|
||||
List<String> lines = FileUtils.readLines(mapFile, MAP_FILE_CHARSET);
|
||||
|
||||
for (String l : lines) {
|
||||
if (l.startsWith("p ")) {
|
||||
final String rule = l.substring(2);
|
||||
final String va[] = rule.split("=");
|
||||
|
||||
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("=");
|
||||
|
||||
if (va.length == 2) {
|
||||
if (preloadClsMap.isEmpty()) {
|
||||
preloadClsMap = new HashMap<String, String>();
|
||||
}
|
||||
preloadClsMap.put(va[0], va[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void process() {
|
||||
preprocess();
|
||||
if (DEBUG) {
|
||||
dumpAlias();
|
||||
}
|
||||
|
||||
preloadClsMap.clear();
|
||||
preloadClsMap = Collections.emptyMap();
|
||||
}
|
||||
|
||||
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
|
||||
for (PackageNode pp : node.getInnerPackages()) {
|
||||
dfsPackageName(list, prefix + '.' + node.getName(), pp);
|
||||
}
|
||||
|
||||
if (node.hasAlias()) {
|
||||
list.add(String.format("p %s.%s=%s", prefix, node.getName(), node.getAlias()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves DefaultDeobfuscator presets
|
||||
*
|
||||
* @param mapFile
|
||||
* @throws IOException
|
||||
*/
|
||||
public void save(File mapFile) throws IOException {
|
||||
List<String> list = new ArrayList<String>();
|
||||
|
||||
// packages
|
||||
for (PackageNode p : rootPackage.getInnerPackages()) {
|
||||
for (PackageNode pp : p.getInnerPackages()) {
|
||||
dfsPackageName(list, p.getName(), pp);
|
||||
}
|
||||
|
||||
if (p.hasAlias()) {
|
||||
list.add(String.format("p %s=%s", p.getName(), p.getAlias()));
|
||||
}
|
||||
}
|
||||
|
||||
// classes
|
||||
for (DefaultDeobfuscatorClassInfo deobfClsInfo : clsMap.values()) {
|
||||
if (deobfClsInfo.alias != null) {
|
||||
list.add(String.format("c %s=%s", deobfClsInfo.cls.getFullName(), deobfClsInfo.alias));
|
||||
}
|
||||
}
|
||||
|
||||
FileUtils.writeLines(mapFile, MAP_FILE_CHARSET, list);
|
||||
list.clear();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getPackageName(String packageName) {
|
||||
final PackageNode pkg = getPackageNode(packageName, false);
|
||||
if (pkg != null) {
|
||||
return pkg.getFullAlias();
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassShortName(ClassNode cls) {
|
||||
return getClassShortName(cls.getClassInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassShortName(ClassInfo clsInfo) {
|
||||
final DefaultDeobfuscatorClassInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return (deobfClsInfo.alias != null) ? deobfClsInfo.alias : clsInfo.getShortName();
|
||||
}
|
||||
|
||||
return clsInfo.getShortName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName(ClassNode cls) {
|
||||
return getClassName(cls.getClassInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName(ClassInfo clsInfo) {
|
||||
final DefaultDeobfuscatorClassInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getNameWithoutPackage(deobfClsInfo);
|
||||
}
|
||||
|
||||
return getNameWithoutPackage(clsInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassFullName(ClassNode cls) {
|
||||
return getClassFullName(cls.getClassInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassFullName(ClassInfo clsInfo) {
|
||||
final DefaultDeobfuscatorClassInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getFullName();
|
||||
}
|
||||
|
||||
return getPackageName(clsInfo.getPackage()) + DefaultDeobfuscator.classNameSepearator + getClassName(clsInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassFullPath(ClassInfo clsInfo) {
|
||||
final DefaultDeobfuscatorClassInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.pkg.getFullAlias().replace(DefaultDeobfuscator.classNameSepearator, File.separatorChar)
|
||||
+ File.separatorChar
|
||||
+ deobfClsInfo.getNameWithoutPackage(deobfClsInfo).replace(DefaultDeobfuscator.classNameSepearator, '_');
|
||||
}
|
||||
|
||||
|
||||
return getPackageName(clsInfo.getPackage()).replace('.', File.separatorChar)
|
||||
+ File.separatorChar
|
||||
+ clsInfo.getNameWithoutPackage().replace('.', '_');
|
||||
}
|
||||
}
|
37
jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
Normal file
37
jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
Normal file
@ -0,0 +1,37 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
public class Deobfuscator {
|
||||
|
||||
private static final StubDeobfuscator stubDeobfuscator;
|
||||
private static IDeobfuscator deobfuscatorInstance;
|
||||
|
||||
static {
|
||||
stubDeobfuscator = new StubDeobfuscator();
|
||||
deobfuscatorInstance = stubDeobfuscator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets instance of active deobfuscator
|
||||
*
|
||||
* @return deobfuscator instance
|
||||
*/
|
||||
public static IDeobfuscator instance() {
|
||||
return deobfuscatorInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets active deobfuscator
|
||||
*
|
||||
* @param deobfuscator object that makes deobfuscation or {@code null}
|
||||
* to set stub deobfuscator
|
||||
*
|
||||
*/
|
||||
public static void setDeobfuscator(IDeobfuscator deobfuscator) {
|
||||
if (deobfuscator != null) {
|
||||
deobfuscatorInstance = deobfuscator;
|
||||
} else {
|
||||
deobfuscatorInstance = stubDeobfuscator;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
18
jadx-core/src/main/java/jadx/core/deobf/IDeobfuscator.java
Normal file
18
jadx-core/src/main/java/jadx/core/deobf/IDeobfuscator.java
Normal file
@ -0,0 +1,18 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
public interface IDeobfuscator {
|
||||
|
||||
public String getPackageName(String packageName);
|
||||
|
||||
public String getClassShortName(ClassNode cls);
|
||||
public String getClassShortName(ClassInfo clsInfo);
|
||||
public String getClassName(ClassNode cls);
|
||||
public String getClassName(ClassInfo clsInfo);
|
||||
public String getClassFullName(ClassNode cls);
|
||||
public String getClassFullName(ClassInfo clsInfo);
|
||||
|
||||
public String getClassFullPath(ClassInfo clsInfo);
|
||||
}
|
147
jadx-core/src/main/java/jadx/core/deobf/PackageNode.java
Normal file
147
jadx-core/src/main/java/jadx/core/deobf/PackageNode.java
Normal file
@ -0,0 +1,147 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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 String packageName;
|
||||
private String packageAlias;
|
||||
|
||||
private String cachedPackageFullName;
|
||||
private String cachedPackageFullAlias;
|
||||
|
||||
public PackageNode(String packageName) {
|
||||
this.packageName = packageName;
|
||||
this.parentPackage = this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
if (cachedPackageFullName == null) {
|
||||
Stack<PackageNode> pp = getParentPackages();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (pp.size() > 0) {
|
||||
result.append(separatorChar);
|
||||
result.append(pp.pop().getName());
|
||||
}
|
||||
|
||||
cachedPackageFullName = result.toString();
|
||||
}
|
||||
|
||||
return cachedPackageFullName;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
if (packageAlias != null) {
|
||||
return packageAlias;
|
||||
}
|
||||
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
packageAlias = alias;
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
return (packageAlias != null);
|
||||
}
|
||||
|
||||
public String getFullAlias() {
|
||||
if (cachedPackageFullAlias == null) {
|
||||
Stack<PackageNode> pp = getParentPackages();
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getAlias());
|
||||
while (pp.size() > 0) {
|
||||
result.append(separatorChar);
|
||||
result.append(pp.pop().getAlias());
|
||||
}
|
||||
|
||||
cachedPackageFullAlias = result.toString();
|
||||
}
|
||||
|
||||
return cachedPackageFullAlias;
|
||||
}
|
||||
|
||||
public PackageNode getParentPackage() {
|
||||
return parentPackage;
|
||||
}
|
||||
|
||||
public List<PackageNode> getInnerPackages() {
|
||||
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>();
|
||||
}
|
||||
innerPackages.add(pkg);
|
||||
pkg.parentPackage = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets inner package node by name
|
||||
*
|
||||
* @param name inner package name
|
||||
*
|
||||
* @return package node or {@code null}
|
||||
*/
|
||||
public PackageNode getInnerPackageByName(String name) {
|
||||
PackageNode result = null;
|
||||
for (PackageNode p : innerPackages) {
|
||||
if (p.getName().equals(name)) {
|
||||
result = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills stack with parent packages exclude root node
|
||||
*
|
||||
* @return stack with parent packages
|
||||
*/
|
||||
private Stack<PackageNode> getParentPackages() {
|
||||
Stack<PackageNode> pp = new Stack<PackageNode>();
|
||||
|
||||
PackageNode currentP = this;
|
||||
PackageNode parentP = currentP.getParentPackage();
|
||||
|
||||
while (currentP != parentP) {
|
||||
pp.push(currentP);
|
||||
currentP = parentP;
|
||||
parentP = currentP.getParentPackage();
|
||||
}
|
||||
|
||||
return pp;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
public class StubDeobfuscator implements IDeobfuscator {
|
||||
|
||||
@Override
|
||||
public String getPackageName(String packageName) {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassShortName(ClassNode cls) {
|
||||
return cls.getShortName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassShortName(ClassInfo clsInfo) {
|
||||
return clsInfo.getShortName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName(ClassNode cls) {
|
||||
return cls.getClassInfo().getNameWithoutPackage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName(ClassInfo clsInfo) {
|
||||
return clsInfo.getNameWithoutPackage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassFullName(ClassNode cls) {
|
||||
return cls.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassFullName(ClassInfo clsInfo) {
|
||||
return clsInfo.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassFullPath(ClassInfo clsInfo) {
|
||||
return clsInfo.getFullPath();
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.codegen.MethodGen;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@ -104,7 +105,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
+ (useRegions ? ".regions" : "")
|
||||
+ (rawInsn ? ".raw" : "")
|
||||
+ ".dot";
|
||||
dot.save(dir, mth.getParentClass().getClassInfo().getFullPath() + "_graphs", fileName);
|
||||
dot.save(dir, Deobfuscator.instance().getClassFullPath(mth.getParentClass().getClassInfo()) + "_graphs", fileName);
|
||||
}
|
||||
|
||||
private void processMethodRegion(MethodNode mth) {
|
||||
|
@ -2,6 +2,7 @@ package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
@ -24,7 +25,7 @@ public class SaveCode extends AbstractVisitor {
|
||||
|
||||
public static void save(File dir, IJadxArgs args, ClassNode cls) {
|
||||
CodeWriter clsCode = cls.getCode();
|
||||
String fileName = cls.getClassInfo().getFullPath() + ".java";
|
||||
String fileName = Deobfuscator.instance().getClassFullPath(cls.getClassInfo()) + ".java";
|
||||
if (args.isFallbackMode()) {
|
||||
fileName += ".jadx";
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user