fix: check names from Kotlin metadata before use (#1364)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<EncodedValue> 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<EncodedValue> getParamAsList(IAnnotation annotation, String paramName) {
|
||||
if (annotation == null) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user