May 31, 2020

    Why Mobile Developers Must Better Protect their Kotlin Apps—And How to Do It

    Kotlin is now the primary programming language for Android development. Its success can be chalked up to core design principles that support modern programming paradigms, like functional programming with lambdas and asynchronous programming with coroutines. The Kotlin compiler injects code and metadata into the classes it generates to work around the limitations of the JVM and the Android platform. While this workaround effectively allows to bypass said limitations, it also has negative side effects on the security of the generated code: the injected metadata exposes potentially sensitive information.

    Guardsquare’s solutions for Android, DexGuard and the open source ProGuard optimizer, both provide security functionality for Kotlin mobile apps: ProGuard provides basic protection against reverse engineering, while DexGuard offers complete, layered mobile app security. This helps developers take advantage of the many benefits of Kotlin as a programming language without falling prey to security pitfalls.

    In this blog post, we’ll take a look at the potential security pitfalls of Kotlin-developed apps and explain how to improve security without negatively impacting performance.

    Kotlin Security Issue: Leaking Original Names

    The metadata injected by the Kotlin compiler takes the shape of an annotation added to classes. The Kotlin compiler uses its presence to distinguish between Java classes and Kotlin classes. If a class has the metadata annotation, it is treated as a Kotlin class and all features are available. Otherwise, the class is treated as a plain Java class and only what’s in the generated bytecode is available to use.

    In order to model all additional Kotlin features, the metadata annotation exposes a complete API representation of the original Kotlin class in a custom format. This poses a dilemma for developers. The metadata is required to support certain Kotlin features but, at the same time, it leaks information about the original class. 

    To see how much information is leaked, take a look at the example Kotlin class below and its associated metadata as printed by our new Kotlin Metadata Printer:

    class Greeter(var greeting: String) {
    
    	fun getMessage(addressee: String) = "$greeting, $addressee!"
    
    	fun setHello() {
        	    greeting = "Hello"
    	}
    }
    

    Snippet 1: A simple Kotlin class

    class Greeter(greeting: String) {
    
        // Properties
    
        var greeting: String
            // backing field: java.lang.String greeting
            get // getter method: public final java.lang.String getGreeting()
            set() // setter method: public final void setGreeting(java.lang.String)
    
        // Functions
    
        fun getMessage(addressee: String): String { }
        fun setHello() { }
    }

    Snippet 2: Disassembled Kotlin metadata

    When applying its layered mobile app security, DexGuard processes Koltlin metadata alongside the rest of the code to minimize overhead and eliminate unnecessary exposure of information. As of the newest release, ProGuard supports Kotlin as well. As an example, after obfuscating the class above, the metadata is adapted to use the obfuscated values:

    class Greeter(p0: String) {
    
        // Properties
    
        var ɩ: String
            // backing field: java.lang.String Ι
            get // getter method: private java.lang.String ı()
            set() // setter method: private void ı(java.lang.String)
    
        // Functions
    
        fun ι(p0: String): String { }
        fun ɩ() { }
    }

    Snippet 3: Disassembled Kotlin metadata after Pro/DexGuard

    On the Importance of Metadata

    DexGuard, like many other obfuscators, had previously taken the approach of completely removing the metadata from generated classes. While this ensures no original class or member names remain in a final application (ideal for security), it also throws away information that may be necessary and causes classes to be treated as plain Java (not ideal for development). There are two main use cases that will not work without correct metadata:

    1. The Kotlin-reflect library cannot find obfuscated Kotlin classes/members in the metadata at runtime.
    2. The Kotlin compiler cannot support Kotlin-specific features like suspend functions, top-level functions, type aliases, etc, in libraries.

    Given the quick adoption of Kotlin by the developer community, simply discarding the metadata is no longer an acceptable solution. Popular libraries such as Jackson and MvRx rely on Kotlin reflection, which means that in the past large portions of the code had to be left untouched to ensure that the Kotlin metadata remained in sync with the compiled classes. This created potential security issues. The growing popularity of coroutines also calls for a need to preserve information about which functions are suspendable, which is only defined in the metadata. With our latest update, these challenges can be overcome without sacrificing security.

    Our Goal: Lower Configuration Effort, Increase Security 

    So how does this work in practice? When developing, teams can simply instruct DexGuard or ProGuard to process the Kotlin metadata by specifying the new rule -keepkotlinmetadata. Whether you’re developing a Kotlin SDK or using frameworks in your application that builds on kotlin-reflect, you don’t need to worry about leaking original class information through metadata anymore.

    We’re pleased to be able to offer developers a way to take advantage of all the robust features and benefits of Kotlin without forcing them to sacrifice security or performance in any way.

    Discover how Guardsquare provides industry-leading protection for mobile apps.

    Request Pricing

    Other posts you might be interested in