Hacky Easter 2015 – Clumsy Cloud


Hello there again, welcome back! This another hard challenge from the Hacky Easter 2015 challenges, and it’s all in the cloud! It is an Android challenge, with your typical application-to-webservice set-up, where we use the app’s code to win!

Cjallenge 26 banner.
Challenge 26 banner.

Challenge Layout

For the unaware, Hacky Easter came with an android application from Google Play, so that you could scan the eggs you got ad get awarded points. This was accomplished by the app scanning and decoding the QR code, then encrypting it and sending it off to an end-point. The end-point would then reply with a yes or no and the app would inform you accordingly.

The mobile app, however, served an additional purpose. There were some challenges that were mobile only, and were accomplished through the app. This is one of them, and it required you to enter a 4-digit pin to recover the egg.

Tackling the Challenge

The reversing of the mobile app needs to happen in a number of stages. First we must decompile the mobile application, then analyse its source and find out what it does, as well as bypass any protections that are in place. After this, we will communicate with the web-service end-point in order to determine how we can win.

The first task can be achieved with three tools, apktool and unzip. For the uninitiated, APK files are really just ZIP files with a specific directory structure. In particular they must contain binaries and resources, as well as a manifest file, in pre-defined locations that Android specifies. As such, we do the following:

$ unzip -d apk ps.hacking.hackyeaster.android.apk
$ ls apk

At this point the files are still compiled however, so if we want to read the AndroidManifest.xml file, we will need to decompile the APK using apktool. Running

 $ apktool d ps.hacking.hackyeaster.android.apk
$ ls ps.hacking.hackyeaster.android.apk

Will result in a very similar directory structure, with some key differences. The manifest file is now a flat text file, as opposed to a binary blob, and there is a smali/ directory. Smali files are essentially decompiled bytecode, so if we wanted to patch anything in the binary we could edit the smali files and then recompile the apk. Additionally, when we unzipped the file we got a file called classes.dex, which is the application’s class data.

Compiling applications for Android involves compiling the Java files down to Class files, and then compiling those Class files to a Dex file. Therefore decompiling for Android would consist of turning that Dex file back into Java Classes. dex2jar is a very handy utility that does just that, giving us back a Jar file we can read with our favourite Java decompiler!

Inspecting the application

Inspecting the AndroidManifest.xml file, we see that it has some interesting permissions:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:required="false"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:required="false"/>

So it can talk to the internet and use the camera and flash, as well as location. The one that stands out however is WRITE_EXTERNAL_STORAGE, as it hints towards filesystem activity! What is this challenge doing? We will have the answer to this in a bit!

If we take a closer look at the decompiled files, we will soon spot the assets/www/ directory, containing a challenge26.html file. It would seem that the application was written in HTML, CSS and JavaScript, just like a normal webapp, and then used in a WebView. Challenge26.html contains a pinPressed(int a) function with the following code fragment:

if (pinEntered.length >= 4) {
    setTimeout(function() {
        window.location = 'ps://pin?' + pinEntered;
        pinEntered = '';
    }, 200);

pinEntered is a string holding the PIN we entered, incremented a digit per key-press. When the pin entered is equal to four digits, then the JavaScript will redirect the page to ‘ps://pin?‘, or in other words on the pin domain using the ps protocol. Of Course ps is not a valid protocol, so something else must be happening, perhaps somewhere deeper in the application!

If only we could figure out what is going on internally!

Manifest, The Sequel

The Manifest file is where the application manifests itself, so let’s take a close look. In the manifest file we can see that when we launch the application, the SplashActivity Activity will be the first thing to start:

 <activity android:label="@string/app_name" android:name=".SplashActivity" android:screenOrientation="fullSensor" android:theme="@android:style/Theme.Black.NoTitleBar">
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>

At this point we should use dex2jar to convert the Classes.dex file into a JAR file, so we can use a Java bytecode viewer (I’ll personally use Java ByteCodeViewer). If we find the file ps.hacking.hackyeaster.android.SplashActivity.class, we will see that it starts a new runnable (think Thread, for the non-androids out there), which in turn starts an Activity Activity.

Now we know it’s on, as we can tell that the files are obfuscated somewhat. All the classes and variables have got meaningless names such as var1, var2, a, g, f. The functions in the classes are overloaded and so there are 4 different declarations of a function a (void a(), void a(int, String), void a(Intent), void a(Strings, String, Context)) and so do most letters of the alphabet. Following an Activity’s life-cycle, we should go looking for an onCreate method:

   public void onCreate(Bundle var1) {
    this.j = this.getString(2131230727);
    this.k = this.getString(2131230728);
    if(!this.isTaskRoot()) {
       Intent var3 = this.getIntent();
       String var2 = var3.getAction();
       if(var3.hasCategory("android.intent.category.LAUNCHER") && var2 != null && var2.equals("android.intent.action.MAIN")) {
    this.g = (SensorManager)this.getSystemService("sensor");
    this.a.setOnKeyListener(new a(this, this));

public void b() {
    this.a.setWebViewClient(new c(this, this));

Bingo! The call we are interested in is this.b(). Setting a WebViewClient for an Activity allows the application to handle user-clicks on links. Which means that it might also handle programmatic protocol changes, link ‘ps://pin’ links! Inspecting Class c:

class c extends WebViewClient {
    final Activity a;
    private final Activity b;
    public boolean shouldOverrideUrlLoading(WebView var1, String var2) {
        boolean var5 = false;
        if( ... ){ ...
        } else {
            if( ... ) {
            } else if(var2 != null && var2.startsWith("ps://pin?")) {
                var2 = Uri.parse(var2).getQuery();
                int var8 = Activity.a(this.a, var2, this.b);
                var1.loadUrl("javascript:pinFeedback(\'{\"status\": " + var8 + "}\')");
                var5 = true;
            } ....
        return var5;

So class c is responsible for handling the URI request for the pin! Class c calls class Activity‘s method int a(Activity, String, Context) and this in turn calls int a(String, Context) method, conveniently located just above it in the code of Activity.class:

private int a(String var1, Context var2) {
    byte var4;
    try {
        SecretKeySpec var3 = new SecretKeySpec(a(var1, "ovaederecumsale", 10000), "AES");
        Cipher var6 = Cipher.getInstance("AES");
        var6.init(2, var3);
        String var8 = new    String(var6.doFinal(Base64.decode("8QeNdEdkspV6+1I77SEEEF4aWs5dl/auahJ46MMufkg=", 0)));
        DownloadManager var7 = (DownloadManager)this.getSystemService("download");
        Request var9 = new Request(Uri.parse("http://hackyeaster.hacking-lab.com/hackyeaster/pin?p=" + var8));
        var9.setTitle("Hacky Easter");
        var9.setDescription("Egg Download");
        var9.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "egg_26.png");
        this.registerReceiver(new d(this), new IntentFilter("android.intent.action.DOWNLOAD_COMPLETE"));
        Toast.makeText(var2, "Download started", 0).show();
    } catch (Exception var5) {
        var4 = 1;
        return var4;
    var4 = 0;
    return var4;

static int a(Activity var0, String var1, Context var2) {
    return var0.a(var1, var2);

As you might have realised by now, the method int a(String, Context) is where all the show takes place! On a high level, it looks like the app is taking our input, encrypting it and sending it off to the web-service. This form of protection is known as message-level encryption, and it is a further barrier of defense where the application level (in addition and to the transport layer, like TLS) encrypts the messages to the server to protect them from eavesdropping! It is a very good strategy, however on its own can fail you (as we will very soon see) .

I  am not going to go into any details here in order to explain what the code does per-line, but the code above has a secret key baked in which is used to encrypt the supplied pin with AES, and then it sends it to http://hackyeaster.hacking-lab.com/hackyeaster/pin?p=encrypted_pin_here. What is interesting to note about the code above is the line


Which basically saves the response from the server in a file called egg_26.png. OK, so here is some file activity! We are expecting a PNG file from the server!

Using The Force

We can use the code above to brute-force the pin, as it is only four digits! Copying and pasting the code above, as well as de-obfuscating the code a bit, we have the following:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.net.HttpURLConnection;
import java.net.URLConnection;
import java.io.InputStream;
import java.net.URL;

public class Brute{
public static void main(String[] args){
    for (int i = 0 ; i < 10000; i++){

private static int a(String pin) {
    byte return_status;
    try {
        SecretKeySpec secret_key_spec = new SecretKeySpec(a_ssi(pin, "ovaederecumsale", 10000), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(2, secret_key_spec);
        String var8 = new String(cipher.doFinal(DatatypeConverter.parseBase64Binary("8QeNdEdkspV6+1I77SEEEF4aWs5dl/auahJ46MMufkg=")));
        URLConnection connection = new URL("http://hackyeaster.hacking-lab.com/hackyeaster/pin?p=" + var8).openConnection();
        InputStream response = connection.getInputStream();
        byte[] resp = new byte[1000];
        int r = response.read(resp);
        System.out.println("a(" + pin + ") -- " + r + new String(resp));
    } catch (Exception var5) {
        return_status = 1;
	return return_status;
    return_status = 0;
    return return_status;

public static byte[] a_ssi(String var0, String var1, int iterations) throws Exception {
    MessageDigest msg_digest = MessageDigest.getInstance("SHA1");
    byte[] var5 = (var1 + var0).getBytes();
    for(int i = 0; i < iterations; ++i) {
        var5 = msg_digest.digest(var5);
    byte[] var6 = new byte[16];
    System.arraycopy(var5, 0, var6, 0, 15);
    return var6;

Compile, run and wait for the magic to happen! The serer would reply with -1 for invalid pins, until at some point I saw strange unprintable characters printed, followed by PNG and more unprintable characters! What was printed was the downloaded image, given away by PNG’s mime signature! The correct code for unlocking all of our data was 7113 and the egg was:

Challenge 26 egg!
Challenge 26 egg!



This was a fun challenge to solve, although it required a bigger set of skills than the other challenges. The process got a bit easier once we converted the DEX file to a JAR, since we could just decompile the JAR file like we did with the other challenges. The most challenging bit in this challenge I believe was that the code was somewhat obfuscated.

A number of steps could have been taken to protect this application, at least for applications in the real world. The first big no-no was that the application was communicating over HTTP. The reason why this is bad is not so much that the messages could be read, as there was message level encryption, but that the authenticity of the receiver of the messages could not be identified. Additionally, the message-level encryption can be broken, as it’s symmetric key cryptography and the keys are easily recoverable from the application’s binary. This could have been mitigated by using an obfuscator, in order to make static analysis a lot harder, and code armoring so that dynamic analysis would also be throttled.

In addition to the application’s protection, the web-app could could also implement some protections against pin brute-forcing.

Hacky Easter 2015 – Clumsy Cloud

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s