Published on

Reverse Engineering Android App

Author
  • avatar
    Name
    Karuppusamy
    Headline
    Developer

Introduction

In this post, I am going to show how to reverse engineer an Android app that requires an activation code based on the IMEI number to use it.

⚠️ Disclaimer: This article is just for educational purpose.

Target app

This app displays a primary key according to the IMEI number. The user needs to enter the correct activation code to activate the app. However, I am not going to expose the app name.

Decompile source code from APK

First download Jadx Decompiler from here.

Open the target APK with Jadx Decompiler. Now you will see the source code of the app. Export the source code of the app by navigating to File -> Save as gradle project.

jadx-decompiler

Alternatively, you can use a hosted solution of Jadx Decompiler like this. You just need to upload the apk and it will give a decompiled source in a zip file.

Find the activation code

Now open the saved project with your favourite code editor and search for the function which activates the app.

jadx-decompiler

In this case this.btnactivate.setOnClickListener(new View.OnClickListener() is the event listener of the activate button. So let's look at that and try to understand it's working.

activation.java
// Button OnClickListener
this.btnactivate.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        Activation activation = Activation.this;
        activation.straccode = activation.activationcode.getText().toString().trim();
        String prkey = Activation.primarykeyprinted;
        String stringimei = Activation.this.hexValue().trim();
        String imeikey = Activation.this.ReturnKey() + stringimei;
        Activation activation2 = Activation.this;
        activation2.strActccode = activation2.straccode;
        Activation activation3 = Activation.this;
        activation3.firsttimedecode = Activation.Decode(activation3.strActccode, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
        Activation activation4 = Activation.this;
        activation4.Secondtimedecode = Activation.Decode(activation4.firsttimedecode, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
        if (prkey.trim().length() <= 0 || imeikey.trim().length() <= 0 || Activation.this.strActccode.trim().length() <= 0) {
            Activation.this.PopupRegister("Example App", "Activation code is incorrect !", 0);
        } else if (prkey.equals(Activation.this.Secondtimedecode)) {
            SharedPreferences.Editor editor1 = Activation.this.getSharedPreferences("strActcKey", 0).edit();
            editor1.putString("strActcKey", Activation.this.Secondtimedecode);
            editor1.commit();
            Activation activation5 = Activation.this;
            activation5.activationcodestring = activation5.strActccode;
            SharedPreferences.Editor editor = Activation.this.getSharedPreferences("strActccode", 0).edit();
            editor.putString("strActccode", Activation.this.activationcodestring);
            editor.commit();
            Activation.this.sessionNew.createLoginSession();
            Activation.this.PopupRegister("Example App", "Example App is now Activated ", 1);
        } else {
            Activation.this.PopupRegister("Example App", "Activation code incorrect !", 0);
        }
    }
}

// Decode Function
public static String Decode(String inString, String strCodeString) {
    String RetString = inString;
    if (inString.length() > 0 && inString.length() <= 14) {
        int CutPos = strCodeString.indexOf(inString.substring(0, 1));
        String NewString = strCodeString.substring(CutPos + 1) + strCodeString.substring(0, CutPos + 1);
        RetString = "";
        for (int i = 1; i < inString.length(); i++) {
            String TmpChar = inString.substring(i, i + 1);
            int pos = (NewString.indexOf(TmpChar) - i) - 1;
            if (pos <= -1) {
                pos += strCodeString.length();
            }
            RetString = NewString.indexOf(TmpChar) > -1 ? RetString + strCodeString.charAt(pos) : RetString + TmpChar;
        }
    }
    return RetString;
}

Analyzing the code

If we look at the code, the user input is passed into the Decode() function two times, and the result is compared with the primary key. So the activation code is an encoded version of the primary key.

So here, we need to reverse the decode function to return the activation code for the given primary key.

At first, it may difficult to understand, but if you analyze the code line by line it's just a simple symmetric cipher algorithm.

activation-min.java
public void onClick(View v) {
    Activation activation = Activation.this;
    activation.straccode = activation.activationcode.getText().toString().trim(); // User Input ( Activation Code )
    String prkey = Activation.primarykeyprinted; // Primary Key

    // get IMEI Code
    String stringimei = Activation.this.hexValue().trim();
    String imeikey = Activation.this.ReturnKey() + stringimei;

    // Decode Activation code two times with Decode Function with Decryption String
    // Decryption String is 36 char long (A-Z + 0-9) and shuffled
    activation.strActivationCode = activation.straccode;
    activation.firstTimeDecode = Activation.Decode(activation.strActivationCode, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
    activation.secondTimeDecode = Activation.Decode(activation.firstTimeDecode, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");

    // Check if IMEI, Primary Key and Activation Code Exist
    if (prkey.trim().length() <= 0 || imeikey.trim().length() <= 0 || Activation.this.strActccode.trim().length() <= 0) {
        Activation.this.PopupRegister("Example App", "Activation code is incorrect !", 0);

    // If Second time Decoded String equal to Primary Key
    } else if (prkey.equals(Activation.this.secondTimeDecode)) {
        // App Activated Successfully
        Activation.this.PopupRegister("Example App", "Example App is now Activated ", 1);
    } else {
        Activation.this.PopupRegister("Example App", "Activation code incorrect !", 0);
    }
}

// Decode Function
public static String Decode(String str, String strCode) {
    String result = str;
    if (str.length() > 0 && str.length() <= 14) {
        int curPos = strCode.indexOf(str.substring(0, 1));
        String newString = strCode.substring(curPos + 1) + strCode.substring(0, curPos + 1);
        result = "";
        for (int i = 1; i < str.length(); i++) {
            String TmpChar = str.substring(i, i + 1);
            int pos = (newString.indexOf(TmpChar) - i) - 1;
            if (pos <= -1) {
                pos += strCode.length();
            }
            result = newString.indexOf(TmpChar) > -1 ? result + strCode.charAt(pos) : result + TmpChar;
        }
    }
    return result;
}

Reversing the code

Firstly, I am going to convert the code to JavaScript. So I can easily add breakpoints and run code line by line within the browser to test its working.

javascript-code.js
function Decode(str, strCode = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") {
  let result = str;
  if (str.length > 0 && str.length <= 14) {
    /* ---- newString (Decryption String) Generation ---- */
    let currentPos = strCode.indexOf(str[0]);
    let newString =
      strCode.substring(currentPos + 1) + strCode.substring(0, currentPos + 1);
    /* ---- End newString Generation ---- */

    result = "";
    for (let i = 1; i < str.length; i++) {
      // Loop through str [1 - length]

      let tmpChar = str[i];
      let pos = newString.indexOf(tmpChar) - i - 1;

      // if pos is negative
      if (pos <= -1) {
        pos += strCode.length;
      }

      // if tmpChar present inside newString (Always true for our purpose)
      if (newString.indexOf(tmpChar) > -1) {
        result = result + strCode[pos];
      } else {
        result = result + tmpChar;
      }
    }
  }

  // result.length = str.length - 1
  return result;
}

Now run the code in debugging mode and find the algorithm. This decode function uses a symmetric cipher algorithm. Now use this knowledge to write the encode function.

I explained how to reverse the symmetric cipher algorithm here.

Final code

Here is the code to generate the activation key for the given primary key.

final-code.js
function Encode(str, strCode = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") {
  /* ---- newString (Encryption String) Generation ---- */
  let currentPos = strCode.indexOf(str[0]);
  let newString =
    strCode.substring(currentPos + 1) + strCode.substring(0, currentPos + 1);
  /* ---- End newString Generation ---- */
  let result = str[0];

  for (let i = 0; i < str.length; i++) {
    // Loop through str
    let char = str[i];
    let pos = strCode.indexOf(char) + i + 2;

    // If pos is negative
    if (pos >= newString.length) {
      pos -= newString.length;
    }
    result = result + newString[pos];
  }
  // result.length = str.length + 1
  return result;
}