如何混淆使用 Kotlin 编码的 SDK(并摆脱元数据)

How can I obfuscate my SDK coded with Kotlin (and get rid of Metadata)

提问人:AlexG 提问时间:9/14/2017 最后编辑:oolyviAlexG 更新时间:10/18/2023 访问量:4473

问:

我正在开发一个 SDK(Android 库),我必须对大部分代码进行混淆,这样客户就不会尝试使用内部代码。 我的库是用 Kotlin 编码的,我使用 Proguard 对代码进行混淆。问题是还有@kotlin。编译和混淆后代码中的元数据(运行时)注释。有了这些注解,检索产生这个“(不那么)混淆”字节码的 Java 代码就很容易了。

我一开始以为是我的错,我的项目有太多的熵源,可能诱发了这种行为,所以我做了一个示例项目来证明问题不是来自我的 SDK 实现。 我用 AS 创建了一个新项目,然后创建了一个包含 2 个文件的 lib 模块:

  • façade.kt 是我的 facade 类,我不想混淆以便客户可以使用它:

      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 {*;}

结果:当我 ,我确实获得了一个 aar,其中保留了我的 facade,并且我的内部类已重命名为“a”,使用一个方法“a”,除了该类仍然使用 Kotlin @Metadata 进行注释,它包含帮助反编译器检索原始类名、方法、属性和参数名称的所有信息, 等。。。 所以我的代码根本没有那么混淆......./gradlew clean myLib:assembleRelease

@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 {
    ...
}

所以我的问题是:是否有可能摆脱这些注释,我是唯一一个面临这个问题的人,还是我错过了什么?

Android Kotlin 元数据 Proguard

评论

0赞 user1643723 9/17/2017
这很糟糕......由于您已经从 中删除了 (?) 默认 Android 文件,我怀疑 Kotlin Gradle 插件会以某种方式强制保留注释。您可能需要使用单独的 Proguard 通行证来将它们从 jar 中剥离出来,因为 Proguard 只是 Java 库/Ant 任务,因此您可以声明对它的依赖关系,并在自定义 Gradle 任务/Android 插件转换中自行使用它。*.proproguardFilesclasspath
0赞 AlexG 9/18/2017
@user1643723感谢您的建议,我并不怀疑这可能来自 kotlin-gradle 插件,我将尝试单独的 proguard 调用技巧。
0赞 AlexG 9/18/2017
我检查了这个解决方案,确实删除了注释。但是这不是工业性的,我不得不重新设计我的 proguard 文件才能使其工作,而且我还没有尝试在运行时使用 AAR......
2赞 AlexG 9/18/2017
所以现在我在运行时检查,这就是我所期望的...... 我遇到了像这样的神秘错误,这似乎与 java 与 kotlin 字节码的交互方式有关......我注定要失败。java.lang.AbstractMethodError: abstract method "java.lang.Object kotlin.jvm.functions.Function0.invoke()"
0赞 user1643723 9/19/2017
您可能需要使用您最近尝试的描述来更新问题。使用 Proguard 手动处理库文件是一项艰巨的任务,您可能弄错了列表或类似的东西。确保您在 Proguard 配置中同时拥有“-dontskip*”选项!-libraryjars

答:

0赞 Pemassi 11/26/2019 #1

最后,我找到了一种删除 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.properties

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 完整模式现在仍在测试中,因此有时效果不佳。但是,对我来说,它目前非常有效。

评论

0赞 Sofien Rahmouni 9/29/2022
@Permassi 提出的解决方案不起作用。
0赞 Sofien Rahmouni 9/29/2022 #2

有一些插件专门用于这种请求:https://github.com/oliver-jonas/unmeta

此插件允许从生成的类文件中删除所有 Kotlin @Metadata / @DebugMetadata 注释。只要满足以下条件,就可以安全地执行此操作:

  • 您不打算将生成的二进制文件用作 Kotlin 库(@Metadata注释用于确定 Kotlin 函数定义),

  • 您没有使用 Kotlin 反射(某些反射功能取决于@Metadata注释的存在)。

必须小心,因为在删除元数据时,kotlin 可能会使您的应用程序或库无法正常工作。

0赞 haoxiqiang 8/2/2023 #3

由于 R8 的 proguard 处理存在问题,没有更好的方法来检索元信息。我必须使用 transformer 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)
            }
        }
    }

}
0赞 Rahul Sharma 11/29/2023 #4

在我的一个项目中,我使用了 DexGuard,它更深入地混淆了代码,它是 Proguard 的高级版本。