我正在开发一个 SDK(Android 库),我必须混淆大部分代码,这样客户就不会尝试使用内部代码。 我的 lib 是用 Kotlin 编写的,我使用 Proguard 来混淆代码。问题在于,经过编译和混淆后,代码中仍然存在@kotlin.Metadata(运行时)注释。有了这些注释,检索生成这个“(不那么)混淆的”字节码的 Java 代码就很容易了。
我首先认为这是我的错,我的项目有太多的熵源可能会引发这种行为,所以我做了一个示例项目来证明问题不是来自我的 SDK 实现。 我用 AS 创建了一个新项目,然后创建了一个包含 2 个文件的 lib 模块:
facade.kt 是我的门面类,我不想混淆它以便客户可以使用它:
package com.example.mylibrary
class MyFacade(val internalClass:InternalClass) {
fun doSomething() {
internalClass.doSomething(
firstArgument=1,
secondArgument=2
)
}
}
在此示例中,internal.kt 包含我想要混淆的类:
package com.example.mylibrary
class InternalClass {
fun doSomething(firstArgument: Int, secondArgument: Int) {
System.out.println("Arguments are : $firstArgument, $secondArgument")
}
}
Proguard 规则通过此发布关闭注入到 gradle 项目中:
buildTypes {
release {
minifyEnabled true
proguardFiles 'proguard-rules.pro'
}
}
这是
proguard-rules.pro
(只有一行,仅此而已):
-keep class com.example.mylibrary.MyFacade {*;}
结果:当我
./gradlew clean myLib:assembleRelease
时,我确实获得了一个aar,其中保留了我的外观,并且我的内部类已在'a'中重命名,使用一种方法'a',除了该类仍然用Kotlin注释@Metadata,它保存有助于反编译器检索原始类名、方法、属性和参数名称等的所有信息...
所以我的代码一点也不那么混乱......
@Metadata(
mv = {1, 1, 7},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\b\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0016\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u00062\u0006\u0010\u0007\u001a\u00020\u0006¨\u0006\b"},
d2 = {"Lcom/example/mylibrary/InternalClass;", "", "()V", "doSomething", "", "firstArgument", "", "secondArgument", "mylibrary_release"}
)
public final class a {
...
}
所以我的问题:是否可以去掉这些注释,我是唯一面临这个问题的人,还是我错过了一些东西?
终于找到了删除Kotlin元数据注释的方法。
为了隐藏 Kotlin 元数据注释,您需要启用 R8 完整模式。
这是有关我的环境的信息。
环境
OS: macOS 10.15.1
Android Studio: 3.5.1
Gradle: 5.4.1
Android Gradle Tool: 3.5.2
您要做的只是向
gradle.properties
添加属性,如下所示
gradle.属性
android.enableR8.fullMode=true
这是我的 Proguard 规则
proguard-rules.pro
-dontwarn kotlin.**
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}
仅供参考,R8 完整模式现在仍在测试中,因此有时效果不佳。然而,对我来说,它现在工作得很好。
有一些插件专门用于此类请求:https://github.com/oliver-jonas/unmeta
此插件允许从生成的类文件中删除所有 Kotlin @Metadata / @DebugMetadata 注释。只要满足以下条件,这样做就是安全的:
您不打算将生成的二进制文件用作 Kotlin 库(@Metadata 注释用于确定 Kotlin 函数定义),
您没有使用 Kotlin Reflection(某些反射功能取决于 @Metadata 注释的存在)。
必须小心,因为删除 kotlin 元数据时,您的应用程序或您的库可能无法工作。
由于 R8 的 proguard 处理问题,没有更好的方法来检索元信息。我必须使用转换器 api 将其从类中删除。
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.Opcodes
class XClassVisitor(cv: ClassVisitor, val name: String) : ClassVisitor(Opcodes.ASM9, cv), Opcodes {
private var modified = false
private val mapping = XGuardRecorder.getMapping()
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor? {
val key = name.substringBefore(".")
if (!mapping.containsKey(key)) {
return super.visitAnnotation(desc, visible)
}
return when (desc) {
"Lkotlin/Metadata;" -> {
println("Removed @Metadata annotation from $key")
modified = true
null
}
"Lkotlin/coroutines/jvm/internal/DebugMetadata;" -> {
println("Removed @DebugMetadata annotation from $key")
modified = true
null
}
else -> {
super.visitAnnotation(desc, visible)
}
}
}
}