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

4

40

I'm developing an SDK (Android library), and I have to obfuscate a large part of my code so the customer may not try and play with internal code. My lib is coded in Kotlin, and I used Proguard to obfuscate the code. The problem is that there are still @kotlin.Metadata (runtime) annotations inside the code after compile and obfuscation. With those annotations, retrieving the Java code that originated this "(not-so)obfuscated" bytecode is easy.

I first thought it was my fault, and my project had too many entropy sources that might have induced this behavior, so I made a sample project to prove that the problem did not come from my SDK implementation. I created a new project with AS, and then a lib module with 2 files:

  • facade.kt is my facade class, the one that I do not wish to obfuscate so that the customer may use it:

      package com.example.mylibrary
    
      class MyFacade(val internalClass:InternalClass) {
    
         fun doSomething() {
            internalClass.doSomething(
                   firstArgument=1,
                   secondArgument=2
            )
          }
       }
    
  • and in this sample, internal.kt holds the classes that I want to obfuscate:

      package com.example.mylibrary
    
      class InternalClass {
          fun doSomething(firstArgument: Int, secondArgument: Int) {
              System.out.println("Arguments are : $firstArgument, $secondArgument")
          }
      }
    

The Proguard rules are injected into the gradle project with this release closure:

buildTypes {
    release {
        minifyEnabled true
        proguardFiles 'proguard-rules.pro'
    }
}

And here is proguard-rules.pro (only one line, nothing more) :

-keep class com.example.mylibrary.MyFacade {*;}

The result: when I ./gradlew clean myLib:assembleRelease, I do obtain an aar in which my facade is kept, and my internal class has been renamed in 'a', with one method 'a', except that the class is still annotated with Kotlin @Metadata, which holds every information that helps the decompiler retrieve the original class name, the method, attribute, and argument names, etc... So my code is not so obfuscated at all...

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

So my question: is it possible to get rid of those annotations, am I the only one facing this problem, or have I missed something?

Eats answered 14/9, 2017 at 12:58 Comment(8)
This sucks… Since you have removed (?) default Android *.pro file from proguardFiles, I suspect, that Kotlin Gradle plugin forces annotations to be kept somehow. You might have to use separate Proguard pass to strip those from your jars, — Proguard is just Java library/Ant task, so you can declare classpath dependency on it and use it yourself in custom Gradle task/Android Plugin transform.Stichter
@Stichter thanks for the suggestion, I did not suspect this might come from the kotlin-gradle plugin, and I'll try the separated proguard call trick.Eats
I checked this solution, and indeed annotations are removed. However this is not industrial, I had to rework my proguard files to have it work, and I have not tried to use the AAR at runtime yet...Eats
So now I checked at runtime, and this is what I expected... java.lang.AbstractMethodError: abstract method "java.lang.Object kotlin.jvm.functions.Function0.invoke()" I got cryptic errors like this one, which seems to be linked to the way java interacts with kotlin bytecode... I'm doomed.Eats
You might want to update the question with description of your recent attempts. Manually processing library files with Proguard is nontrivial task, you probably got your -libraryjars list wrong or something like that. Make sure, that you have both "-dontskip*" options in Proguard config!Stichter
Regarding the metadata, have you tried adding the ProGuard rule -keepattributes !*Metadata* ?Melon
@Melon I've tried it, but it didn't change the output.Trimetric
You should add your proguard file, to your gradle build configuration: developer.android.com/studio/buildSteinman
L
1

There is a plugin dedicated to this kind of request : https://github.com/oliver-jonas/unmeta

This plugin allows removing all Kotlin @Metadata / @DebugMetadata annotations from generated class files. This is safe to do as long as:

  • you do not intend to use the resulting binaries as a Kotlin library (@Metadata annotations are used to determine Kotlin function definitions),

  • you are not using Kotlin Reflection (certain reflection functionality depends on the presence of the @Metadata annotations).

Must be careful because when removing the metadata kotlin may your application or your library will not work.

Luckin answered 28/9, 2022 at 16:42 Comment(0)
C
0

Finally, I found a way to delete Kotlin metadata annotations.

In order to hide Kotlin metadata annotations, you need to enable R8 full mode.

Here is the information about my environment.

Environment

OS: macOS 10.15.1
Android Studio: 3.5.1
Gradle: 5.4.1
Android Gradle Tool: 3.5.2

What you have to do is just add properties to gradle.properties like below

gradle.properties

android.enableR8.fullMode=true

And here is my Proguard Rules

proguard-rules.pro

-dontwarn kotlin.**
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}

FYI, R8 full mode is still testing now, so sometimes it doesn't work well. However, for me, it works perfectly for now.

Copywriter answered 26/11, 2019 at 5:4 Comment(1)
The solution purposed by @Permassi is not working.Luckin
E
0

There is no better way to retrieve meta information due to issues with R8's proguard processing.I have to use the transformer api to remove it from the class.

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)
            }
        }
    }

}
Eye answered 2/8, 2023 at 11:57 Comment(0)
F
0

In one of my projects, I have used DexGuard, it obfuscates the code more deeply, it is an advanced version of Proguard.

Fool answered 29/11, 2023 at 9:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.