diff --git a/jadx-core/src/main/java/jadx/core/deobf/ClsAliasPair.java b/jadx-core/src/main/java/jadx/core/deobf/ClsAliasPair.java new file mode 100644 index 000000000..bdd7eb86c --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/deobf/ClsAliasPair.java @@ -0,0 +1,24 @@ +package jadx.core.deobf; + +public class ClsAliasPair { + private final String pkg; + private final String name; + + public ClsAliasPair(String pkg, String name) { + this.pkg = pkg; + this.name = name; + } + + public String getPkg() { + return pkg; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return pkg + '.' + name; + } +} 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 213d5ec96..09d0a728d 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -386,10 +386,10 @@ public class Deobfuscator { String alias = null; String pkgName = null; if (this.parseKotlinMetadata) { - ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls); + ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls); if (kotlinCls != null) { - alias = prepareNameFull(kotlinCls.getShortName(), "C"); - pkgName = kotlinCls.getPackage(); + alias = kotlinCls.getName(); + pkgName = kotlinCls.getPkg(); } } if (alias == null && this.useSourceNameAsAlias) { @@ -601,20 +601,6 @@ public class Deobfuscator { return NameMapper.removeInvalidCharsMiddle(name); } - private String prepareNameFull(String name, String prefix) { - if (name.length() > maxLength) { - return makeHashName(name, prefix); - } - String result = NameMapper.removeInvalidChars(name, prefix); - if (result.isEmpty()) { - return makeHashName(name, prefix); - } - if (NameMapper.isReserved(result)) { - return prefix + result; - } - return result; - } - private static String makeHashName(String name, String invalidPrefix) { return invalidPrefix + 'x' + Integer.toHexString(name.hashCode()); } 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 index 9d8be13d4..edfc45c20 100644 --- 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 @@ -6,6 +6,16 @@ import jadx.core.dex.attributes.AttrNode; public class RenameReasonAttr implements IJadxAttribute { + public static RenameReasonAttr forNode(AttrNode node) { + RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON); + if (renameReasonAttr != null) { + return renameReasonAttr; + } + RenameReasonAttr newAttr = new RenameReasonAttr(); + node.addAttr(newAttr); + return newAttr; + } + private String description; public RenameReasonAttr() { diff --git a/jadx-core/src/main/java/jadx/core/utils/kotlin/KotlinMetadataUtils.java b/jadx-core/src/main/java/jadx/core/utils/kotlin/KotlinMetadataUtils.java index faf850326..2eba05b0f 100644 --- a/jadx-core/src/main/java/jadx/core/utils/kotlin/KotlinMetadataUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/kotlin/KotlinMetadataUtils.java @@ -9,8 +9,12 @@ import org.slf4j.LoggerFactory; import jadx.api.plugins.input.data.annotations.EncodedType; import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.core.deobf.ClsAliasPair; +import jadx.core.deobf.NameMapper; +import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.ClassNode; +import jadx.core.utils.Utils; // TODO: parse data from d1 (protobuf encoded) to get original method names and other useful info public class KotlinMetadataUtils { @@ -18,13 +22,12 @@ public class KotlinMetadataUtils { private static final String KOTLIN_METADATA_ANNOTATION = "Lkotlin/Metadata;"; private static final String KOTLIN_METADATA_D2_PARAMETER = "d2"; - private static final String KOTLIN_METADATA_CLASSNAME_REGEX = "(L.*;)"; /** * Try to get class info from Kotlin Metadata annotation */ @Nullable - public static ClassInfo getClassName(ClassNode cls) { + public static ClsAliasPair getClassAlias(ClassNode cls) { IAnnotation metadataAnnotation = cls.getAnnotation(KOTLIN_METADATA_ANNOTATION); List d2Param = getParamAsList(metadataAnnotation, KOTLIN_METADATA_D2_PARAMETER); if (d2Param == null || d2Param.isEmpty()) { @@ -36,8 +39,14 @@ public class KotlinMetadataUtils { } try { String rawClassName = ((String) firstValue.getValue()).trim(); - if (rawClassName.matches(KOTLIN_METADATA_CLASSNAME_REGEX)) { - return ClassInfo.fromName(cls.root(), rawClassName); + if (rawClassName.isEmpty()) { + return null; + } + String clsName = Utils.cleanObjectName(rawClassName); + ClsAliasPair alias = splitAndCheckClsName(cls, clsName); + if (alias != null) { + RenameReasonAttr.forNode(cls).append("from Kotlin metadata"); + return alias; } } catch (Exception e) { LOG.error("Failed to parse kotlin metadata", e); @@ -45,6 +54,54 @@ public class KotlinMetadataUtils { return null; } + // Don't use ClassInfo facility to not pollute class into cache + private static ClsAliasPair splitAndCheckClsName(ClassNode originCls, String fullClsName) { + if (!NameMapper.isValidFullIdentifier(fullClsName)) { + return null; + } + String pkg; + String name; + int dot = fullClsName.lastIndexOf('.'); + if (dot == -1) { + pkg = ""; + name = fullClsName; + } else { + pkg = fullClsName.substring(0, dot); + name = fullClsName.substring(dot + 1); + } + ClassInfo originClsInfo = originCls.getClassInfo(); + String originName = originClsInfo.getShortName(); + if (originName.equals(name) + || name.contains("$") + || !NameMapper.isValidIdentifier(name) + || countPkgParts(originClsInfo.getPackage()) != countPkgParts(pkg) + || pkg.startsWith("java.")) { + return null; + } + ClassNode newClsNode = originCls.root().resolveClass(fullClsName); + if (newClsNode != null) { + // class with alias name already exist + return null; + } + return new ClsAliasPair(pkg, name); + } + + private static int countPkgParts(String pkg) { + if (pkg.isEmpty()) { + return 0; + } + int count = 1; + int pos = 0; + while (true) { + pos = pkg.indexOf('.', pos); + if (pos == -1) { + return count; + } + pos++; + count++; + } + } + @SuppressWarnings("unchecked") private static List getParamAsList(IAnnotation annotation, String paramName) { if (annotation == null) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestKotlinMetadata.java b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestKotlinMetadata.java index 485a40fd9..c35dc206a 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestKotlinMetadata.java +++ b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestKotlinMetadata.java @@ -28,7 +28,8 @@ public class TestKotlinMetadata extends SmaliTest { prepareArgs(true); assertThat(getClassNodeFromSmali()) .code() - .containsOne("class TestMetaData {"); + .containsOne("class TestMetaData {") + .containsOne("reason: from Kotlin metadata"); } @Test @@ -42,6 +43,7 @@ public class TestKotlinMetadata extends SmaliTest { private void prepareArgs(boolean parseKotlinMetadata) { enableDeobfuscation(); args.setDeobfuscationMinLength(100); // rename everything + args.setDeobfuscationForceSave(true); getArgs().setParseKotlinMetadata(parseKotlinMetadata); disableCompilation(); } diff --git a/jadx-core/src/test/smali/deobf/TestKotlinMetadata.smali b/jadx-core/src/test/smali/deobf/TestKotlinMetadata.smali index bcfa4b443..58e66da7d 100644 --- a/jadx-core/src/test/smali/deobf/TestKotlinMetadata.smali +++ b/jadx-core/src/test/smali/deobf/TestKotlinMetadata.smali @@ -14,7 +14,7 @@ "\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\n\u0002\u0010\u0008\n\u0002\u0008\u0004\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002J\u0015\u0010\u0005\u001a\u00020\u00042\u0006\u0010\u0006\u001a\u00020\u0004H\u0007\u00a2\u0006\u0002\u0008\u0007R\u0010\u0010\u0003\u001a\u00020\u00048\u0006X\u0087D\u00a2\u0006\u0002\n\u0000\u00a8\u0006\u0008" } d2 = { - "LTestMetaData;", + "Ljadx/TestMetaData;", "", "()V", "id",