Mark Mossberg's Blog

Hacker Nonsense

SU-CTF 2014 - "Commerical Application!"

This weekend I decided to try playing SU-CTF. I’m pretty bad at CTF to be honest, so I was pretty thrilled to get one of the 200 point challenges in the third (of five) difficulty tiers.

“Commerical Application!”

For this challenge, we’re given an Android application and the hint:

Flag is a serial number.

I installed it on my phone, here’s what it looks like:

I can tap on “Picture-01” and sliding to the right reveals this picture, but if I try to tap on “Picture-02” or “Pictures-03” the app says I need to enter a registration key. If I tap on the gear in the top right, I’m prompted to enter my product key.

Running file reveals that .apk files are apparently just Zip archives, so let’s try simply unzip‘ing it.

$ file suCTF.apk
suCTF.apk: Zip archive data, at least v2.0 to extract
$ unzip suCTF.apk
Archive:  suCTF.apk
  inflating: assets/db.db
  inflating: res/color/abs__primary_text_disable_only_holo_dark.xml
  inflating: res/color/abs__primary_text_disable_only_holo_light.xml
  ...
  inflating: classes.dex
  inflating: META-INF/MANIFEST.MF
  inflating: META-INF/CERT.SF
  inflating: META-INF/CERT.RSA

Cool! Now we have all the miscellaneous files that comprise the app. There’s a database file, various .xml design files, and most interestingly, a classes.dex file. .dex files contain bytecode run on the Android Dalvik VM, which is currently the Java runtime for Android devices, so classes.dex likely contains the code that runs the app, in compiled form. We can use the nifty d2j-dex2jar utility for decompiling it into a classes-dex2jar.jar file. .jar files are apparently also Zip archives, and we can again unzip it to extract its contents.

$ file classes.dex
classes.dex: Dalvik dex file version 035
$ d2j-dex2jar classes.dex
dex2jar classes.dex -> classes-dex2jar.jar
$ unzip classes-dex2jar.jar
  ...
  inflating: edu/sharif/ctf/BuildConfig.class
  inflating: edu/sharif/ctf/CTFApplication.class
  inflating: edu/sharif/ctf/R$attr.class
  inflating: edu/sharif/ctf/R$bool.class
  inflating: edu/sharif/ctf/R$color.class
  inflating: edu/sharif/ctf/R$dimen.class
  inflating: edu/sharif/ctf/R$drawable.class
  inflating: edu/sharif/ctf/R$id.class
  inflating: edu/sharif/ctf/R$integer.class
  inflating: edu/sharif/ctf/R$layout.class
  inflating: edu/sharif/ctf/R$menu.class
  inflating: edu/sharif/ctf/R$string.class
  inflating: edu/sharif/ctf/R$style.class
  inflating: edu/sharif/ctf/R$styleable.class
  inflating: edu/sharif/ctf/R.class
  inflating: edu/sharif/ctf/activities/MainActivity$4.class
  inflating: edu/sharif/ctf/activities/MainActivity$5.class
  inflating: edu/sharif/ctf/activities/MainActivity$6.class
  inflating: edu/sharif/ctf/config/AppConfig.class
  inflating: edu/sharif/ctf/db/DBHelper.class
  inflating: edu/sharif/ctf/fragments/DListFragment$1.class
  inflating: edu/sharif/ctf/fragments/ListFragment$1.class
  inflating: edu/sharif/ctf/fragments/ListFragment$OnPictureSelectedListener.class
  inflating: edu/sharif/ctf/security/KeyVerifier.class
  ...

This produces a TON of various .class files, but the most interesting lie in the edu/sharif/ctf/ directory and are the compiled versions of the actual code that makes up this app. We can use the jad tool to decompile these back into Java source and start trying to reverse the product key.

There’s a directory in the app source called security/ and contains a file called KeyVerifier.class that seems pretty promising. After decompiling it, we find a KeyVerifier class with some pretty cool functions.

public static boolean isValidLicenceKey(String s, String s1, String s2)
{
    boolean flag;
    if(encrypt(s, s1, s2).equals("29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84"))
        flag = true;
    else
        flag = false;
    return flag;
}

public static String encrypt(String s, String s1, String s2)
{
    String s3 = "";
    String s4;
    SecretKeySpec secretkeyspec = new SecretKeySpec(hexStringToBytes(s1), "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(1, secretkeyspec, new IvParameterSpec(s2.getBytes()));
    s4 = bytesToHexString(cipher.doFinal(s.getBytes()));
    s3 = s4;
_L2:
    return s3;
    Exception exception;
    exception;
    exception.printStackTrace();
    if(true) goto _L2; else goto _L1
_L1:
}

It’s pretty clear that isValidLicenceKey() is what processes the product key prompt in the app. The encrypt() function shows us that the first paramter s is the cleartext to be encrypted, the second parameter s1 is the AES encryption key, and the last parameter s2 is the AES initialization vector. After doing a bit of grepping, I confirmed this by decompiling activities/MainActivity.class and finding this code snippet:

public void onClick(DialogInterface dialoginterface, int i)
{
    if(KeyVerifier.isValidLicenceKey(userInput.getText().toString(), app.getDataHelper().getConfig().getSecurityKey(), app.getDataHelper().getConfig().getSecurityIv()))
    {
        app.getDataHelper().updateLicence(2014);
        MainActivity.isRegisterd = true;
        showAlertDialog(context, "Thank you, Your application has full licence. Enjoy it...!");
    } else
    {
        showAlertDialog(context, "Your licence key is incorrect...! Please try again with another.");
    }
}

With this in mind, the code seems to AES encrypt the user input and check if it matches a certain output. If we had the AES key and IV, we could decrypt the given output and find the plaintext product key.

Tracing through the calls for the second and third parameters passed into isValidLicense() I found that the AES key and IV were stored in the assets/db.db SQLite database I noticed earlier.

$ sqlite3 assets/db.db
 ...
 sqlite> select * from config;
 a           b           c           d           e                 f                                 g           h               i
 ----------  ----------  ----------  ----------  ----------------  --------------------------------  ----------  --------------  ----------
 1           2           2014        0           a5efdbd57b84ca36  37eaae0141f1a3adf8a1dee655853714  1000        ctf.sharif.edu  9

There are no headers to the columns, but it is pretty obvious that the key is the longer and the IV is the shorter of the “interesting strings” in the database. For further confidence, I can verify this from the decompiled code in db/DBHelper.class.

public AppConfig getConfig()
{
    boolean flag = true;
    AppConfig appconfig = new AppConfig();
    Cursor cursor = myDataBase.rawQuery(SELECT_QUERY, null);
    if(cursor.moveToFirst())
    {
        appconfig.setId(cursor.getInt(0));
        appconfig.setName(cursor.getString(flag));
        appconfig.setInstallDate(cursor.getString(2));
        if(cursor.getInt(3) <= 0)
            flag = false;
        appconfig.setValidLicence(flag);
        appconfig.setSecurityIv(cursor.getString(4));
        appconfig.setSecurityKey(cursor.getString(5));
        appconfig.setDesc(cursor.getString(7));
    }
    return appconfig;
}

Using the key, IV and expected encrypted output, I wrote a simple decryption program.

import java.util.*;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class blah {
    // omitted for brevity
    public static String bytesToHexString(byte abyte0[]) { ... }
    public static byte[] hexStringToBytes(String s) { ... }

    public static String decrypt(String s, String s1, String s2)
    {
        SecretKeySpec secretkeyspec = new SecretKeySpec(hexStringToBytes(s1), "AES");
        Cipher cipher = null;
        byte[] key = null;
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretkeyspec, new IvParameterSpec(s2.getBytes()));
            key = cipher.doFinal(hexStringToBytes(s));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new String(key);
    }

    public static void main(String args[]) {
        String e = "29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84";
        String iv = "a5efdbd57b84ca36";
        String key = "37eaae0141f1a3adf8a1dee655853714";
        System.out.println(decrypt(e, key, iv));
    }
}
$ java blah
fl-ag-IS-se-ri-al-NU-MB-ER

Thanks for reading!

Comments