The challenge:

The key is stored in the application, but you will need to hack the server.
( *Do not use your real email address.)
http://scontents.quals.seccon.jp/files/app-release-apk.pwn.seccon.jp-v1.3.apk

Lets extract the classes.dex file from the archive and use dex2jar [1] to convert the Dalvik code to a jar file containing Java Bytecode, so we can decompile it with jd [2].

Browsing through the Application we can see 4 packages, a.a.a, android.support, b.a.a.a, and kr.repo.h2spice.yekehtmai. The app is obfuscated using simple name mangling, no big deal. The first package is really the Volley framework [3], which means we do not have to analyse it any further. The support library and the third package are boring as well, which leaves us with kr.repo.h2spice.yekehtmai containing the actual application core.

The app contains 3 activities: MainActivity, RegisterActivity, and WelcomeActivity. Lets start by analysing the entry-point activity MainActivity. After creating a layout containing two buttons, two text-views and a couple of other widgets, onCreate calls into this.g.a():

if (this.g.a())
{
    startActivity(new Intent(this, WelcomeActivity.class));
    finish();
}

// this.g.a
public boolean a()
{
    // `a' is of type SharedPreferences.
    return this.a.getBoolean("isLoggedIn", false);
}

So the application checks whether we are already logged in, and if so continues on to the WelcomeActivity. Otherwise, this MainActivity with the two text-boxes is used as a login screen. Additionally, it has a further button which leads to the registration activity.

The WelcomeActivity performs the following startup operations:

Object localObject = this.d.a();
paramBundle = (String)((HashMap)localObject).get("name");
String str = (String)((HashMap)localObject).get("email");
localObject = ((String)((HashMap)localObject).get("uid")).substring(i, j);
try
{
    this.f = c.b("fuO/gyps1L1JZwet4jYaU0hNvIxa/ncffqy+3fEHIn4=", (String)localObject);
    this.a.setText(paramBundle);
    this.b.setText(this.f);
    this.c.setOnClickListener(new q(this));
    return;
}

It sends an HTTP POST request to http://apk.pwn.seccon.jp/login.php, which on success will return back the username and a user id in the uid parameter. The first 16 bytes of this user id are then used together with a base64-encoded string in method c.a, which performs this:

paramString2 = new SecretKeySpec(paramString2.getBytes(), "AES");
Cipher localCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// MODE_DECRYPT
localCipher.init(2, paramString2);
// b.a is base64-decode: org.apache.commons.codec.binary.Base64
paramString1 = localCipher.doFinal(b.a(paramString1));
return new String(paramString1);

So this string fuO/gyps1L1JZwet4jYaU0hNvIxa/ncffqy+3fEHIn4= is probably the encrypted flag (recall the problem description claiming that the flag is within the application). The decryption key is the user id from one specific user - but which one?

Lets see how the registration and login process works. Analysing the registration activity, we can see that the HTTP POST request parameters are encrypted using the same scheme as illustrated above, however with different keys. Instead of reversing the key derivation procedure, I quickly disassembled the app into smali code using apktool [4] and modified the encryption method in /smali/kr/repo/h2spice/yekehtmai/c.smali, adding Log.d calls to dump the encryption keys:

.method public static a(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    .locals 4
    const-string v0, "BOOP"
    # Log argument 1 (the thing to be encrypted).
    invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
    const-string v0, "BOOP"
    # Log argument 2 (the encryption key).
    invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
    # ...

Installing the modified app on an emulator, running it and creating an account / logging in reveals the following keys from adb logcat -s "BOOP":

D/BOOP    ( 1705): foo_username
D/BOOP    ( 1705): 9845674983296465

The key for registration encryption is 9845674983296465, and for login it is 3246847986364861. At this point, we are done analysing the application. Time to hack their server.

Using a couple of sample usernames, emails, and passwords to register new accounts, it becomes evident that the email parameter is vulnerable to SQL injection, because the server suddenly reports back generic errors or does not answer at all. After some probing, the login SQL query seems to look like this: 'SELECT <some columns> FROM <some table> WHERE <column for email> = ' + user_controlled_email

A bit more blind trial and error tells us that the query selects 8 columns from the table users. With this query, we can leak the column name for the user id (z@z.z is a dummy email I used in a previous account registration):

z@z.z' AND 1 in (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name LIKE '%s%c%%') ; #

The %s is for the leaked name so far, and %c is for the current character guess. After a couple of requests, we find the user id column name to be unique_id.

Now we can leak some user ids (note that the table also contains an integer id field for the primary key) from users with low numeric ids:

z@z.z' AND 1 in (SELECT 1 FROM users WHERE id = %d AND unique_id LIKE '%s%c%%') 

Using ids 1 and 2 yields nothing of interest, but the unique_id from id=3 successfully decrypts the flag! The unique_id is a159c1f7097ba804:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

public class decrypt {

    public static String decrypt(String input, String key) {
        byte[] output = null;

        try {
            SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, skey);
            output = cipher.doFinal(Base64.decodeBase64(input));
            System.out.println(new String(output));
        } catch (Exception e) {
            System.out.println("bad unique_id, try again..");
        }

        return new String(output);
    }

    public static void main(String[] args) {
        decrypt("fuO/gyps1L1JZwet4jYaU0hNvIxa/ncffqy+3fEHIn4=", args[0]);
    }
}

Which gives us the flag SECCON{6FgshufUTpRm}.

References

  1. https://github.com/pxb1988/dex2jar
  2. http://jd.benow.ca/
  3. http://developer.android.com/training/volley/index.html
  4. https://ibotpeaches.github.io/Apktool/