@@ -64,6 +64,9 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
|
||||
protected boolean deobfuscationForceSave = false;
|
||||
|
||||
@Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
@@ -242,4 +245,9 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useSourceNameAsClassAlias() {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,4 +68,9 @@ public class DefaultJadxArgs implements IJadxArgs {
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useSourceNameAsClassAlias() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,6 @@ public interface IJadxArgs {
|
||||
int getDeobfuscationMaxLength();
|
||||
|
||||
boolean isDeobfuscationForceSave();
|
||||
|
||||
boolean useSourceNameAsClassAlias();
|
||||
}
|
||||
|
||||
@@ -6,13 +6,17 @@ import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -40,11 +44,16 @@ public class Deobfuscator {
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
|
||||
|
||||
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<MethodInfo, OverridedMethodsNode>();
|
||||
private final List<OverridedMethodsNode> ovrd = new ArrayList<OverridedMethodsNode>();
|
||||
|
||||
private final PackageNode rootPackage = new PackageNode("");
|
||||
private final Set<String> pkgSet = new TreeSet<String>();
|
||||
|
||||
private final int maxLength;
|
||||
private final int minLength;
|
||||
private final boolean useSourceNameAsAlias;
|
||||
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
private int fldIndex = 0;
|
||||
@@ -56,6 +65,7 @@ public class Deobfuscator {
|
||||
|
||||
this.minLength = args.getDeobfuscationMinLength();
|
||||
this.maxLength = args.getDeobfuscationMaxLength();
|
||||
this.useSourceNameAsAlias = args.useSourceNameAsClassAlias();
|
||||
|
||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||
}
|
||||
@@ -95,6 +105,32 @@ public class Deobfuscator {
|
||||
processClass(dexNode, cls);
|
||||
}
|
||||
}
|
||||
postProcess();
|
||||
}
|
||||
|
||||
private void postProcess() {
|
||||
int id = 1;
|
||||
for (OverridedMethodsNode o : ovrd) {
|
||||
|
||||
Iterator<MethodInfo> it = o.getMethods().iterator();
|
||||
if (it.hasNext()) {
|
||||
MethodInfo mth = it.next();
|
||||
|
||||
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
|
||||
}
|
||||
String firstMethodAlias = mth.getAlias();
|
||||
|
||||
while (it.hasNext()) {
|
||||
mth = it.next();
|
||||
if (!mth.getAlias().equals(firstMethodAlias)) {
|
||||
mth.setAlias(firstMethodAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
@@ -102,6 +138,98 @@ public class Deobfuscator {
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClassNode resolveOverridingInternal(DexNode dex, ClassNode cls, String signature,
|
||||
Set<MethodInfo> overrideSet, ClassNode rootClass) {
|
||||
ClassNode result = null;
|
||||
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (m.getMethodInfo().getShortId().startsWith(signature)) {
|
||||
result = cls;
|
||||
if (!overrideSet.contains(m.getMethodInfo())) {
|
||||
overrideSet.add(m.getMethodInfo());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
ClassNode superNode = dex.resolveClass(superClass);
|
||||
if (superNode != null) {
|
||||
ClassNode clsWithMth = resolveOverridingInternal(dex, superNode, signature, overrideSet, rootClass);
|
||||
if (clsWithMth != null) {
|
||||
if ((result != null) && (result != cls)) {
|
||||
if (clsWithMth != result) {
|
||||
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
|
||||
signature,
|
||||
result.getFullName(), clsWithMth.getFullName(),
|
||||
rootClass.getFullName()));
|
||||
}
|
||||
} else {
|
||||
result = clsWithMth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ArgType iFaceType : cls.getInterfaces()) {
|
||||
ClassNode iFaceNode = dex.resolveClass(iFaceType);
|
||||
if (iFaceNode != null) {
|
||||
ClassNode clsWithMth = resolveOverridingInternal(dex, iFaceNode, signature, overrideSet, rootClass);
|
||||
if (clsWithMth != null) {
|
||||
if ((result != null) && (result != cls)) {
|
||||
if (clsWithMth != result) {
|
||||
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
|
||||
signature,
|
||||
result.getFullName(), clsWithMth.getFullName(),
|
||||
rootClass.getFullName()));
|
||||
}
|
||||
} else {
|
||||
result = clsWithMth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void resolveOverriding(DexNode dex, ClassNode cls, MethodNode mth) {
|
||||
Set<MethodInfo> overrideSet = new HashSet<MethodInfo>();
|
||||
resolveOverridingInternal(dex, cls, mth.getMethodInfo().makeSignature(false), overrideSet, cls);
|
||||
|
||||
if (overrideSet.size() > 1) {
|
||||
OverridedMethodsNode overrideNode = null;
|
||||
for (MethodInfo _mth : overrideSet) {
|
||||
if (ovrdMap.containsKey(_mth)) {
|
||||
overrideNode = ovrdMap.get(_mth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideNode == null) {
|
||||
overrideNode = new OverridedMethodsNode(overrideSet);
|
||||
ovrd.add(overrideNode);
|
||||
}
|
||||
|
||||
for (MethodInfo _mth : overrideSet) {
|
||||
if (!ovrdMap.containsKey(_mth)) {
|
||||
ovrdMap.put(_mth, overrideNode);
|
||||
if (!overrideNode.contains(_mth)) {
|
||||
overrideNode.add(_mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
overrideSet.clear();
|
||||
overrideSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(DexNode dex, ClassNode cls) {
|
||||
@@ -123,6 +251,10 @@ public class Deobfuscator {
|
||||
if (alias != null) {
|
||||
methodInfo.setAlias(alias);
|
||||
}
|
||||
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(dex, cls, mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +344,12 @@ public class Deobfuscator {
|
||||
|
||||
private String makeClsAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String alias = getAliasFromSourceFile(cls);
|
||||
String alias = null;
|
||||
|
||||
if (this.useSourceNameAsAlias) {
|
||||
alias = getAliasFromSourceFile(cls);
|
||||
}
|
||||
|
||||
if (alias == null) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
|
||||
@@ -269,6 +406,7 @@ public class Deobfuscator {
|
||||
alias = deobfPresets.getForMth(methodInfo);
|
||||
if (alias != null) {
|
||||
mthMap.put(methodInfo, alias);
|
||||
methodInfo.setAliasFromPreset(true);
|
||||
return alias;
|
||||
}
|
||||
if (shouldRename(mth.getName())) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/* package */ class OverridedMethodsNode {
|
||||
|
||||
private Set<MethodInfo> methods;
|
||||
|
||||
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
|
||||
methods = methodsSet;
|
||||
}
|
||||
|
||||
public boolean contains(MethodInfo mth) {
|
||||
return methods.contains(mth);
|
||||
}
|
||||
|
||||
public void add(MethodInfo mth) {
|
||||
methods.add(mth);
|
||||
}
|
||||
|
||||
public Set<MethodInfo> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,13 @@ public final class MethodInfo {
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
private String alias;
|
||||
private boolean aliasFromPreset;
|
||||
|
||||
private MethodInfo(DexNode dex, int mthIndex) {
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
name = dex.getString(mthId.getNameIndex());
|
||||
alias = name;
|
||||
aliasFromPreset = false;
|
||||
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||
|
||||
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||
@@ -109,6 +111,14 @@ public final class MethodInfo {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
public boolean isAliasFromPreset() {
|
||||
return aliasFromPreset;
|
||||
}
|
||||
|
||||
public void setAliasFromPreset(boolean value) {
|
||||
aliasFromPreset = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = declClass.hashCode();
|
||||
|
||||
@@ -86,10 +86,10 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
fields = new ArrayList<FieldNode>(fieldsCount);
|
||||
|
||||
for (Method mth : clsData.getDirectMethods()) {
|
||||
methods.add(new MethodNode(this, mth));
|
||||
methods.add(new MethodNode(this, mth, false));
|
||||
}
|
||||
for (Method mth : clsData.getVirtualMethods()) {
|
||||
methods.add(new MethodNode(this, mth));
|
||||
methods.add(new MethodNode(this, mth, true));
|
||||
}
|
||||
|
||||
for (Field f : clsData.getStaticFields()) {
|
||||
|
||||
@@ -56,6 +56,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
private int codeSize;
|
||||
private int debugInfoOffset;
|
||||
private boolean noCode;
|
||||
private boolean methodIsVirtual;
|
||||
|
||||
private ArgType retType;
|
||||
private RegisterArg thisArg;
|
||||
@@ -71,12 +72,13 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
private List<ExceptionHandler> exceptionHandlers = Collections.emptyList();
|
||||
private List<LoopInfo> loops = Collections.emptyList();
|
||||
|
||||
public MethodNode(ClassNode classNode, Method mthData) {
|
||||
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
|
||||
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
|
||||
this.parentClass = classNode;
|
||||
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
|
||||
this.noCode = mthData.getCodeOffset() == 0;
|
||||
this.methodData = noCode ? null : mthData;
|
||||
this.methodIsVirtual = isVirtual;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -538,6 +540,10 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isVirtual() {
|
||||
return methodIsVirtual;
|
||||
}
|
||||
|
||||
public int getRegsCount() {
|
||||
return regsCount;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,13 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
IJadxArgs args = root.getArgs();
|
||||
File deobfMapFile = new File(args.getOutDir(), "deobf_map.jobf");
|
||||
|
||||
final String firstInputFileName = root.getDexNodes().get(0).getInputFile().getFile().getAbsolutePath();
|
||||
final String inputPath = org.apache.commons.io.FilenameUtils.getFullPathNoEndSeparator(
|
||||
firstInputFileName);
|
||||
final String inputName = org.apache.commons.io.FilenameUtils.getBaseName(firstInputFileName);
|
||||
|
||||
File deobfMapFile = new File(inputPath, inputName + ".jobf");
|
||||
deobfuscator = new Deobfuscator(args, root.getDexNodes(), deobfMapFile);
|
||||
boolean deobfuscationOn = args.isDeobfuscationOn();
|
||||
if (deobfuscationOn) {
|
||||
|
||||
@@ -132,6 +132,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.deobfuscationForceSave = deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsAlias) {
|
||||
this.deobfuscationUseSourceNameAsAlias = useSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
if (fontStr.isEmpty()) {
|
||||
return DEFAULT_FONT;
|
||||
|
||||
@@ -141,11 +141,21 @@ public class JadxSettingsWindow extends JDialog {
|
||||
}
|
||||
});
|
||||
|
||||
JCheckBox deobfSourceAlias = new JCheckBox();
|
||||
deobfSourceAlias.setSelected(settings.useSourceNameAsClassAlias());
|
||||
deobfSourceAlias.addItemListener(new ItemListener() {
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
settings.setUseSourceNameAsClassAlias(e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
}
|
||||
});
|
||||
|
||||
SettingsGroup deobfGroup = new SettingsGroup(NLS.str("preferences.deobfuscation"));
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_on"), deobfOn);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_force"), deobfForce);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLen);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLen);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_source_alias"), deobfSourceAlias);
|
||||
deobfGroup.end();
|
||||
return deobfGroup;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ preferences.deobfuscation_on=Enable deobfuscation
|
||||
preferences.deobfuscation_force=Force rewrite deobfuscation map file
|
||||
preferences.deobfuscation_min_len=Minimum name length
|
||||
preferences.deobfuscation_max_len=Maximum name length
|
||||
preferences.deobfuscation_source_alias=Use source file name as class name alias
|
||||
preferences.save=Save
|
||||
preferences.cancel=Cancel
|
||||
|
||||
|
||||
Reference in New Issue
Block a user