core: refactor deobfuscator
This commit is contained in:
@@ -56,12 +56,12 @@ public class DefaultJadxArgs implements IJadxArgs {
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return Integer.MIN_VALUE+1;
|
||||
return Integer.MIN_VALUE + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return Integer.MAX_VALUE-1;
|
||||
return Integer.MAX_VALUE - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,8 +4,6 @@ import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.deobf.DefaultDeobfuscator;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
@@ -93,6 +91,8 @@ public final class JadxDecompiler {
|
||||
resources = null;
|
||||
xmlParser = null;
|
||||
root = null;
|
||||
passes = null;
|
||||
codeGen = null;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
@@ -254,56 +254,27 @@ public final class JadxDecompiler {
|
||||
|
||||
void parse() throws DecodeException {
|
||||
reset();
|
||||
root = new RootNode();
|
||||
init();
|
||||
|
||||
root = new RootNode(args);
|
||||
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.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.initAppResClass();
|
||||
|
||||
initVisitors();
|
||||
}
|
||||
|
||||
private void initVisitors() {
|
||||
for (IDexTreeVisitor pass : passes) {
|
||||
try {
|
||||
pass.init(root);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processClass(ClassNode cls) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -116,6 +115,9 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
@@ -151,16 +153,16 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return Deobfuscator.instance().getClassShortName(cls);
|
||||
return cls.getShortName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return Deobfuscator.instance().getClassFullName(cls);
|
||||
return cls.getFullName();
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return Deobfuscator.instance().getPackageName(cls.getPackage());
|
||||
return cls.getPackage();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -11,7 +9,7 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
private final List<JavaClass> classes;
|
||||
|
||||
JavaPackage(String name, List<JavaClass> classes) {
|
||||
this.name = Deobfuscator.instance().getPackageName(name);
|
||||
this.name = name;
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ReSugarCode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
|
||||
@@ -106,6 +107,10 @@ public class Jadx {
|
||||
passes.add(new ProcessVariables());
|
||||
|
||||
passes.add(new DependencyCollector());
|
||||
|
||||
if (args.isDeobfuscationOn()) {
|
||||
passes.add(new RenameVisitor());
|
||||
}
|
||||
}
|
||||
return passes;
|
||||
}
|
||||
|
||||
@@ -33,9 +33,7 @@ public final class ProcessClass {
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
DepthTraversal.visit(visitor, cls);
|
||||
}
|
||||
for (ClassNode clsNode : cls.getDependencies()) {
|
||||
process(clsNode, passes, null);
|
||||
}
|
||||
processDependencies(cls, passes);
|
||||
cls.setState(PROCESSED);
|
||||
}
|
||||
if (cls.getState() == PROCESSED && codeGen != null) {
|
||||
@@ -52,4 +50,14 @@ public final class ProcessClass {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
for (ClassNode depCls : cls.getDependencies()) {
|
||||
if (cls.getTopParentClass() == cls) {
|
||||
// ignore inner classes of this class
|
||||
continue;
|
||||
}
|
||||
process(depCls, passes, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
@@ -77,15 +77,15 @@ public class ClsSet {
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
|
||||
ClassInfo superClass = cls.getSuperClass();
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
NClass c = getCls(superClass.getRawName(), names);
|
||||
NClass c = getCls(superClass.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
}
|
||||
for (ClassInfo iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getRawName(), names);
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
|
||||
@@ -45,16 +45,10 @@ public class ClspGraph {
|
||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||
}
|
||||
int size = classes.size();
|
||||
for (ClassNode cls : classes) {
|
||||
size += cls.getInnerClasses().size();
|
||||
}
|
||||
NClass[] nClasses = new NClass[size];
|
||||
int k = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
nClasses[k++] = addClass(cls);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
nClasses[k++] = addClass(inner);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||
@@ -62,8 +56,9 @@ public class ClspGraph {
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
NClass nClass = new NClass(cls.getRawName(), -1);
|
||||
nameMap.put(cls.getRawName(), nClass);
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
nameMap.put(rawName, nClass);
|
||||
return nClass;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.api.DefaultJadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
@@ -42,7 +43,7 @@ public class ConvertToClsSet {
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode();
|
||||
RootNode root = new RootNode(new DefaultJadxArgs());
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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;
|
||||
@@ -81,14 +79,14 @@ public class ClassGen {
|
||||
|
||||
CodeWriter clsCode = new CodeWriter();
|
||||
if (!"".equals(cls.getPackage())) {
|
||||
clsCode.add("package ").add(Deobfuscator.instance().getPackageName(cls.getPackage())).add(';');
|
||||
clsCode.add("package ").add(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(Deobfuscator.instance().getClassFullName(ic));
|
||||
sortImports.add(ic.getAlias().getFullName());
|
||||
}
|
||||
Collections.sort(sortImports);
|
||||
|
||||
@@ -126,7 +124,7 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
// 'static' modifier not allowed for top classes (not inner)
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
if (!cls.getAlias().isInner()) {
|
||||
af = af.remove(AccessFlags.ACC_STATIC);
|
||||
}
|
||||
|
||||
@@ -143,15 +141,15 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("class ");
|
||||
}
|
||||
clsCode.add(Deobfuscator.instance().getClassShortName(cls));
|
||||
clsCode.add(cls.getShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenericMap());
|
||||
clsCode.add(' ');
|
||||
|
||||
ClassInfo sup = cls.getSuperClass();
|
||||
ArgType sup = cls.getSuperClass();
|
||||
if (sup != null
|
||||
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
|
||||
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
|
||||
&& !sup.equals(ArgType.OBJECT)
|
||||
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
|
||||
clsCode.add("extends ");
|
||||
useClass(clsCode, sup);
|
||||
clsCode.add(' ');
|
||||
@@ -163,8 +161,8 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("implements ");
|
||||
}
|
||||
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||
ClassInfo interf = it.next();
|
||||
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||
ArgType interf = it.next();
|
||||
useClass(clsCode, interf);
|
||||
if (it.hasNext()) {
|
||||
clsCode.add(", ");
|
||||
@@ -192,7 +190,7 @@ public class ClassGen {
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
useClass(code, ClassInfo.fromType(cls.dex(), type));
|
||||
useClass(code, type);
|
||||
}
|
||||
if (list != null && !list.isEmpty()) {
|
||||
code.add(" extends ");
|
||||
@@ -201,7 +199,7 @@ public class ClassGen {
|
||||
if (g.isGenericType()) {
|
||||
code.add(g.getObject());
|
||||
} else {
|
||||
useClass(code, ClassInfo.fromType(cls.dex(), g));
|
||||
useClass(code, g);
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(" & ");
|
||||
@@ -407,7 +405,7 @@ public class ClassGen {
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
useClass(code, ClassInfo.fromType(cls.dex(), type));
|
||||
useClass(code, type);
|
||||
}
|
||||
} else if (stype == PrimitiveType.ARRAY) {
|
||||
useType(code, type.getArrayElement());
|
||||
@@ -417,14 +415,9 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
code.attachAnnotation(classNode);
|
||||
}
|
||||
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
code.add(baseClass);
|
||||
ArgType[] generics = classInfo.getType().getGenericTypes();
|
||||
public void useClass(CodeWriter code, ArgType type) {
|
||||
useClass(code, ClassInfo.extCls(cls.dex(), type));
|
||||
ArgType[] generics = type.getGenericTypes();
|
||||
if (generics != null) {
|
||||
code.add('<');
|
||||
int len = generics.length;
|
||||
@@ -449,54 +442,63 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
|
||||
String fullName = classInfo.getFullName();
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
code.attachAnnotation(classNode);
|
||||
}
|
||||
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
|
||||
code.add(baseClass);
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||
String fullName = extClsInfo.getFullName();
|
||||
if (fallback) {
|
||||
return fullName;
|
||||
}
|
||||
fullName = Deobfuscator.instance().getClassFullName(classInfo);
|
||||
String shortName = Deobfuscator.instance().getClassShortName(classInfo);
|
||||
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
|
||||
fullName = extClsInfo.getFullName();
|
||||
String shortName = extClsInfo.getShortName();
|
||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||
return shortName;
|
||||
} else {
|
||||
// don't add import if this class inner for current class
|
||||
if (isClassInnerFor(classInfo, useCls)) {
|
||||
if (isClassInnerFor(extClsInfo, useCls)) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if this class from same package
|
||||
if (classInfo.getPackage().equals(useCls.getPackage()) && !classInfo.isInner()) {
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if class not public (must be accessed using inheritance)
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
|
||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
||||
return shortName;
|
||||
}
|
||||
if (searchCollision(cls.dex(), useCls, classInfo)) {
|
||||
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
if (classInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = Deobfuscator.instance().getClassName(classInfo);
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = extClsInfo.getNameWithoutPackage();
|
||||
}
|
||||
for (ClassInfo importCls : getImports()) {
|
||||
if (!importCls.equals(classInfo)
|
||||
if (!importCls.equals(extClsInfo)
|
||||
&& importCls.getShortName().equals(shortName)) {
|
||||
if (classInfo.isInner()) {
|
||||
String parent = useClassInternal(useCls, classInfo.getParentClass());
|
||||
if (extClsInfo.isInner()) {
|
||||
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
|
||||
return parent + "." + shortName;
|
||||
} else {
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
addImport(classInfo);
|
||||
addImport(extClsInfo);
|
||||
return shortName;
|
||||
}
|
||||
}
|
||||
|
||||
private void addImport(ClassInfo classInfo) {
|
||||
if (parentGen != null) {
|
||||
parentGen.addImport(classInfo);
|
||||
parentGen.addImport(classInfo.getAlias());
|
||||
} else {
|
||||
imports.add(classInfo);
|
||||
}
|
||||
@@ -530,7 +532,7 @@ public class ClassGen {
|
||||
if (classNode != null) {
|
||||
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||
if (inner.getShortName().equals(shortName)
|
||||
&& !inner.getClassInfo().equals(searchCls)) {
|
||||
&& !inner.getAlias().equals(searchCls)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,14 +162,14 @@ public class InsnGen {
|
||||
|
||||
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getFullName().startsWith(declClass.getFullName());
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||
if (!fieldFromThisClass) {
|
||||
// Android specific resources class handler
|
||||
ClassInfo parentClass = declClass.getParentClass();
|
||||
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
||||
clsGen.useClass(code, parentClass);
|
||||
code.add('.');
|
||||
code.add(declClass.getShortName());
|
||||
code.add(declClass.getAlias().getShortName());
|
||||
} else {
|
||||
clsGen.useClass(code, declClass);
|
||||
}
|
||||
@@ -186,6 +186,10 @@ public class InsnGen {
|
||||
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ArgType type) {
|
||||
mgen.getClassGen().useClass(code, type);
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo cls) {
|
||||
mgen.getClassGen().useClass(code, cls);
|
||||
}
|
||||
@@ -200,9 +204,6 @@ public class InsnGen {
|
||||
|
||||
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
try {
|
||||
if (insn.getType() == InsnType.NOP) {
|
||||
return false;
|
||||
}
|
||||
Set<Flags> state = EnumSet.noneOf(Flags.class);
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
state.add(flag);
|
||||
@@ -531,7 +532,7 @@ public class InsnGen {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
// anonymous class construction
|
||||
ClassInfo parent;
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
@@ -600,7 +601,7 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case STATIC:
|
||||
ClassInfo insnCls = mth.getParentClass().getClassInfo();
|
||||
ClassInfo insnCls = mth.getParentClass().getAlias();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (!insnCls.equals(declClass)) {
|
||||
useClass(code, declClass);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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;
|
||||
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -86,7 +86,7 @@ public class MethodGen {
|
||||
code.add(' ');
|
||||
}
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
code.add(Deobfuscator.instance().getClassShortName(classGen.getClassNode())); // constructor
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
} else {
|
||||
classGen.useType(code, mth.getReturnType());
|
||||
code.add(' ');
|
||||
@@ -209,7 +209,7 @@ public class MethodGen {
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn == null) {
|
||||
if (insn == null || insn.getType() == InsnType.NOP) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
|
||||
|
||||
@@ -158,8 +158,8 @@ public class NameGen {
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
ClassInfo clsInfo = ClassInfo.fromType(mth.dex(), type);
|
||||
String shortName = clsInfo.getShortName();
|
||||
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
@@ -223,12 +223,12 @@ public class NameGen {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String makeNameFromInvoke(MethodInfo callMth) {
|
||||
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||
String name = callMth.getName();
|
||||
if (name.startsWith("get") || name.startsWith("set")) {
|
||||
return fromName(name.substring(3));
|
||||
}
|
||||
ArgType declType = callMth.getDeclClass().getType();
|
||||
ArgType declType = callMth.getDeclClass().getAlias().getType();
|
||||
if ("iterator".equals(name)) {
|
||||
return "it";
|
||||
}
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
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('.', '_');
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,365 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
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 Deobfuscator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
||||
|
||||
private static final StubDeobfuscator stubDeobfuscator;
|
||||
private static IDeobfuscator deobfuscatorInstance;
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
static {
|
||||
stubDeobfuscator = new StubDeobfuscator();
|
||||
deobfuscatorInstance = stubDeobfuscator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets instance of active deobfuscator
|
||||
*
|
||||
* @return deobfuscator instance
|
||||
*/
|
||||
public static IDeobfuscator instance() {
|
||||
return deobfuscatorInstance;
|
||||
private static final String MAP_FILE_CHARSET = "UTF-8";
|
||||
private static final String classNameSeparator = ".";
|
||||
private static final String innerClassSeparator = "$";
|
||||
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
|
||||
private final IJadxArgs args;
|
||||
private final File deobfMapFile;
|
||||
private final List<DexNode> dexNodes;
|
||||
|
||||
private int maxLength = 40;
|
||||
private int minLength = 2;
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
|
||||
private PackageNode rootPackage = new PackageNode("");
|
||||
private Map<String, String> preLoadClsMap = Collections.emptyMap();
|
||||
|
||||
public Deobfuscator(IJadxArgs args, List<DexNode> dexNodes, File deobfMapFile) {
|
||||
this.args = args;
|
||||
this.dexNodes = dexNodes;
|
||||
this.deobfMapFile = deobfMapFile;
|
||||
|
||||
this.minLength = args.getDeobfuscationMinLength();
|
||||
this.maxLength = args.getDeobfuscationMaxLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
public void execute() {
|
||||
if (deobfMapFile.exists()) {
|
||||
try {
|
||||
load();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
process();
|
||||
try {
|
||||
if (deobfMapFile.exists()) {
|
||||
if (args.isDeobfuscationForceSave()) {
|
||||
save();
|
||||
} else {
|
||||
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||
deobfMapFile.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
save();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void process() {
|
||||
preProcess();
|
||||
if (DEBUG) {
|
||||
dumpAlias();
|
||||
}
|
||||
preLoadClsMap.clear();
|
||||
preLoadClsMap = Collections.emptyMap();
|
||||
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode classNode : dexNode.getClasses()) {
|
||||
ClassInfo clsInfo = classNode.getClassInfo();
|
||||
String fullName = getClassFullName(clsInfo);
|
||||
clsInfo.rename(dexNode, fullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets package node for full package name
|
||||
*
|
||||
* @param fullPkgName full package name
|
||||
* @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) {
|
||||
if (fullPkgName.isEmpty() || fullPkgName.equals(classNameSeparator)) {
|
||||
return rootPackage;
|
||||
}
|
||||
PackageNode result = rootPackage;
|
||||
PackageNode parentNode;
|
||||
do {
|
||||
String pkgName;
|
||||
int idx = fullPkgName.indexOf(classNameSeparator);
|
||||
|
||||
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) && (create)) {
|
||||
result = new PackageNode(pkgName);
|
||||
parentNode.addInnerPackage(result);
|
||||
}
|
||||
} while (!fullPkgName.isEmpty() && (result != null));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private final class DeobfClsInfo {
|
||||
public ClassNode cls;
|
||||
public PackageNode pkg;
|
||||
public String alias;
|
||||
|
||||
public DeobfClsInfo(ClassNode cls, PackageNode pkg) {
|
||||
this.cls = cls;
|
||||
this.pkg = pkg;
|
||||
}
|
||||
|
||||
public String makeNameWithoutPkg() {
|
||||
String prefix;
|
||||
ClassNode parentClass = cls.getParentClass();
|
||||
if (parentClass != cls) {
|
||||
DeobfClsInfo parentDeobfClsInfo = clsMap.get(parentClass.getClassInfo());
|
||||
if (parentDeobfClsInfo != null) {
|
||||
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
|
||||
} else {
|
||||
prefix = getNameWithoutPackage(parentClass.getClassInfo());
|
||||
}
|
||||
prefix += innerClassSeparator;
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
return prefix + ((this.alias != null) ? this.alias : this.cls.getShortName());
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return pkg.getFullAlias() + classNameSeparator + makeNameWithoutPkg();
|
||||
}
|
||||
}
|
||||
|
||||
public String getNameWithoutPackage(ClassInfo clsInfo) {
|
||||
String prefix;
|
||||
ClassInfo parentClsInfo = clsInfo.getParentClass();
|
||||
if (parentClsInfo != null) {
|
||||
DeobfClsInfo parentDeobfClsInfo = clsMap.get(parentClsInfo);
|
||||
if (parentDeobfClsInfo != null) {
|
||||
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
|
||||
} else {
|
||||
prefix = getNameWithoutPackage(parentClsInfo);
|
||||
}
|
||||
prefix += innerClassSeparator;
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
return prefix + clsInfo.getShortName();
|
||||
}
|
||||
|
||||
private void doClass(ClassNode cls) {
|
||||
final String pkgFullName = cls.getClassInfo().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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clsMap.containsKey(cls.getClassInfo())) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads deobfuscator presets
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void load() throws IOException {
|
||||
if (!deobfMapFile.exists()) {
|
||||
return;
|
||||
}
|
||||
List<String> lines = FileUtils.readLines(deobfMapFile, 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
public void save() 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 (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
||||
if (deobfClsInfo.alias != null) {
|
||||
list.add(String.format("c %s=%s", deobfClsInfo.cls.getFullName(), deobfClsInfo.alias));
|
||||
}
|
||||
}
|
||||
Collections.sort(list);
|
||||
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
|
||||
list.clear();
|
||||
}
|
||||
|
||||
public String getPackageName(String packageName) {
|
||||
final PackageNode pkg = getPackageNode(packageName, false);
|
||||
if (pkg != null) {
|
||||
return pkg.getFullAlias();
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public String getClassName(ClassInfo clsInfo) {
|
||||
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.makeNameWithoutPkg();
|
||||
}
|
||||
return getNameWithoutPackage(clsInfo);
|
||||
}
|
||||
|
||||
public String getClassFullName(ClassNode cls) {
|
||||
return getClassFullName(cls.getClassInfo());
|
||||
}
|
||||
|
||||
public String getClassFullName(ClassInfo clsInfo) {
|
||||
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getFullName();
|
||||
}
|
||||
return getPackageName(clsInfo.getPackage()) + classNameSeparator + getClassName(clsInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class PackageNode {
|
||||
private List<PackageNode> innerPackages = Collections.emptyList();
|
||||
|
||||
public static final char separatorChar = '.';
|
||||
|
||||
|
||||
private String packageName;
|
||||
private String packageAlias;
|
||||
|
||||
@@ -51,14 +51,14 @@ public class PackageNode {
|
||||
if (packageAlias != null) {
|
||||
return packageAlias;
|
||||
}
|
||||
|
||||
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
packageAlias = alias;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasAlias() {
|
||||
return (packageAlias != null);
|
||||
}
|
||||
@@ -108,9 +108,8 @@ public class PackageNode {
|
||||
|
||||
/**
|
||||
* Gets inner package node by name
|
||||
*
|
||||
*
|
||||
* @param name inner package name
|
||||
*
|
||||
* @return package node or {@code null}
|
||||
*/
|
||||
public PackageNode getInnerPackageByName(String name) {
|
||||
@@ -127,7 +126,7 @@ public class PackageNode {
|
||||
|
||||
/**
|
||||
* Fills stack with parent packages exclude root node
|
||||
*
|
||||
*
|
||||
* @return stack with parent packages
|
||||
*/
|
||||
private Stack<PackageNode> getParentPackages() {
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,17 +16,27 @@ public final class ClassInfo {
|
||||
private String fullName;
|
||||
// for inner class not equals null
|
||||
private ClassInfo parentClass;
|
||||
// class info after rename (deobfuscation)
|
||||
private ClassInfo alias;
|
||||
|
||||
private ClassInfo(DexNode dex, ArgType type) {
|
||||
if (!type.isObject()) {
|
||||
this(dex, type, true);
|
||||
}
|
||||
|
||||
private ClassInfo(DexNode dex, ArgType type, boolean inner) {
|
||||
if (!type.isObject() || type.isGeneric()) {
|
||||
throw new JadxRuntimeException("Not class type: " + type);
|
||||
}
|
||||
this.type = type;
|
||||
this.alias = this;
|
||||
|
||||
splitNames(dex, true);
|
||||
splitNames(dex, inner);
|
||||
}
|
||||
|
||||
public static ClassInfo fromType(DexNode dex, ArgType type) {
|
||||
if (type.isArray()) {
|
||||
type = ArgType.OBJECT;
|
||||
}
|
||||
ClassInfo cls = dex.getInfoStorage().getCls(type);
|
||||
if (cls != null) {
|
||||
return cls;
|
||||
@@ -39,26 +49,32 @@ public final class ClassInfo {
|
||||
if (clsIndex == DexNode.NO_INDEX) {
|
||||
return null;
|
||||
}
|
||||
ArgType type = dex.getType(clsIndex);
|
||||
if (type.isArray()) {
|
||||
type = ArgType.OBJECT;
|
||||
}
|
||||
return fromType(dex, type);
|
||||
return fromType(dex, dex.getType(clsIndex));
|
||||
}
|
||||
|
||||
public static ClassInfo fromName(DexNode dex, String clsName) {
|
||||
return fromType(dex, ArgType.object(clsName));
|
||||
}
|
||||
|
||||
public static ClassInfo extCls(DexNode dex, ArgType type) {
|
||||
ClassInfo classInfo = fromName(dex, type.getObject());
|
||||
return classInfo.alias;
|
||||
}
|
||||
|
||||
public void rename(DexNode dex, String fullName) {
|
||||
this.alias = new ClassInfo(dex, ArgType.object(fullName), isInner());
|
||||
}
|
||||
|
||||
public ClassInfo getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
private void splitNames(DexNode dex, boolean canBeInner) {
|
||||
String fullObjectName = type.getObject();
|
||||
assert fullObjectName.indexOf('/') == -1 : "Raw type: " + type;
|
||||
|
||||
String clsName;
|
||||
int dot = fullObjectName.lastIndexOf('.');
|
||||
if (dot == -1) {
|
||||
// rename default package if it used from class with package (often for obfuscated apps),
|
||||
pkg = Consts.DEFAULT_PACKAGE_NAME;
|
||||
pkg = "";
|
||||
clsName = fullObjectName;
|
||||
} else {
|
||||
pkg = fullObjectName.substring(0, dot);
|
||||
@@ -83,8 +99,12 @@ public final class ClassInfo {
|
||||
if (NameMapper.isReserved(clsName)) {
|
||||
clsName += "_";
|
||||
}
|
||||
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + clsName;
|
||||
this.name = clsName;
|
||||
if (parentClass != null) {
|
||||
this.fullName = parentClass.fullName + "." + clsName;
|
||||
} else {
|
||||
this.fullName = pkg.isEmpty() ? clsName : pkg + "." + clsName;
|
||||
}
|
||||
}
|
||||
|
||||
public String getFullPath() {
|
||||
@@ -97,28 +117,23 @@ public final class ClassInfo {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public boolean isObject() {
|
||||
return fullName.equals(Consts.CLASS_OBJECT);
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return type.getObject();
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public boolean isPackageDefault() {
|
||||
return pkg.isEmpty() || pkg.equals(Consts.DEFAULT_PACKAGE_NAME);
|
||||
public String getRawName() {
|
||||
return type.getObject();
|
||||
}
|
||||
|
||||
public String getNameWithoutPackage() {
|
||||
return (parentClass != null ? parentClass.getNameWithoutPackage() + "." : "") + name;
|
||||
if (parentClass == null) {
|
||||
return name;
|
||||
}
|
||||
return parentClass.getNameWithoutPackage() + "." + name;
|
||||
}
|
||||
|
||||
public ClassInfo getParentClass() {
|
||||
@@ -154,7 +169,7 @@ public final class ClassInfo {
|
||||
}
|
||||
if (obj instanceof ClassInfo) {
|
||||
ClassInfo other = (ClassInfo) obj;
|
||||
return this.getFullName().equals(other.getFullName());
|
||||
return this.type.equals(other.type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import jadx.core.dex.nodes.DexNode;
|
||||
|
||||
import com.android.dex.FieldId;
|
||||
|
||||
public class FieldInfo {
|
||||
public final class FieldInfo {
|
||||
|
||||
private final ClassInfo declClass;
|
||||
private final String name;
|
||||
|
||||
@@ -621,12 +621,10 @@ public class InsnDecoder {
|
||||
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
|
||||
}
|
||||
}
|
||||
InsnNode node = new FilledNewArrayNode(elType, regs == null ? 0 : regs.length);
|
||||
InsnNode node = new FilledNewArrayNode(elType, regs.length);
|
||||
node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
|
||||
if (regs != null) {
|
||||
for (InsnArg arg : regs) {
|
||||
node.addArg(arg);
|
||||
}
|
||||
for (InsnArg arg : regs) {
|
||||
node.addArg(arg);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public abstract class ArgType {
|
||||
public static final ArgType OBJECT = object(Consts.CLASS_OBJECT);
|
||||
public static final ArgType CLASS = object(Consts.CLASS_CLASS);
|
||||
public static final ArgType STRING = object(Consts.CLASS_STRING);
|
||||
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
|
||||
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
|
||||
|
||||
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -46,8 +47,8 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
private final DexNode dex;
|
||||
private final ClassInfo clsInfo;
|
||||
private final AccessInfo accessFlags;
|
||||
private ClassInfo superClass;
|
||||
private List<ClassInfo> interfaces;
|
||||
private ArgType superClass;
|
||||
private List<ArgType> interfaces;
|
||||
private Map<ArgType, List<ArgType>> genericMap;
|
||||
|
||||
private final List<MethodNode> methods;
|
||||
@@ -70,11 +71,11 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
|
||||
this.superClass = null;
|
||||
} else {
|
||||
this.superClass = ClassInfo.fromDex(dex, cls.getSupertypeIndex());
|
||||
this.superClass = dex.getType(cls.getSupertypeIndex());
|
||||
}
|
||||
this.interfaces = new ArrayList<ClassInfo>(cls.getInterfaces().length);
|
||||
this.interfaces = new ArrayList<ArgType>(cls.getInterfaces().length);
|
||||
for (short interfaceIdx : cls.getInterfaces()) {
|
||||
this.interfaces.add(ClassInfo.fromDex(dex, interfaceIdx));
|
||||
this.interfaces.add(dex.getType(interfaceIdx));
|
||||
}
|
||||
if (cls.getClassDataOffset() != 0) {
|
||||
ClassData clsData = dex.readClassData(cls);
|
||||
@@ -111,7 +112,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
int sfIdx = cls.getSourceFileIndex();
|
||||
if (sfIdx != DexNode.NO_INDEX) {
|
||||
String fileName = dex.getString(sfIdx);
|
||||
if (!this.getFullName().contains(fileName.replace(".java", ""))
|
||||
if (!clsInfo.getFullName().contains(fileName.replace(".java", ""))
|
||||
&& !fileName.equals("SourceFile")) {
|
||||
this.addAttr(new SourceFileAttr(fileName));
|
||||
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
|
||||
@@ -129,7 +130,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException("Error decode class: " + getFullName(), e);
|
||||
throw new DecodeException("Error decode class: " + clsInfo, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,12 +192,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
// parse class generic map
|
||||
genericMap = sp.consumeGenericMap();
|
||||
// parse super class signature
|
||||
superClass = ClassInfo.fromType(dex, sp.consumeType());
|
||||
superClass = sp.consumeType();
|
||||
// parse interfaces signatures
|
||||
for (int i = 0; i < interfaces.size(); i++) {
|
||||
ArgType type = sp.consumeType();
|
||||
if (type != null) {
|
||||
interfaces.set(i, ClassInfo.fromType(dex, type));
|
||||
interfaces.set(i, type);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -247,11 +248,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
}
|
||||
|
||||
public ClassInfo getSuperClass() {
|
||||
@Nullable
|
||||
public ArgType getSuperClass() {
|
||||
return superClass;
|
||||
}
|
||||
|
||||
public List<ClassInfo> getInterfaces() {
|
||||
public List<ArgType> getInterfaces() {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
@@ -403,12 +405,14 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
|
||||
public boolean isEnum() {
|
||||
return getAccessFlags().isEnum() && getSuperClass().getFullName().equals(Consts.CLASS_ENUM);
|
||||
return getAccessFlags().isEnum()
|
||||
&& getSuperClass() != null
|
||||
&& getSuperClass().getObject().equals(ArgType.ENUM.getObject());
|
||||
}
|
||||
|
||||
public boolean isAnonymous() {
|
||||
return clsInfo.isInner()
|
||||
&& getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX)
|
||||
&& clsInfo.getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX)
|
||||
&& getDefaultConstructor() != null;
|
||||
}
|
||||
|
||||
@@ -429,24 +433,34 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
return dex;
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return clsInfo.getRawName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class info (don't use in code generation and external api).
|
||||
*/
|
||||
public ClassInfo getClassInfo() {
|
||||
return clsInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class info for external usage (code generation and external api).
|
||||
*/
|
||||
public ClassInfo getAlias() {
|
||||
return clsInfo.getAlias();
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return clsInfo.getShortName();
|
||||
return clsInfo.getAlias().getShortName();
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return clsInfo.getFullName();
|
||||
return clsInfo.getAlias().getFullName();
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return clsInfo.getPackage();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return clsInfo.getRawName();
|
||||
return clsInfo.getAlias().getPackage();
|
||||
}
|
||||
|
||||
public void setCode(CodeWriter code) {
|
||||
@@ -471,6 +485,6 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFullName();
|
||||
return clsInfo.getFullName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,10 @@ public class DexNode {
|
||||
|
||||
private final RootNode root;
|
||||
private final Dex dexBuf;
|
||||
private final InputFile file;
|
||||
|
||||
private final List<ClassNode> classes = new ArrayList<ClassNode>();
|
||||
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>();
|
||||
|
||||
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
|
||||
|
||||
@@ -41,12 +44,36 @@ public class DexNode {
|
||||
|
||||
public DexNode(RootNode root, InputFile input) {
|
||||
this.root = root;
|
||||
this.file = input;
|
||||
this.dexBuf = input.getDexBuffer();
|
||||
}
|
||||
|
||||
public void loadClasses() throws DecodeException {
|
||||
for (ClassDef cls : dexBuf.classDefs()) {
|
||||
classes.add(new ClassNode(this, cls));
|
||||
ClassNode clsNode = new ClassNode(this, cls);
|
||||
classes.add(clsNode);
|
||||
clsMap.put(clsNode.getClassInfo(), clsNode);
|
||||
}
|
||||
}
|
||||
|
||||
void initInnerClasses() {
|
||||
// move inner classes
|
||||
List<ClassNode> inner = new ArrayList<ClassNode>();
|
||||
for (ClassNode cls : classes) {
|
||||
if (cls.getClassInfo().isInner()) {
|
||||
inner.add(cls);
|
||||
}
|
||||
}
|
||||
for (ClassNode cls : inner) {
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
ClassNode parent = resolveClass(clsInfo.getParentClass());
|
||||
if (parent == null) {
|
||||
clsMap.remove(clsInfo);
|
||||
clsInfo.notInner(cls.dex());
|
||||
clsMap.put(clsInfo, cls);
|
||||
} else {
|
||||
parent.addInnerClass(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +83,7 @@ public class DexNode {
|
||||
|
||||
@Nullable
|
||||
public ClassNode resolveClass(ClassInfo clsInfo) {
|
||||
return root.resolveClass(clsInfo);
|
||||
return clsMap.get(clsInfo);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -85,6 +112,10 @@ public class DexNode {
|
||||
return infoStorage;
|
||||
}
|
||||
|
||||
public InputFile getInputFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
// DexBuffer wrappers
|
||||
|
||||
public String getString(int index) {
|
||||
|
||||
@@ -597,7 +597,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parentClass.getFullName() + "." + mthInfo.getName()
|
||||
return parentClass + "." + mthInfo.getName()
|
||||
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + "):"
|
||||
+ retType;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
@@ -27,19 +28,19 @@ import org.slf4j.LoggerFactory;
|
||||
public class RootNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
|
||||
|
||||
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
|
||||
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
||||
private final IJadxArgs args;
|
||||
|
||||
private List<DexNode> dexNodes;
|
||||
|
||||
/**
|
||||
* Resources *
|
||||
*/
|
||||
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
|
||||
@Nullable
|
||||
private String appPackage;
|
||||
private ClassNode appResClass;
|
||||
|
||||
public RootNode(IJadxArgs args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void load(List<InputFile> dexFiles) throws DecodeException {
|
||||
dexNodes = new ArrayList<DexNode>(dexFiles.size());
|
||||
for (InputFile dex : dexFiles) {
|
||||
@@ -54,21 +55,7 @@ public class RootNode {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
dexNode.loadClasses();
|
||||
}
|
||||
|
||||
List<ClassNode> classes = new ArrayList<ClassNode>();
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
names.put(cls.getFullName(), cls);
|
||||
}
|
||||
classes.addAll(dexNode.getClasses());
|
||||
}
|
||||
|
||||
try {
|
||||
initClassPath(classes);
|
||||
} catch (IOException e) {
|
||||
throw new DecodeException("Error loading classpath", e);
|
||||
}
|
||||
initInnerClasses(classes);
|
||||
initInnerClasses();
|
||||
}
|
||||
|
||||
public void loadResources(List<ResourceFile> resources) {
|
||||
@@ -99,55 +86,52 @@ public class RootNode {
|
||||
|
||||
ResourceStorage resStorage = parser.getResStorage();
|
||||
resourcesNames = resStorage.getResourcesNames();
|
||||
appPackage = resStorage.getAppPackage();
|
||||
}
|
||||
|
||||
public void initAppResClass() {
|
||||
ClassNode resCls = null;
|
||||
if (appPackage != null) {
|
||||
resCls = searchClassByName(appPackage + ".R");
|
||||
} else {
|
||||
for (ClassNode cls : names.values()) {
|
||||
if (cls.getShortName().equals("R")) {
|
||||
resCls = cls;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resCls != null) {
|
||||
appResClass = resCls;
|
||||
ClassNode resCls;
|
||||
if (appPackage == null) {
|
||||
appResClass = makeClass("R");
|
||||
return;
|
||||
}
|
||||
String fullName = appPackage + ".R";
|
||||
resCls = searchClassByName(fullName);
|
||||
if (resCls != null) {
|
||||
appResClass = resCls;
|
||||
} else {
|
||||
appResClass = makeClass(fullName);
|
||||
}
|
||||
}
|
||||
|
||||
private ClassNode makeClass(String clsName) {
|
||||
DexNode firstDex = dexNodes.get(0);
|
||||
appResClass = new ClassNode(firstDex, ClassInfo.fromName(firstDex, "R"));
|
||||
ClassInfo r = ClassInfo.fromName(firstDex, clsName);
|
||||
return new ClassNode(firstDex, r);
|
||||
}
|
||||
|
||||
private static void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
|
||||
if (!ArgType.isClspSet()) {
|
||||
ClspGraph clsp = new ClspGraph();
|
||||
clsp.load();
|
||||
clsp.addApp(classes);
|
||||
public void initClassPath() throws DecodeException {
|
||||
try {
|
||||
if (!ArgType.isClspSet()) {
|
||||
ClspGraph clsp = new ClspGraph();
|
||||
clsp.load();
|
||||
|
||||
ArgType.setClsp(clsp);
|
||||
List<ClassNode> classes = new ArrayList<ClassNode>();
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
classes.addAll(dexNode.getClasses());
|
||||
}
|
||||
clsp.addApp(classes);
|
||||
|
||||
ArgType.setClsp(clsp);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DecodeException("Error loading classpath", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initInnerClasses(List<ClassNode> classes) {
|
||||
// move inner classes
|
||||
List<ClassNode> inner = new ArrayList<ClassNode>();
|
||||
for (ClassNode cls : classes) {
|
||||
if (cls.getClassInfo().isInner()) {
|
||||
inner.add(cls);
|
||||
}
|
||||
}
|
||||
for (ClassNode cls : inner) {
|
||||
ClassNode parent = resolveClass(cls.getClassInfo().getParentClass());
|
||||
if (parent == null) {
|
||||
names.remove(cls.getFullName());
|
||||
cls.getClassInfo().notInner(cls.dex());
|
||||
names.put(cls.getFullName(), cls);
|
||||
} else {
|
||||
parent.addInnerClass(cls);
|
||||
}
|
||||
private void initInnerClasses() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
dexNode.initInnerClasses();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,12 +152,14 @@ public class RootNode {
|
||||
}
|
||||
|
||||
public ClassNode searchClassByName(String fullName) {
|
||||
return names.get(fullName);
|
||||
}
|
||||
|
||||
public ClassNode resolveClass(ClassInfo cls) {
|
||||
String fullName = cls.getFullName();
|
||||
return searchClassByName(fullName);
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(dexNode, fullName);
|
||||
ClassNode cls = dexNode.resolveClass(clsInfo);
|
||||
if (cls != null) {
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<DexNode> getDexNodes() {
|
||||
@@ -196,4 +182,8 @@ public class RootNode {
|
||||
public ClassNode getAppResClass() {
|
||||
return appResClass;
|
||||
}
|
||||
|
||||
public IJadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,15 @@ package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class AbstractVisitor implements IDexTreeVisitor {
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
return true;
|
||||
|
||||
@@ -199,7 +199,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
*/
|
||||
private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) {
|
||||
FieldInfo field = (FieldInfo) insn.getIndex();
|
||||
String thisClass = cls.getFullName();
|
||||
String thisClass = cls.getClassInfo().getFullName();
|
||||
if (field.getDeclClass().getFullName().equals(thisClass)) {
|
||||
FieldNode fn = cls.searchField(field);
|
||||
if (fn != null && fn.getAccessFlags().isFinal()) {
|
||||
|
||||
@@ -31,8 +31,8 @@ public class DependencyCollector extends AbstractVisitor {
|
||||
|
||||
private static void processClass(ClassNode cls, DexNode dex, Set<ClassNode> depList) {
|
||||
addDep(dex, depList, cls.getSuperClass());
|
||||
for (ClassInfo clsInfo : cls.getInterfaces()) {
|
||||
addDep(dex, depList, clsInfo);
|
||||
for (ArgType iType : cls.getInterfaces()) {
|
||||
addDep(dex, depList, iType);
|
||||
}
|
||||
for (FieldNode fieldNode : cls.getFields()) {
|
||||
addDep(dex, depList, fieldNode.getType());
|
||||
@@ -77,7 +77,7 @@ public class DependencyCollector extends AbstractVisitor {
|
||||
private static void addDep(DexNode dex, Set<ClassNode> depList, ArgType type) {
|
||||
if (type != null) {
|
||||
if (type.isObject()) {
|
||||
addDep(dex, depList, ClassInfo.fromName(type.getObject()));
|
||||
addDep(dex, depList, ClassInfo.fromName(dex, type.getObject()));
|
||||
ArgType[] genericTypes = type.getGenericTypes();
|
||||
if (type.isGeneric() && genericTypes != null) {
|
||||
for (ArgType argType : genericTypes) {
|
||||
|
||||
@@ -2,7 +2,6 @@ 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;
|
||||
@@ -68,7 +67,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
public void process(MethodNode mth) {
|
||||
dot.startLine("digraph \"CFG for");
|
||||
dot.add(escape(mth.getParentClass().getFullName() + "." + mth.getMethodInfo().getShortId()));
|
||||
dot.add(escape(mth.getParentClass() + "." + mth.getMethodInfo().getShortId()));
|
||||
dot.add("\" {");
|
||||
|
||||
if (useRegions) {
|
||||
@@ -85,7 +84,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
dot.startLine("MethodNode[shape=record,label=\"{");
|
||||
dot.add(escape(mth.getAccessFlags().makeString()));
|
||||
dot.add(escape(mth.getReturnType() + " "
|
||||
+ mth.getParentClass().getFullName() + "." + mth.getName()
|
||||
+ mth.getParentClass() + "." + mth.getName()
|
||||
+ "(" + Utils.listToString(mth.getArguments(true)) + ") "));
|
||||
|
||||
String attrs = attributesString(mth);
|
||||
@@ -105,7 +104,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
+ (useRegions ? ".regions" : "")
|
||||
+ (rawInsn ? ".raw" : "")
|
||||
+ ".dot";
|
||||
dot.save(dir, Deobfuscator.instance().getClassFullPath(mth.getParentClass().getClassInfo()) + "_graphs", fileName);
|
||||
dot.save(dir, mth.getParentClass().getClassInfo().getFullPath() + "_graphs", fileName);
|
||||
}
|
||||
|
||||
private void processMethodRegion(MethodNode mth) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
/**
|
||||
@@ -9,6 +10,11 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
*/
|
||||
public interface IDexTreeVisitor {
|
||||
|
||||
/**
|
||||
* Called after loading dex tree, but before visitor traversal.
|
||||
*/
|
||||
void init(RootNode root) throws JadxException;
|
||||
|
||||
/**
|
||||
* Visit class
|
||||
*
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
public class RenameVisitor extends AbstractVisitor {
|
||||
|
||||
private Deobfuscator deobfuscator;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
String firstInputFileName = root.getDexNodes().get(0).getInputFile().getFile().getAbsolutePath();
|
||||
String inputPath = FilenameUtils.getFullPathNoEndSeparator(firstInputFileName);
|
||||
String inputName = FilenameUtils.getBaseName(firstInputFileName);
|
||||
|
||||
File deobfMapFile = new File(inputPath, inputName + ".jobf");
|
||||
deobfuscator = new Deobfuscator(root.getArgs(), root.getDexNodes(), deobfMapFile);
|
||||
// TODO: check classes for case sensitive names (issue #24)
|
||||
// TODO: sometimes can be used source file name from 'SourceFileAttr'
|
||||
deobfuscator.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
// TODO: rename fields and methods
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ 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;
|
||||
|
||||
@@ -25,7 +24,7 @@ public class SaveCode extends AbstractVisitor {
|
||||
|
||||
public static void save(File dir, IJadxArgs args, ClassNode cls) {
|
||||
CodeWriter clsCode = cls.getCode();
|
||||
String fileName = Deobfuscator.instance().getClassFullPath(cls.getClassInfo()) + ".java";
|
||||
String fileName = cls.getClassInfo().getFullPath() + ".java";
|
||||
if (args.isFallbackMode()) {
|
||||
fileName += ".jadx";
|
||||
}
|
||||
|
||||
@@ -145,70 +145,70 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static InsnNode convertInvoke(MethodNode mth, InsnNode insn) {
|
||||
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
|
||||
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
|
||||
|
||||
// If this is a 'new StringBuilder(xxx).append(yyy).append(zzz).toString(),
|
||||
// convert it to STRING_CONCAT pseudo instruction.
|
||||
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
|
||||
&& callMth.getShortId().equals(Consts.MTH_TOSTRING_SIGNATURE)
|
||||
&& insn.getArg(0).isInsnWrap()) {
|
||||
try {
|
||||
List<InsnNode> chain = flattenInsnChain(insn);
|
||||
int constrIndex = -1; //RAF
|
||||
// Case where new StringBuilder() is called with NO args (the entire
|
||||
// string is created using .append() calls:
|
||||
if (chain.size()>1 && chain.get(0).getType()==InsnType.CONSTRUCTOR) {
|
||||
constrIndex = 0;
|
||||
} else if (chain.size()>2 && chain.get(1).getType()==InsnType.CONSTRUCTOR) {
|
||||
//RAF Case where the first string element is String arg to the
|
||||
// new StringBuilder("xxx") constructor
|
||||
constrIndex = 1;
|
||||
} else if (chain.size()>3 && chain.get(2).getType()==InsnType.CONSTRUCTOR) {
|
||||
//RAF Case where the first string element is String.valueOf() arg
|
||||
// to the new StringBuilder(String.valueOf(zzz)) constructor
|
||||
constrIndex = 2;
|
||||
}
|
||||
// If this is a 'new StringBuilder(xxx).append(yyy).append(zzz).toString(),
|
||||
// convert it to STRING_CONCAT pseudo instruction.
|
||||
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
|
||||
&& callMth.getShortId().equals(Consts.MTH_TOSTRING_SIGNATURE)
|
||||
&& insn.getArg(0).isInsnWrap()) {
|
||||
try {
|
||||
List<InsnNode> chain = flattenInsnChain(insn);
|
||||
int constrIndex = -1; //RAF
|
||||
// Case where new StringBuilder() is called with NO args (the entire
|
||||
// string is created using .append() calls:
|
||||
if (chain.size() > 1 && chain.get(0).getType() == InsnType.CONSTRUCTOR) {
|
||||
constrIndex = 0;
|
||||
} else if (chain.size() > 2 && chain.get(1).getType() == InsnType.CONSTRUCTOR) {
|
||||
//RAF Case where the first string element is String arg to the
|
||||
// new StringBuilder("xxx") constructor
|
||||
constrIndex = 1;
|
||||
} else if (chain.size() > 3 && chain.get(2).getType() == InsnType.CONSTRUCTOR) {
|
||||
//RAF Case where the first string element is String.valueOf() arg
|
||||
// to the new StringBuilder(String.valueOf(zzz)) constructor
|
||||
constrIndex = 2;
|
||||
}
|
||||
|
||||
if (constrIndex != -1) { // If we found a CONSTRUCTOR, is it a StringBuilder?
|
||||
ConstructorInsn constr = (ConstructorInsn) chain.get(constrIndex);
|
||||
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)) {
|
||||
int len = chain.size(), argInd = 1;
|
||||
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len-1);
|
||||
InsnNode argInsn;
|
||||
if (constrIndex > 0) { // There was an arg to the StringBuilder constr
|
||||
InsnWrapArg iwa;
|
||||
if (constrIndex==2
|
||||
&& (argInsn = chain.get(1)).getType()==InsnType.INVOKE
|
||||
&& ((InvokeNode)argInsn).getCallMth().getName().compareTo("valueOf")==0) {
|
||||
// The argument of new StringBuilder() is a String.valueOf(chainElement0)
|
||||
iwa = (InsnWrapArg)argInsn.getArg(0);
|
||||
argInd = 3; // Cause for loop below to skip to after the constructor
|
||||
} else {
|
||||
InsnNode firstNode = chain.get(0);
|
||||
if (firstNode instanceof ConstStringNode) {
|
||||
ConstStringNode csn = (ConstStringNode) firstNode;
|
||||
iwa = new InsnWrapArg(csn);
|
||||
argInd = 2; // Cause for loop below to skip to after the constructor
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
concatInsn.addArg(iwa);
|
||||
}
|
||||
if (constrIndex != -1) { // If we found a CONSTRUCTOR, is it a StringBuilder?
|
||||
ConstructorInsn constr = (ConstructorInsn) chain.get(constrIndex);
|
||||
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)) {
|
||||
int len = chain.size(), argInd = 1;
|
||||
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1);
|
||||
InsnNode argInsn;
|
||||
if (constrIndex > 0) { // There was an arg to the StringBuilder constr
|
||||
InsnWrapArg iwa;
|
||||
if (constrIndex == 2
|
||||
&& (argInsn = chain.get(1)).getType() == InsnType.INVOKE
|
||||
&& ((InvokeNode) argInsn).getCallMth().getName().compareTo("valueOf") == 0) {
|
||||
// The argument of new StringBuilder() is a String.valueOf(chainElement0)
|
||||
iwa = (InsnWrapArg) argInsn.getArg(0);
|
||||
argInd = 3; // Cause for loop below to skip to after the constructor
|
||||
} else {
|
||||
InsnNode firstNode = chain.get(0);
|
||||
if (firstNode instanceof ConstStringNode) {
|
||||
ConstStringNode csn = (ConstStringNode) firstNode;
|
||||
iwa = new InsnWrapArg(csn);
|
||||
argInd = 2; // Cause for loop below to skip to after the constructor
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
concatInsn.addArg(iwa);
|
||||
}
|
||||
|
||||
for (; argInd < len; argInd++) { // Add the .append(xxx) arg string to concat
|
||||
concatInsn.addArg(chain.get(argInd).getArg(1));
|
||||
}
|
||||
concatInsn.setResult(insn.getResult());
|
||||
return concatInsn;
|
||||
} // end of if constructor is for StringBuilder
|
||||
} // end of if we found a constructor early in the chain
|
||||
for (; argInd < len; argInd++) { // Add the .append(xxx) arg string to concat
|
||||
concatInsn.addArg(chain.get(argInd).getArg(1));
|
||||
}
|
||||
concatInsn.setResult(insn.getResult());
|
||||
return concatInsn;
|
||||
} // end of if constructor is for StringBuilder
|
||||
} // end of if we found a constructor early in the chain
|
||||
|
||||
} catch (Throwable e) {
|
||||
LOG.debug("Can't convert string concatenation: {} insn: {}", mth, insn, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
LOG.debug("Can't convert string concatenation: {} insn: {}", mth, insn, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static InsnNode simplifyArith(InsnNode insn) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ParserStream {
|
||||
|
||||
protected static final Charset STRING_CHARSET_UTF16 = Charset.forName("UTF-16LE");
|
||||
@@ -15,7 +17,7 @@ public class ParserStream {
|
||||
private final InputStream input;
|
||||
private long readPos = 0;
|
||||
|
||||
public ParserStream(InputStream inputStream) {
|
||||
public ParserStream(@NotNull InputStream inputStream) {
|
||||
this.input = inputStream;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.tests.api;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
import java.io.File;
|
||||
@@ -21,8 +20,7 @@ public class SmaliTest extends IntegrationTest {
|
||||
File smaliFile = getSmaliFile(clsName);
|
||||
File outDex = createTempFile(".dex");
|
||||
compileSmali(smaliFile, outDex);
|
||||
String fullClsName = Consts.DEFAULT_PACKAGE_NAME + "." + clsName;
|
||||
return getClassNodeFromFile(outDex, fullClsName);
|
||||
return getClassNodeFromFile(outDex, clsName);
|
||||
}
|
||||
|
||||
private static File getSmaliFile(String clsName) {
|
||||
|
||||
+35
-35
@@ -12,42 +12,42 @@ import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestSwitchReturnFromCase extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public void test(int a) {
|
||||
String s = null;
|
||||
if (a > 1000) {
|
||||
return;
|
||||
}
|
||||
switch (a % 4) {
|
||||
case 1:
|
||||
s = "1";
|
||||
break;
|
||||
case 2:
|
||||
s = "2";
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
s = "4";
|
||||
break;
|
||||
case 5:
|
||||
return;
|
||||
}
|
||||
s = "5";
|
||||
}
|
||||
}
|
||||
public static class TestCls {
|
||||
public void test(int a) {
|
||||
String s = null;
|
||||
if (a > 1000) {
|
||||
return;
|
||||
}
|
||||
switch (a % 4) {
|
||||
case 1:
|
||||
s = "1";
|
||||
break;
|
||||
case 2:
|
||||
s = "2";
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
s = "4";
|
||||
break;
|
||||
case 5:
|
||||
return;
|
||||
}
|
||||
s = "5";
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsString("switch (a % 4) {"));
|
||||
assertEquals(5, count(code, "case "));
|
||||
assertEquals(3, count(code, "break;"));
|
||||
assertThat(code, containsString("switch (a % 4) {"));
|
||||
assertEquals(5, count(code, "case "));
|
||||
assertEquals(3, count(code, "break;"));
|
||||
|
||||
assertThat(code, containsOne("s = \"1\";"));
|
||||
assertThat(code, containsOne("s = \"2\";"));
|
||||
assertThat(code, containsOne("s = \"4\";"));
|
||||
assertThat(code, containsOne("s = \"5\";"));
|
||||
}
|
||||
assertThat(code, containsOne("s = \"1\";"));
|
||||
assertThat(code, containsOne("s = \"2\";"));
|
||||
assertThat(code, containsOne("s = \"4\";"));
|
||||
assertThat(code, containsOne("s = \"5\";"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user