Android Software Reverse Engineering & Decompilation

Your First Kotlin RE Project: A Step-by-Step Walkthrough Decompiling an Android APK

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Kotlin Decompilation

Kotlin has rapidly become the preferred language for Android app development, lauded for its conciseness, safety features, and interoperability with existing Java codebases. This rise in popularity means that modern Android reverse engineering (RE) projects increasingly involve Kotlin bytecode. While the fundamental principles of Android APK analysis remain consistent, understanding Kotlin’s unique compilation artifacts and language constructs is crucial for effective decompilation.

This tutorial provides a detailed, step-by-step guide to decompiling a Kotlin-based Android application package (APK). We will cover the essential tools and techniques required to transform compiled Dalvik bytecode back into a human-readable, Java-like source code, enabling you to analyze its logic, identify potential vulnerabilities, or simply understand its internal workings. By the end of this walkthrough, you’ll have a solid foundation for your Kotlin RE endeavors.

Essential Tools for Your RE Toolkit

Before we begin, ensure you have the following tools installed and configured on your system. These are standard for Android reverse engineering and will be instrumental in our process:

  • Java Development Kit (JDK): Required for running Java-based tools. Ensure you have JDK 8 or later.
  • Apktool: An essential tool for unpacking (and repacking) APKs. It extracts resources, manifest files, and decompiles Dalvik bytecode (DEX) into Smali assembly language.
  • dex2jar: This utility converts Dalvik bytecode (.dex files) into Java Archive (.jar) files, making them compatible with Java decompilers.
  • JD-GUI / Luyten / Bytecode Viewer: These are Java decompilers. While they primarily target Java bytecode, they often perform remarkably well on Kotlin bytecode, producing readable Java/Kotlin-like source code. We’ll generally refer to JD-GUI, but Luyten and Bytecode Viewer are excellent alternatives.

Most of these tools are cross-platform (Windows, macOS, Linux). Ensure they are either in your system’s PATH or you know their executable locations.

Step 1: Acquiring the Target APK

The first step in any reverse engineering project is to obtain the target application. For this tutorial, you can use any Kotlin-based Android APK. Here are common ways to get one:

  • Build your own: Create a simple Kotlin Android app in Android Studio and build a release APK. This gives you full control and knowledge of the original source.
  • Extract from a device: If you have an Android device with the app installed, you can use adb pull to retrieve the APK. First, find the package name (e.g., com.example.myapp) using adb shell pm list packages -3. Then, find its path: adb shell pm path com.example.myapp. Finally, pull it: adb pull /data/app/com.example.myapp-XYZ.apk myapp.apk.
  • Download from repositories: Reputable sites like APKMirror or F-Droid offer APKs for various applications.

For the remainder of this guide, let’s assume your target APK is named MyKotlinApp.apk.

Step 2: Unpacking Resources and Smali with Apktool

Apktool is the Swiss Army knife for Android package analysis. It allows you to decode application resources to nearly original form and decompile classes.dex into Smali assembly code, a human-readable representation of Dalvik bytecode. This is often the first step in any static analysis.

Execute the following command in your terminal:

apktool d MyKotlinApp.apk -o MyKotlinApp_re

This command instructs apktool to decompile (d) MyKotlinApp.apk and output (-o) the results into a new directory named MyKotlinApp_re. Inside this directory, you will find:

  • AndroidManifest.xml: The application’s manifest file, defining its components, permissions, and structure.
  • res/: All application resources, including layouts, strings, images, and raw assets.
  • smali/: Directories containing Smali assembly code for the application’s bytecode. This is where the core logic resides, albeit in a low-level format.

While Smali is crucial for patching and deep analysis, our goal here is higher-level code.

Step 3: Converting DEX to JAR with Dex2jar

Android applications utilize Dalvik Executable (DEX) files, which contain bytecode optimized for the Dalvik/ART runtime, distinct from standard Java bytecode (.class files) found in JARs. To use conventional Java decompilers, we need to convert these .dex files into .jar files.

An APK can contain one or more .dex files (e.g., classes.dex, classes2.dex, etc., especially in larger apps employing multidex). First, extract these from the APK archive:

unzip MyKotlinApp.apk 'classes*.dex' -d temp_dex

This command extracts all files matching classes*.dex into a temporary directory called temp_dex.

Next, use dex2jar to convert each .dex file:

/path/to/dex2jar/d2j-dex2jar.sh temp_dex/classes.dex -o MyKotlinApp_classes.jar

If your APK has multiple DEX files, repeat the command for each (e.g., classes2.dex, classes3.dex). On Windows, you would typically use d2j-dex2jar.bat instead of .sh. This step might produce some warnings, but it usually completes successfully, generating one or more .jar files containing the Java bytecode equivalent.

Step 4: Decompiling JAR to Source Code with JD-GUI/Luyten

Now that we have the .jar files, we can use a Java decompiler to get human-readable source code. JD-GUI, Luyten, and Bytecode Viewer are excellent choices.

Launch your preferred decompiler. For JD-GUI, if it’s a standalone JAR, you might run it like this:

java -jar /path/to/jd-gui-1.x.x.jar

Once the decompiler’s interface is open, navigate to File > Open File (or equivalent) and select MyKotlinApp_classes.jar (and any other generated JARs). The decompiler will process the bytecode and display a tree-like structure of packages, classes, and methods. Clicking on any class will reveal its decompiled source code in a pane.

You will notice that the code looks like Java, but it retains many patterns and structures that hint at its Kotlin origin. The next step focuses on interpreting these.

Step 5: Understanding Kotlin-Specific Constructs in Decompiled Code

While the decompiler produces Java-like syntax, recognizing the tell-tale signs of original Kotlin constructs is crucial for accurate analysis. Kotlin often generates specific bytecode patterns that translate uniquely into Java.

Data Classes

Kotlin’s data class automatically generates boilerplate methods like equals(), hashCode(), toString(), copy(), and componentN() functions (for destructuring declarations). In decompiled Java, these methods will be explicitly visible, making a class much more verbose than a simple Java POJO.

Original Kotlin example:

data class User(val name: String, val age: Int)

Simplified decompiled Java representation:

public final class User {  @NotNull private final String name;  private final int age;  @NotNull public final String getName() { return this.name; }  public final int getAge() { return this.age; }  public User(@NotNull String name, int age) { /* constructor logic */ }  // Methods like equals, hashCode, toString, copy, component1, component2 will be present}

Extension Functions

Kotlin extension functions are compiled into static methods within a synthetic utility class. This class is often named OriginalClassNameKt (e.g., StringExtensionKt), and the receiver object (the object on which the extension is called) is passed as the first argument to this static method.

Original Kotlin example:

fun String.addExclamation(): String = this + "!"

Simplified decompiled Java representation:

public final class StringExtensionKt {  @NotNull public static final String addExclamation(@NotNull String $this$addExclamation) {    Intrinsics.checkNotNullParameter($this$addExclamation, "$this$addExclamation");    return $this$addExclamation + "!";  }}

Nullability and Intrinsics

Kotlin enforces strict nullability at compile time. In the decompiled Java, you’ll frequently see calls to kotlin.jvm.internal.Intrinsics.checkNotNullParameter() or checkExpressionValueIsNotNull(). These indicate that the corresponding variable or parameter in the original Kotlin code was non-nullable.

Coroutines

Kotlin Coroutines compile into complex state machines, often involving anonymous classes and callback interfaces. Decompiling coroutine-heavy code can be challenging, as it often results in a significant amount of boilerplate Java code that obfuscates the original asynchronous logic.

Object (Singleton) Classes

A Kotlin object declaration (which creates a singleton instance) compiles to a final Java class with a static INSTANCE field that holds the single instance of the class.

Original Kotlin example:

object MySingleton {  fun doSomething() { /* ... */ }}

Simplified decompiled Java representation:

public final class MySingleton {  @NotNull public static final MySingleton INSTANCE;  private MySingleton() { /* private constructor */ }  public final void doSomething() { /* ... */ }  static {    MySingleton var0 = new MySingleton();    INSTANCE = var0;  }}

Step 6: Rebuilding (Optional but Insightful)

If you’ve made modifications to the Smali code (from Step 2) and want to test your changes, you can rebuild the APK using apktool. This is common in patching or behavior modification scenarios.

Rebuild the APK from your modified directory:

apktool b MyKotlinApp_re -o MyKotlinApp_modified.apk

The rebuilt APK, MyKotlinApp_modified.apk, needs to be signed before it can be installed on an Android device. You can use a debug key:

# Generate a debug keystore (if you don't have one)keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000# Sign the APKjarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore debug.keystore MyKotlinApp_modified.apk androiddebugkey# Align the APK (essential for Android performance and installation)zipalign -v 4 MyKotlinApp_modified.apk MyKotlinApp_modified_aligned.apk

The final file, MyKotlinApp_modified_aligned.apk, can now be installed on an Android device using adb install.

Conclusion

Decompiling Kotlin Android APKs is a vital skill for anyone delving into mobile security, competitive analysis, or simply understanding how an application works under the hood. While Kotlin’s modern features introduce certain bytecode complexities, the established toolchain of Apktool, dex2jar, and Java decompilers provides a robust and effective pipeline to reverse engineer compiled applications.

By following this step-by-step guide and learning to recognize Kotlin-specific patterns in the decompiled Java output, you gain a powerful capability to analyze, understand, and even modify Android applications. This walkthrough provides a solid foundation, opening the door to deeper static and dynamic analysis techniques in your ongoing Kotlin RE journey.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner