diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 1ad92ba5e..55443a7a2 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -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 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 convert(String value) { - Set 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 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; diff --git a/jadx-cli/src/test/java/jadx/cli/RenameConverterTest.java b/jadx-cli/src/test/java/jadx/cli/RenameConverterTest.java index e8c1784e0..b20d7f00d 100644 --- a/jadx-cli/src/test/java/jadx/cli/RenameConverterTest.java +++ b/jadx-cli/src/test/java/jadx/cli/RenameConverterTest.java @@ -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()); } } diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java index 4c280119c..e6ba7e84b 100644 --- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java +++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index a523afb83..149581304 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -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()); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index e2d690ca1..ed1500fe0 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -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()); diff --git a/jadx-core/src/main/java/jadx/core/codegen/NameGen.java b/jadx-core/src/main/java/jadx/core/codegen/NameGen.java index b8dd224fe..b666d3f04 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/NameGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/NameGen.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java index 19a9357a6..9747d07ec 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -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 dexNodes, File deobfMapFile) { - this(args, dexNodes, deobfMapFile.toPath()); - } - public Deobfuscator(JadxArgs args, @NotNull List 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 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) { diff --git a/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java b/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java index bb13326f9..054413dee 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java +++ b/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index aaeccd901..57918c8af 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -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 { public static final AType DECLARE_VARIABLES = new AType<>(); public static final AType LOOP_LABEL = new AType<>(); public static final AType IGNORE_EDGE = new AType<>(); + public static final AType RENAME_REASON = new AType<>(); // method public static final AType LOCAL_VARS_DEBUG_INFO = new AType<>(); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java new file mode 100644 index 000000000..06f0ddbee --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java @@ -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 getType() { + return AType.RENAME_REASON; + } + + @Override + public String toString() { + return "RENAME_REASON:" + description; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 1b0c8ef7c..c25249520 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -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(); }); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 6bbde5741..9f4ac4587 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -212,17 +212,19 @@ public class ClassModifier extends AbstractVisitor { } private static boolean removeBridgeMethod(ClassNode cls, MethodNode mth) { - List 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 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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index f6cb41890..3c51f0f29 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java index 54f5d383e..518d9abc8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java @@ -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 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 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 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 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 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 classes) { + private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List classes) { Set 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")); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java index a59487f73..4a1673bf9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java @@ -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()); diff --git a/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java b/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java new file mode 100644 index 000000000..9289c14bb --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java @@ -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 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() { + } +} diff --git a/jadx-core/src/main/java/jadx/core/utils/CodegenUtils.java b/jadx-core/src/main/java/jadx/core/utils/CodegenUtils.java deleted file mode 100644 index 22af53fd9..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/CodegenUtils.java +++ /dev/null @@ -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 comments = node.getAll(AType.COMMENTS); - if (!comments.isEmpty()) { - comments.stream().distinct() - .forEach(comment -> code.startLine("/* ").addMultiLine(comment).add(" */")); - } - } -} diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java index 9c85299ed..1ba419c91 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java index c33269f65..2c4247215 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java @@ -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(); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java index df6080f6f..9cec2b21c 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java index bd8e51877..42b44010a 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java @@ -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 resMap; - public ValuesParser(String[] strings, Map resMap) { + public ValuesParser(RootNode root, String[] strings, Map 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(); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index c28a8237e..56284b594 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -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; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index d48fb80a6..140eedf28 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -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(); });