fix: improve rename checks and show rename reason (#584)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
@@ -106,7 +106,7 @@ public class JadxCLIArgs {
|
||||
+ " 'case' for system case sensitivity,"
|
||||
+ " 'valid' for java identifiers,"
|
||||
+ " 'printable' characters,"
|
||||
+ " 'none' or 'all'",
|
||||
+ " 'none' or 'all' (default)",
|
||||
converter = RenameConverter.class
|
||||
)
|
||||
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||
@@ -181,7 +181,7 @@ public class JadxCLIArgs {
|
||||
args.setThreadsCount(threadsCount);
|
||||
args.setSkipSources(skipSources);
|
||||
if (singleClass != null) {
|
||||
args.setClassFilter((className) -> singleClass.equals(className));
|
||||
args.setClassFilter(className -> singleClass.equals(className));
|
||||
}
|
||||
args.setSkipResources(skipResources);
|
||||
args.setFallbackMode(fallbackMode);
|
||||
@@ -303,38 +303,14 @@ public class JadxCLIArgs {
|
||||
return renameFlags.contains(RenameEnum.CASE);
|
||||
}
|
||||
|
||||
public void setRenameCaseSensitive(boolean renameCase) {
|
||||
if (renameCase && !isRenameCaseSensitive()) {
|
||||
renameFlags.add(RenameEnum.CASE);
|
||||
} else if (!renameCase && isRenameCaseSensitive()) {
|
||||
renameFlags.remove(RenameEnum.CASE);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRenameValid() {
|
||||
return renameFlags.contains(RenameEnum.VALID);
|
||||
}
|
||||
|
||||
public void setRenameValid(boolean renameValid) {
|
||||
if (renameValid && !isRenameValid()) {
|
||||
renameFlags.add(RenameEnum.VALID);
|
||||
} else if (!renameValid && isRenameValid()) {
|
||||
renameFlags.remove(RenameEnum.VALID);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRenamePrintable() {
|
||||
return renameFlags.contains(RenameEnum.PRINTABLE);
|
||||
}
|
||||
|
||||
public void setRenamePrintable(boolean renamePrintable) {
|
||||
if (renamePrintable && !isRenamePrintable()) {
|
||||
renameFlags.add(RenameEnum.PRINTABLE);
|
||||
} else if (!renamePrintable && isRenamePrintable()) {
|
||||
renameFlags.remove(RenameEnum.PRINTABLE);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFsCaseSensitive() {
|
||||
return fsCaseSensitive;
|
||||
}
|
||||
@@ -348,23 +324,23 @@ public class JadxCLIArgs {
|
||||
|
||||
@Override
|
||||
public Set<RenameEnum> convert(String value) {
|
||||
Set<RenameEnum> set = new HashSet<>();
|
||||
if (value.equalsIgnoreCase("NONE")) {
|
||||
return EnumSet.noneOf(RenameEnum.class);
|
||||
}
|
||||
if (value.equalsIgnoreCase("ALL")) {
|
||||
set.add(RenameEnum.CASE);
|
||||
set.add(RenameEnum.VALID);
|
||||
set.add(RenameEnum.PRINTABLE);
|
||||
} else if (!value.equalsIgnoreCase("NONE")) {
|
||||
for (String s : value.split(",")) {
|
||||
try {
|
||||
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
String values = "'" + RenameEnum.CASE
|
||||
+ "', '" + RenameEnum.VALID
|
||||
+ "' and '" + RenameEnum.PRINTABLE + '\'';
|
||||
throw new IllegalArgumentException(
|
||||
s + " is unknown for parameter " + paramName
|
||||
+ ", possible values are " + values.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
return EnumSet.allOf(RenameEnum.class);
|
||||
}
|
||||
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
|
||||
for (String s : value.split(",")) {
|
||||
try {
|
||||
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
String values = Arrays.stream(RenameEnum.values())
|
||||
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
|
||||
.collect(Collectors.joining(", "));
|
||||
throw new IllegalArgumentException(
|
||||
'\'' + s + "' is unknown for parameter " + paramName
|
||||
+ ", possible values are " + values);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
|
||||
@@ -42,8 +42,8 @@ public class RenameConverterTest {
|
||||
() -> converter.convert("wrong"),
|
||||
"Expected convert() to throw, but it didn't");
|
||||
|
||||
assertEquals("wrong is unknown for parameter someParam, "
|
||||
+ "possible values are 'case', 'valid' and 'printable'",
|
||||
assertEquals("'wrong' is unknown for parameter someParam, "
|
||||
+ "possible values are 'case', 'valid', 'printable'",
|
||||
thrown.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public final class ResourcesLoader {
|
||||
return ResContainer.textResource(rf.getName(), content);
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser().decodeFiles(inputStream);
|
||||
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return decodeImage(rf, inputStream);
|
||||
|
||||
@@ -32,7 +32,7 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
|
||||
import jadx.core.utils.CodegenUtils;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
@@ -104,7 +104,7 @@ public class ClassGen {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
CodegenUtils.addComments(code, cls);
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
insertDecompilationProblems(code, cls);
|
||||
addClassDeclaration(code);
|
||||
addClassBody(code);
|
||||
@@ -299,7 +299,7 @@ public class ClassGen {
|
||||
}
|
||||
code.add(';');
|
||||
} else {
|
||||
CodegenUtils.addComments(code, mth);
|
||||
CodeGenUtils.addComments(code, mth);
|
||||
insertDecompilationProblems(code, mth);
|
||||
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
||||
if (badCode && showInconsistentCode) {
|
||||
@@ -356,11 +356,12 @@ public class ClassGen {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
CodegenUtils.addComments(code, f);
|
||||
CodeGenUtils.addComments(code, f);
|
||||
annotationGen.addForField(code, f);
|
||||
|
||||
if (f.getFieldInfo().isRenamed()) {
|
||||
code.startLine("/* renamed from: ").add(f.getName()).add(" */");
|
||||
code.newLine();
|
||||
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
||||
}
|
||||
code.startLine(f.getAccessFlags().makeString());
|
||||
useType(code, f.getType());
|
||||
@@ -620,7 +621,7 @@ public class ClassGen {
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (classInfo.hasAlias()) {
|
||||
code.startLine("/* renamed from: ").add(classInfo.getType().getObject()).add(" */");
|
||||
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
@@ -84,7 +85,7 @@ public class MethodGen {
|
||||
}
|
||||
|
||||
if (mth.getMethodInfo().isRenamed() && !ai.isConstructor()) {
|
||||
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
|
||||
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
||||
}
|
||||
code.startLineWithNum(mth.getSourceLine());
|
||||
code.add(ai.makeString());
|
||||
|
||||
@@ -129,7 +129,7 @@ public class NameGen {
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
varName = varName + 'R';
|
||||
}
|
||||
if (!NameMapper.isValidIdentifier(varName)) {
|
||||
if (!NameMapper.isValidAndPrintable(varName)) {
|
||||
varName = getFallbackName(var);
|
||||
}
|
||||
return varName;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -64,11 +63,6 @@ public class Deobfuscator {
|
||||
private int fldIndex = 0;
|
||||
private int mthIndex = 0;
|
||||
|
||||
@Deprecated
|
||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
|
||||
this(args, dexNodes, deobfMapFile.toPath());
|
||||
}
|
||||
|
||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
|
||||
this.args = args;
|
||||
this.dexNodes = dexNodes;
|
||||
@@ -86,8 +80,20 @@ public class Deobfuscator {
|
||||
initIndexes();
|
||||
}
|
||||
process();
|
||||
}
|
||||
|
||||
public void savePresets() {
|
||||
deobfPresets.save(args.isDeobfuscationForceSave());
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
deobfPresets.clear();
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
private void initIndexes() {
|
||||
@@ -148,16 +154,6 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
deobfPresets.clear();
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
private void resolveOverriding(MethodNode mth) {
|
||||
Set<ClassNode> clsParents = new LinkedHashSet<>();
|
||||
collectClassHierarchy(mth.getParentClass(), clsParents);
|
||||
@@ -241,7 +237,7 @@ public class Deobfuscator {
|
||||
} else if (!clsInfo.isInner()) {
|
||||
// check if package renamed
|
||||
PackageNode pkgNode = getPackageNode(clsInfo.getPackage(), false);
|
||||
if (pkgNode.hasAnyAlias()) {
|
||||
if (pkgNode != null && pkgNode.hasAnyAlias()) {
|
||||
clsInfo.changePkg(pkgNode.getFullAlias());
|
||||
}
|
||||
}
|
||||
@@ -259,7 +255,7 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
public void renameField(FieldNode field) {
|
||||
private void renameField(FieldNode field) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = getFieldAlias(field);
|
||||
if (alias != null) {
|
||||
@@ -271,7 +267,7 @@ public class Deobfuscator {
|
||||
field.getFieldInfo().setAlias(makeFieldAlias(field));
|
||||
}
|
||||
|
||||
public void renameMethod(MethodNode mth) {
|
||||
private void renameMethod(MethodNode mth) {
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
mth.getMethodInfo().setAlias(alias);
|
||||
@@ -408,7 +404,7 @@ public class Deobfuscator {
|
||||
} else if (name.endsWith(".kt")) {
|
||||
name = name.substring(0, name.length() - ".kt".length());
|
||||
}
|
||||
if (!NameMapper.isValidIdentifier(name) || !NameMapper.isAllCharsPrintable(name)) {
|
||||
if (!NameMapper.isValidAndPrintable(name)) {
|
||||
return null;
|
||||
}
|
||||
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
||||
@@ -500,8 +496,7 @@ public class Deobfuscator {
|
||||
|
||||
private boolean shouldRename(String s) {
|
||||
int len = s.length();
|
||||
return len < minLength || len > maxLength
|
||||
|| !NameMapper.isValidIdentifier(s);
|
||||
return len < minLength || len > maxLength;
|
||||
}
|
||||
|
||||
private String prepareNamePart(String name) {
|
||||
|
||||
@@ -87,6 +87,10 @@ public class NameMapper {
|
||||
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches();
|
||||
}
|
||||
|
||||
public static boolean isValidAndPrintable(String str) {
|
||||
return isValidIdentifier(str) && isAllCharsPrintable(str);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifierStart(int codePoint) {
|
||||
return Character.isJavaIdentifierStart(codePoint);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
@@ -55,6 +56,7 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
|
||||
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
||||
|
||||
// method
|
||||
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
|
||||
public class RenameReasonAttr implements IAttribute {
|
||||
|
||||
private String description;
|
||||
|
||||
public RenameReasonAttr() {
|
||||
this.description = "";
|
||||
}
|
||||
|
||||
public RenameReasonAttr(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public RenameReasonAttr(AttrNode node) {
|
||||
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
||||
if (renameReasonAttr != null) {
|
||||
this.description = renameReasonAttr.description;
|
||||
} else {
|
||||
this.description = "";
|
||||
}
|
||||
}
|
||||
|
||||
public RenameReasonAttr(AttrNode node, boolean notValid, boolean notPrintable) {
|
||||
this(node);
|
||||
if (notValid) {
|
||||
notValid();
|
||||
}
|
||||
if (notPrintable) {
|
||||
notPrintable();
|
||||
}
|
||||
}
|
||||
|
||||
private RenameReasonAttr notValid() {
|
||||
return append("not valid java name");
|
||||
}
|
||||
|
||||
private RenameReasonAttr notPrintable() {
|
||||
return append("contains not printable characters");
|
||||
}
|
||||
|
||||
public RenameReasonAttr append(String reason) {
|
||||
if (description.isEmpty()) {
|
||||
description += reason;
|
||||
} else {
|
||||
description += " and " + reason;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<RenameReasonAttr> getType() {
|
||||
return AType.RENAME_REASON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RENAME_REASON:" + description;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ public class RootNode {
|
||||
}
|
||||
try {
|
||||
ResourceStorage resStorage = ResourcesLoader.decodeStream(arsc, (size, is) -> {
|
||||
ResTableParser parser = new ResTableParser();
|
||||
ResTableParser parser = new ResTableParser(this);
|
||||
parser.decode(is);
|
||||
return parser.getResStorage();
|
||||
});
|
||||
|
||||
@@ -212,17 +212,19 @@ public class ClassModifier extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean removeBridgeMethod(ClassNode cls, MethodNode mth) {
|
||||
List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
|
||||
if (allInsns.size() == 1) {
|
||||
InsnNode wrappedInsn = allInsns.get(0);
|
||||
if (wrappedInsn.getType() == InsnType.RETURN) {
|
||||
InsnArg arg = wrappedInsn.getArg(0);
|
||||
if (arg.isInsnWrap()) {
|
||||
wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (cls.root().getArgs().isRenameValid()) {
|
||||
List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
|
||||
if (allInsns.size() == 1) {
|
||||
InsnNode wrappedInsn = allInsns.get(0);
|
||||
if (wrappedInsn.getType() == InsnType.RETURN) {
|
||||
InsnArg arg = wrappedInsn.getArg(0);
|
||||
if (arg.isInsnWrap()) {
|
||||
wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
}
|
||||
}
|
||||
if (checkSyntheticWrapper(mth, wrappedInsn)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (checkSyntheticWrapper(mth, wrappedInsn)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return !isMethodUnique(cls, mth);
|
||||
|
||||
@@ -152,8 +152,8 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
String name = getConstString(cls.dex(), co.getArg(0));
|
||||
if (name != null
|
||||
&& !fieldInfo.getAlias().equals(name)
|
||||
&& NameMapper.isValidIdentifier(name)) {
|
||||
// LOG.debug("Rename enum field: '{}' to '{}' in {}", fieldInfo.getName(), name, cls);
|
||||
&& NameMapper.isValidAndPrintable(name)
|
||||
&& cls.root().getArgs().isRenameValid()) {
|
||||
fieldInfo.setAlias(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import jadx.core.Consts;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -24,8 +25,6 @@ import jadx.core.utils.files.InputFile;
|
||||
|
||||
public class RenameVisitor extends AbstractVisitor {
|
||||
|
||||
private Deobfuscator deobfuscator;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
List<DexNode> dexNodes = root.getDexNodes();
|
||||
@@ -36,24 +35,29 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
Path inputFilePath = firstInputFile.getFile().toPath();
|
||||
|
||||
String inputName = inputFilePath.getFileName().toString();
|
||||
inputName = inputName.substring(0, inputName.lastIndexOf('.'));
|
||||
String baseName = inputName.substring(0, inputName.lastIndexOf('.'));
|
||||
Path deobfMapPath = inputFilePath.getParent().resolve(baseName + ".jobf");
|
||||
|
||||
Path deobfMapPath = inputFilePath.getParent().resolve(inputName + ".jobf");
|
||||
JadxArgs args = root.getArgs();
|
||||
deobfuscator = new Deobfuscator(args, dexNodes, deobfMapPath);
|
||||
boolean deobfuscationOn = args.isDeobfuscationOn();
|
||||
if (deobfuscationOn) {
|
||||
Deobfuscator deobfuscator = new Deobfuscator(args, dexNodes, deobfMapPath);
|
||||
if (args.isDeobfuscationOn()) {
|
||||
deobfuscator.execute();
|
||||
}
|
||||
checkClasses(root, args);
|
||||
|
||||
checkClasses(deobfuscator, root, args);
|
||||
|
||||
if (args.isDeobfuscationOn()) {
|
||||
deobfuscator.savePresets();
|
||||
deobfuscator.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkClasses(RootNode root, JadxArgs args) {
|
||||
private static void checkClasses(Deobfuscator deobfuscator, RootNode root, JadxArgs args) {
|
||||
List<ClassNode> classes = root.getClasses(true);
|
||||
for (ClassNode cls : classes) {
|
||||
checkClassName(cls, args);
|
||||
checkFields(cls, args);
|
||||
checkMethods(cls, args);
|
||||
checkClassName(deobfuscator, cls, args);
|
||||
checkFields(deobfuscator, cls, args);
|
||||
checkMethods(deobfuscator, cls, args);
|
||||
}
|
||||
if (!args.isFsCaseSensitive() && args.isRenameCaseSensitive()) {
|
||||
Set<String> clsFullPaths = new HashSet<>(classes.size());
|
||||
@@ -62,20 +66,22 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
if (!clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase())) {
|
||||
String newShortName = deobfuscator.getClsAlias(cls);
|
||||
clsInfo.changeShortName(newShortName);
|
||||
cls.addAttr(new RenameReasonAttr(cls).append("case insensitive filesystem"));
|
||||
clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
processRootPackages(root, classes);
|
||||
processRootPackages(deobfuscator, root, classes);
|
||||
}
|
||||
|
||||
private void checkClassName(ClassNode cls, JadxArgs args) {
|
||||
private static void checkClassName(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String clsName = classInfo.getAliasShortName();
|
||||
|
||||
String newShortName = fixClsShortName(args, clsName);
|
||||
if (!newShortName.equals(clsName)) {
|
||||
classInfo.changeShortName(newShortName);
|
||||
cls.addAttr(new RenameReasonAttr(cls).append("invalid class name"));
|
||||
}
|
||||
if (args.isRenameValid()) {
|
||||
if (classInfo.isInner()) {
|
||||
@@ -84,6 +90,7 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
if (parentClass.getAliasShortName().equals(clsName)) {
|
||||
String clsAlias = deobfuscator.getClsAlias(cls);
|
||||
classInfo.changeShortName(clsAlias);
|
||||
cls.addAttr(new RenameReasonAttr(cls).append("collision with other inner class name"));
|
||||
break;
|
||||
}
|
||||
parentClass = parentClass.getParentClass();
|
||||
@@ -91,12 +98,13 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
} else {
|
||||
if (classInfo.getAliasPkg().isEmpty()) {
|
||||
classInfo.changePkg(Consts.DEFAULT_PACKAGE_NAME);
|
||||
cls.addAttr(new RenameReasonAttr(cls).append("default package"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String fixClsShortName(JadxArgs args, String clsName) {
|
||||
private static String fixClsShortName(JadxArgs args, String clsName) {
|
||||
char firstChar = clsName.charAt(0);
|
||||
boolean renameValid = args.isRenameValid();
|
||||
if (Character.isDigit(firstChar) && renameValid) {
|
||||
@@ -114,25 +122,33 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
return cleanClsName;
|
||||
}
|
||||
|
||||
private void checkFields(ClassNode cls, JadxArgs args) {
|
||||
private static void checkFields(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) {
|
||||
Set<String> names = new HashSet<>();
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String fieldName = fieldInfo.getAlias();
|
||||
if (!names.add(fieldName)
|
||||
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(fieldName))
|
||||
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(fieldName))) {
|
||||
boolean notUnique = !names.add(fieldName);
|
||||
boolean notValid = args.isRenameValid() && !NameMapper.isValidIdentifier(fieldName);
|
||||
boolean notPrintable = args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(fieldName);
|
||||
if (notUnique || notValid || notPrintable) {
|
||||
deobfuscator.forceRenameField(field);
|
||||
field.addAttr(new RenameReasonAttr(field, notValid, notPrintable));
|
||||
if (notUnique) {
|
||||
field.addAttr(new RenameReasonAttr(field).append("collision with other field name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMethods(ClassNode cls, JadxArgs args) {
|
||||
private static void checkMethods(Deobfuscator deobfuscator, ClassNode cls, JadxArgs args) {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
String alias = mth.getAlias();
|
||||
if (args.isRenameValid() && !NameMapper.isValidIdentifier(alias)
|
||||
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(alias))) {
|
||||
|
||||
boolean notValid = args.isRenameValid() && !NameMapper.isValidIdentifier(alias);
|
||||
boolean notPrintable = args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(alias);
|
||||
if (notValid || notPrintable) {
|
||||
deobfuscator.forceRenameMethod(mth);
|
||||
mth.addAttr(new RenameReasonAttr(mth, notValid, notPrintable));
|
||||
}
|
||||
}
|
||||
Set<String> names = new HashSet<>();
|
||||
@@ -147,11 +163,12 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
String signature = mth.getMethodInfo().makeSignature(true, false);
|
||||
if (!names.add(signature)) {
|
||||
deobfuscator.forceRenameMethod(mth);
|
||||
mth.addAttr(new RenameReasonAttr("collision with other method in class"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processRootPackages(RootNode root, List<ClassNode> classes) {
|
||||
private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) {
|
||||
Set<String> rootPkgs = collectRootPkgs(classes);
|
||||
root.getCacheStorage().setRootPkgs(rootPkgs);
|
||||
|
||||
@@ -161,6 +178,7 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (rootPkgs.contains(field.getAlias())) {
|
||||
deobfuscator.forceRenameField(field);
|
||||
field.addAttr(new RenameReasonAttr("collision with root package name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
|
||||
}
|
||||
} else {
|
||||
if (NameMapper.isValidIdentifier(varName)) {
|
||||
if (NameMapper.isValidAndPrintable(varName)) {
|
||||
ssaVar.setName(varName);
|
||||
}
|
||||
detachDebugInfo(ssaVar.getAssign());
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
|
||||
public class CodeGenUtils {
|
||||
|
||||
public static void addComments(CodeWriter code, AttrNode node) {
|
||||
List<String> comments = node.getAll(AType.COMMENTS);
|
||||
if (!comments.isEmpty()) {
|
||||
comments.stream().distinct()
|
||||
.forEach(comment -> code.startLine("/* ").addMultiLine(comment).add(" */"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void addRenamedComment(CodeWriter code, AttrNode node, String origName) {
|
||||
code.startLine("/* renamed from: ").add(origName);
|
||||
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
||||
if (renameReasonAttr != null) {
|
||||
code.add(" reason: ");
|
||||
code.add(renameReasonAttr.getDescription());
|
||||
}
|
||||
code.add(" */");
|
||||
}
|
||||
|
||||
private CodeGenUtils() {
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
|
||||
public class CodegenUtils {
|
||||
|
||||
public static void addComments(CodeWriter code, AttrNode node) {
|
||||
List<String> comments = node.getAll(AType.COMMENTS);
|
||||
if (!comments.isEmpty()) {
|
||||
comments.stream().distinct()
|
||||
.forEach(comment -> code.startLine("/* ").addMultiLine(comment).add(" */"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,8 @@ public class AndroidResourcesUtils {
|
||||
FieldNode fieldNode = resFieldsMap.get(resource.getId());
|
||||
if (fieldNode != null
|
||||
&& !fieldNode.getName().equals(resName)
|
||||
&& NameMapper.isValidIdentifier(resName)) {
|
||||
&& NameMapper.isValidAndPrintable(resName)
|
||||
&& resCls.root().getArgs().isRenameValid()) {
|
||||
fieldNode.add(AFlag.DONT_RENAME);
|
||||
fieldNode.getFieldInfo().setAlias(resName);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
break;
|
||||
case RES_STRING_POOL_TYPE:
|
||||
strings = parseStringPoolNoType();
|
||||
valuesParser = new ValuesParser(strings, resNames);
|
||||
valuesParser = new ValuesParser(rootNode, strings, resNames);
|
||||
break;
|
||||
case RES_XML_RESOURCE_MAP_TYPE:
|
||||
parseResourceMap();
|
||||
|
||||
@@ -11,6 +11,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.xmlgen.entry.EntryConfig;
|
||||
import jadx.core.xmlgen.entry.RawNamedValue;
|
||||
import jadx.core.xmlgen.entry.RawValue;
|
||||
@@ -50,8 +53,13 @@ public class ResTableParser extends CommonBinaryParser {
|
||||
}
|
||||
}
|
||||
|
||||
private String[] strings;
|
||||
private final RootNode root;
|
||||
private final ResourceStorage resStorage = new ResourceStorage();
|
||||
private String[] strings;
|
||||
|
||||
public ResTableParser(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public void decode(InputStream inputStream) throws IOException {
|
||||
is = new ParserStream(inputStream);
|
||||
@@ -62,7 +70,7 @@ public class ResTableParser extends CommonBinaryParser {
|
||||
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
|
||||
decode(inputStream);
|
||||
|
||||
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
|
||||
ValuesParser vp = new ValuesParser(root, strings, resStorage.getResourcesNames());
|
||||
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
|
||||
|
||||
CodeWriter content = makeXmlDump();
|
||||
@@ -222,7 +230,13 @@ public class ResTableParser extends CommonBinaryParser {
|
||||
String typeName = pkg.getTypeStrings()[typeId - 1];
|
||||
String keyName = pkg.getKeyStrings()[key];
|
||||
if (keyName.isEmpty()) {
|
||||
keyName = "RES_" + resRef; // autogenerate key name
|
||||
FieldNode constField = root.getConstValues().getGlobalConstFields().get(resRef);
|
||||
if (constField != null) {
|
||||
keyName = constField.getName();
|
||||
constField.add(AFlag.DONT_RENAME);
|
||||
} else {
|
||||
keyName = "RES_" + resRef; // autogenerate key name
|
||||
}
|
||||
}
|
||||
ResourceEntry ri = new ResourceEntry(resRef, pkg.getName(), typeName, keyName);
|
||||
ri.setConfig(config);
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.xmlgen.ParserConstants;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
@@ -25,22 +26,22 @@ public class ValuesParser extends ParserConstants {
|
||||
private final String[] strings;
|
||||
private final Map<Integer, String> resMap;
|
||||
|
||||
public ValuesParser(String[] strings, Map<Integer, String> resMap) {
|
||||
public ValuesParser(RootNode root, String[] strings, Map<Integer, String> resMap) {
|
||||
this.strings = strings;
|
||||
this.resMap = resMap;
|
||||
|
||||
if (androidStrings == null && androidResMap == null) {
|
||||
try {
|
||||
decodeAndroid();
|
||||
decodeAndroid(root);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to decode Android Resource file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void decodeAndroid() throws IOException {
|
||||
private static void decodeAndroid(RootNode root) throws IOException {
|
||||
InputStream inputStream = new BufferedInputStream(ValuesParser.class.getResourceAsStream("/resources.arsc"));
|
||||
ResTableParser androidParser = new ResTableParser();
|
||||
ResTableParser androidParser = new ResTableParser(root);
|
||||
androidParser.decode(inputStream);
|
||||
androidStrings = androidParser.getStrings();
|
||||
androidResMap = androidParser.getResStorage().getResourcesNames();
|
||||
|
||||
@@ -278,6 +278,14 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.deobfuscationUseSourceNameAsAlias = deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public void updateRenameFlag(JadxArgs.RenameEnum flag, boolean enabled) {
|
||||
if (enabled) {
|
||||
renameFlags.add(flag);
|
||||
} else {
|
||||
renameFlags.remove(flag);
|
||||
}
|
||||
}
|
||||
|
||||
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||
this.escapeUnicode = escapeUnicode;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import say.swing.JFontChooser;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
@@ -184,21 +185,21 @@ public class JadxSettingsWindow extends JDialog {
|
||||
JCheckBox renameCaseSensitive = new JCheckBox();
|
||||
renameCaseSensitive.setSelected(settings.isRenameCaseSensitive());
|
||||
renameCaseSensitive.addItemListener(e -> {
|
||||
settings.setRenameCaseSensitive(e.getStateChange() == ItemEvent.SELECTED);
|
||||
settings.updateRenameFlag(JadxArgs.RenameEnum.CASE, e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox renameValid = new JCheckBox();
|
||||
renameValid.setSelected(settings.isRenameValid());
|
||||
renameValid.addItemListener(e -> {
|
||||
settings.setRenameValid(e.getStateChange() == ItemEvent.SELECTED);
|
||||
settings.updateRenameFlag(JadxArgs.RenameEnum.VALID, e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox renamePrintable = new JCheckBox();
|
||||
renamePrintable.setSelected(settings.isRenamePrintable());
|
||||
renamePrintable.addItemListener(e -> {
|
||||
settings.setRenamePrintable(e.getStateChange() == ItemEvent.SELECTED);
|
||||
settings.updateRenameFlag(JadxArgs.RenameEnum.PRINTABLE, e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user