35

I'm developing a 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. Problem is that there are still @kotlin.Metadata (runtime) annotations inside the code after compile and obfuscation. With those annotations, it's really easy to retrieve the java code that originated this "(not-so-)obfuscated" bytecode.

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

  • facade.kt is my facade class, the one that I do not wish to obfuscate, so 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 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?

Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
AlexG
  • 367
  • 3
  • 6
  • 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. – user1643723 Sep 17 '17 at 12:26
  • @user1643723 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. – AlexG Sep 18 '17 at 07:53
  • 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... – AlexG Sep 18 '17 at 08:40
  • 2
    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. – AlexG Sep 18 '17 at 08:52
  • 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! – user1643723 Sep 19 '17 at 01:47
  • Did you find a fix for this, in the meantime? Also facing the same issue. – cjurjiu Apr 19 '18 at 11:43
  • Regarding the metadata, have you tried adding the ProGuard rule `-keepattributes !*Metadata*` ? – Michael Jun 20 '18 at 15:29
  • @Michael I've tried it, but it didn't change the output. – stefana Jun 21 '18 at 08:21
  • You should add your proguard file, to your gradle build configuration: https://developer.android.com/studio/build/ – SamyB Jun 27 '18 at 08:48

3 Answers3

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.

Pemassi
  • 612
  • 1
  • 7
  • 20
0

There is some plugin dedicated for this kind of this 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.

Sofien Rahmouni
  • 4,354
  • 1
  • 21
  • 22
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)
            }
        }
    }

}
haoxiqiang
  • 359
  • 2
  • 4