fix: resolved regression in Kotlin metadata parser
This commit is contained in:
@@ -28,6 +28,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.kotlin.KotlinMetadataUtils;
|
||||
|
||||
public class Deobfuscator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
||||
@@ -36,9 +37,6 @@ public class Deobfuscator {
|
||||
|
||||
public static final String CLASS_NAME_SEPARATOR = ".";
|
||||
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||
public static final String KOTLIN_METADATA_ANNOTATION = "kotlin.Metadata";
|
||||
public static final String KOTLIN_METADATA_D2_PARAMETER = "d2";
|
||||
public static final String KOTLIN_METADATA_CLASSNAME_REGEX = "(L.*;)";
|
||||
|
||||
private final JadxArgs args;
|
||||
private final RootNode root;
|
||||
@@ -351,9 +349,8 @@ public class Deobfuscator {
|
||||
} else {
|
||||
if (!clsMap.containsKey(classInfo)) {
|
||||
String clsShortName = classInfo.getShortName();
|
||||
if (shouldRename(clsShortName) || reservedClsNames.contains(clsShortName)) {
|
||||
makeClsAlias(cls);
|
||||
}
|
||||
boolean badName = shouldRename(clsShortName) || reservedClsNames.contains(clsShortName);
|
||||
makeClsAlias(cls, badName);
|
||||
}
|
||||
}
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
@@ -366,7 +363,7 @@ public class Deobfuscator {
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getAlias();
|
||||
}
|
||||
return makeClsAlias(cls);
|
||||
return makeClsAlias(cls, true);
|
||||
}
|
||||
|
||||
public String getPkgAlias(ClassNode cls) {
|
||||
@@ -387,41 +384,35 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
private String makeClsAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
|
||||
String metadataClassName = "";
|
||||
String metadataPackageName = "";
|
||||
private String makeClsAlias(ClassNode cls, boolean badName) {
|
||||
String alias = null;
|
||||
String pkgName = null;
|
||||
if (this.parseKotlinMetadata) {
|
||||
String rawClassName = getRawClassNameFromMetadata(cls);
|
||||
if (rawClassName != null) {
|
||||
metadataClassName = rawClassName.substring(rawClassName.lastIndexOf(".") + 1, rawClassName.length() - 1);
|
||||
if (rawClassName.lastIndexOf(".") != -1) {
|
||||
metadataPackageName = rawClassName.substring(1, rawClassName.lastIndexOf("."));
|
||||
}
|
||||
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
|
||||
if (kotlinCls != null) {
|
||||
alias = prepareNameFull(kotlinCls.getShortName(), "C");
|
||||
pkgName = kotlinCls.getPackage();
|
||||
}
|
||||
}
|
||||
String alias = null;
|
||||
|
||||
if (this.useSourceNameAsAlias) {
|
||||
if (alias == null && this.useSourceNameAsAlias) {
|
||||
alias = getAliasFromSourceFile(cls);
|
||||
}
|
||||
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (alias == null) {
|
||||
if (metadataClassName.isEmpty()) {
|
||||
if (badName) {
|
||||
String clsName = classInfo.getShortName();
|
||||
String prefix = makeClsPrefix(cls);
|
||||
alias = String.format("%sC%04d%s", prefix, clsIndex++, prepareNamePart(clsName));
|
||||
} else {
|
||||
alias = metadataClassName;
|
||||
// rename not needed
|
||||
return classInfo.getShortName();
|
||||
}
|
||||
}
|
||||
PackageNode pkg;
|
||||
if (metadataPackageName.isEmpty()) {
|
||||
pkg = getPackageNode(classInfo.getPackage(), true);
|
||||
} else {
|
||||
pkg = getPackageNode(metadataPackageName, true);
|
||||
if (pkgName == null) {
|
||||
pkgName = classInfo.getPackage();
|
||||
}
|
||||
PackageNode pkg = getPackageNode(pkgName, true);
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
return alias;
|
||||
}
|
||||
@@ -484,29 +475,6 @@ public class Deobfuscator {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get class name form Kotlin meta data
|
||||
*
|
||||
* @param cls
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private String getRawClassNameFromMetadata(ClassNode cls) {
|
||||
if (cls.getAnnotation(KOTLIN_METADATA_ANNOTATION) != null
|
||||
&& cls.getAnnotation(KOTLIN_METADATA_ANNOTATION).getValues().get(KOTLIN_METADATA_D2_PARAMETER) != null
|
||||
&& cls.getAnnotation(KOTLIN_METADATA_ANNOTATION).getValues().get(KOTLIN_METADATA_D2_PARAMETER) instanceof List) {
|
||||
Object rawClassNameObject =
|
||||
((List) cls.getAnnotation(KOTLIN_METADATA_ANNOTATION).getValues().get(KOTLIN_METADATA_D2_PARAMETER)).get(0);
|
||||
if (rawClassNameObject instanceof String) {
|
||||
String rawClassName = ((String) rawClassNameObject).trim().replace("/", ".");
|
||||
if (rawClassName.length() > 1 && rawClassName.matches(KOTLIN_METADATA_CLASSNAME_REGEX)) {
|
||||
return rawClassName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getAliasFromSourceFile(ClassNode cls) {
|
||||
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
||||
@@ -628,6 +596,24 @@ 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());
|
||||
}
|
||||
|
||||
private void dumpClassAlias(ClassNode cls) {
|
||||
PackageNode pkg = getPackageNode(cls.getPackage(), false);
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package jadx.core.utils.kotlin;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
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.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
// TODO: parse data from d1 (protobuf encoded) to get original method names and other useful info
|
||||
public class KotlinMetadataUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KotlinMetadataUtils.class);
|
||||
|
||||
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) {
|
||||
IAnnotation metadataAnnotation = cls.getAnnotation(KOTLIN_METADATA_ANNOTATION);
|
||||
List<EncodedValue> d2Param = getParamAsList(metadataAnnotation, KOTLIN_METADATA_D2_PARAMETER);
|
||||
if (d2Param == null || d2Param.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
EncodedValue firstValue = d2Param.get(0);
|
||||
if (firstValue == null || firstValue.getType() != EncodedType.ENCODED_STRING) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String rawClassName = ((String) firstValue.getValue()).trim();
|
||||
if (rawClassName.matches(KOTLIN_METADATA_CLASSNAME_REGEX)) {
|
||||
return ClassInfo.fromName(cls.root(), rawClassName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to parse kotlin metadata", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<EncodedValue> getParamAsList(IAnnotation annotation, String paramName) {
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
EncodedValue encodedValue = annotation.getValues().get(paramName);
|
||||
if (encodedValue == null || encodedValue.getType() != EncodedType.ENCODED_ARRAY) {
|
||||
return null;
|
||||
}
|
||||
return (List<EncodedValue>) encodedValue.getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package jadx.tests.integration.deobf;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestKotlinMetadata extends SmaliTest {
|
||||
// @formatter:off
|
||||
/*
|
||||
@file:JvmName("TestKotlinMetadata")
|
||||
class TestMetaData {
|
||||
|
||||
@JvmField
|
||||
val id = 1
|
||||
|
||||
@JvmName("makeTwo")
|
||||
fun double(x: Int): Int {
|
||||
return 2 * x
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
prepareArgs(true);
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.containsOne("class TestMetaData {");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreMetadata() {
|
||||
prepareArgs(false);
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.containsOne("class C0000TestKotlinMetadata {");
|
||||
}
|
||||
|
||||
private void prepareArgs(boolean parseKotlinMetadata) {
|
||||
enableDeobfuscation();
|
||||
args.setDeobfuscationMinLength(100); // rename everything
|
||||
getArgs().setParseKotlinMetadata(parseKotlinMetadata);
|
||||
disableCompilation();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
.class public final Ldeobf/TestKotlinMetadata;
|
||||
.super Ljava/lang/Object;
|
||||
.source "TestMetaData.kt"
|
||||
|
||||
|
||||
# annotations
|
||||
.annotation runtime Lkotlin/Metadata;
|
||||
bv = {
|
||||
0x1,
|
||||
0x0,
|
||||
0x3
|
||||
}
|
||||
d1 = {
|
||||
"\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;",
|
||||
"",
|
||||
"()V",
|
||||
"id",
|
||||
"",
|
||||
"double",
|
||||
"x",
|
||||
"makeTwo",
|
||||
"test"
|
||||
}
|
||||
k = 0x1
|
||||
mv = {
|
||||
0x1,
|
||||
0x4,
|
||||
0x0
|
||||
}
|
||||
.end annotation
|
||||
|
||||
|
||||
# instance fields
|
||||
.field public final id:I
|
||||
.annotation build Lkotlin/jvm/JvmField;
|
||||
.end annotation
|
||||
.end field
|
||||
|
||||
|
||||
# direct methods
|
||||
.method public constructor <init>()V
|
||||
.registers 2
|
||||
|
||||
.prologue
|
||||
.line 4
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
.line 7
|
||||
const/4 v0, 0x1
|
||||
|
||||
iput v0, p0, Ldeobf/TestKotlinMetadata;->id:I
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
|
||||
# virtual methods
|
||||
.method public final makeTwo(I)I
|
||||
.registers 3
|
||||
.param p1, "x" # I
|
||||
.annotation build Lkotlin/jvm/JvmName;
|
||||
name = "makeTwo"
|
||||
.end annotation
|
||||
|
||||
.prologue
|
||||
.line 11
|
||||
mul-int/lit8 v0, p1, 0x2
|
||||
|
||||
return v0
|
||||
.end method
|
||||
Reference in New Issue
Block a user