Over the last few weeks and in part thanks to my new fascination with Android app security, I have recently found myself heavily invested in cyber security content online. Youtuber’s such as John Hammond and LiveOverflow provide hundreds of hours of free content, teaching the average person (as well as developers such as myself) the core concepts around cyber security, ethical hacking and reverse engineering.

Part of this content introduced me to cyber security’s capture-the-flag (‘CTF’) events, in which teams or individuals compete on challenges in numerous cyber security disciplines in order to gain points, win prizes and ultimately hold the bragging rights over their peers. The challenges usually range in difficulty and in style, from reverse engineering code to completely taking over a machine and gaining root (admin) access.

However, for the complete novice, picoCTF is a free for all annual event aimed at middle school + high school pupils to introduce them into cyber security through a number of hand crafted challenges. Once the event is over these challenges are released and made accessible for non-event attendees to try them for themselves. Despite the intended audience, the difficulty level of some of the harder challenges makes picoCTF a popular event for many seasoned experts as well as beginners. The aim of each challenge is to find a hidden ‘flag’ in the format “picoCTF{xxxxxxxxxxx}” using cyber security tools, your own knowledge and very often some expert google-fu.

What interested me immediately with picoCTF was 5 of their challenges all revolving around Android applications. These challenges, named droid0 to droid4 respectively, provided me with an exciting chance to test my Android knowledge and further my understanding of the platform I have worked with for close to 10 years. I thought being an experienced developer would mean they’d all be easy, but how wrong I was!

Below are my solutions to these problems.

⚠️ Spoilers Ahead

I highly recommend you try all the droid challenges before continuing this post to avoid spoilers. They range in difficulty, but with good research technique should be easy enough to solve without too much trouble.

However, should you wish to see the solutions, please continue at your discretion!

Challenge 1: droids0

The first challenge provided the following description

Where do droid logs go. Check out this file.

The very first step was to download the provided file to check what it was, so using wget I downloaded it to my working directory.

wget https://jupiter.challenges.picoctf.org/static/02bcd73e630f50ef0b12bcdad9d84e0d/zero.apk

It’s an APK file! Great, looks like we are checking out somebody’s compiled app.

Assuming it’s a debuggable build, it is possible to install this APK on a device or emulator via the Android Debug Bridge ‘adb’ tool.

adb install -t zero.apk

Running the app showed an extremely basic app consisting of a title, input text field, button and another text view.

Playing with the app, inputting text and clicking the button caused the “I’m a flag text” to change to a “Not Today…” message.

With the app’s title text and the problem’s description, I immediately knew that I needed to check the application’s logs.

To do this, I made use of pidcat and grep‘d the output to check for flags.

pidcat | grep -E -o "picoCTF{.*}"

Sure enough, there’s the flag: picoCTF{a.moose.once.bit.my.sister}

Challenge 2: droids1

The second challenge starts with the following description

Find the pass, get the flag. Check out this file.

Following the previous challenge, I first downloaded the APK file and installed it to my emulator.

wget https://jupiter.challenges.picoctf.org/static/b12c6d058c7f52eb1fd2015cfd291716/one.apk

adb install -t one.apk

The app that installs is a mirror of the previous challenges app, but with the title stating ‘brute force not required’. Entering any text and hitting the button provides a helpful ‘NOPE’. It looks like a password is first required to gain access to the flag.

Based on the title hint, it suggests that reverse-engineering is the way to go. To reverse engineer the APK we can make use of apktool to decompile the app, check the resources and even inspect the decompiled Android byte-code written in a smali format.

To decompile the app using apktool is ultra straightforward. The tool creates a directory one with directories for all the decompiled files.

apktool d one.apk
# Open the relevant Flag file in Sublime Text
subl one/smali/com/hellocmu/picoctf/FlagstaffHill.smali

Whilst not being a smali expert, opening the smali file I located a getFlag method that seemed like a great point to start. Inspecting the method, it was possible to work out what was going on through adding my own annotations.

The annotated file is below:

// The 'get flag method' presumably provides the flag!
.method public static getFlag(Ljava/lang/String;Landroid/content/Context;)Ljava/lang/String;
    .locals 2
    .param p0, "input"    # Ljava/lang/String;
    .param p1, "ctx"    # Landroid/content/Context;

    .line 11
    const v0, 0x7f0b002f // <-- This looks interesting! Likely a string resource

    // Call context.getString with the value in v0
    invoke-virtual {p1, v0}, Landroid/content/Context;->getString(I)Ljava/lang/String;

    move-result-object v0

    .line 12
    // Create local variable password (presumably from the user input)
    .local v0, "password":Ljava/lang/String;
    // Call String#equals to compare input with the input
    invoke-virtual {p0, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v1

    // Check if the strings are equal
    if-eqz v1, :cond_0

    // If equal, call fenugreek method which returns a String
    // This looks to be a call to a native library that provides the flag
    invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->fenugreek(Ljava/lang/String;)Ljava/lang/String;

The 0x7f0b002f in the code referenced a string resource. So to find out which one, I ran a simple grep.

grep -iFr "0x7f0b002f" .

> ./one/res/values/public.xml:    <public type="string" name="password" id="0x7f0b002f" />
> ./one/smali/com/hellocmu/picoctf/FlagstaffHill.smali:    const v0, 0x7f0b002f
> ./one/smali/com/hellocmu/picoctf/R$string.smali:.field public static final password:I = 0x7f0b002f

Having found out the relevant string resource is password, then we can search the XML files for the relevant password using find and grep.

find . -type f -regex ".*.xml" -exec grep -iFr "password" {} \;

> ./one/res/values/public.xml:    <public type="string" name="password" id="0x7f0b002f" />
> ./one/res/values/strings.xml:    <string name="password">opossum</string>

Bingo. Entering “opossum” into the app reveals the flag picoCTF{pining.for.the.fjords}

Challenge 3: droids2

Find the pass, get the flag. Check out this file.

With the same hint as the last challenge, it was time to follow the same steps as previous.

wget https://jupiter.challenges.picoctf.org/static/b7d30de6eaaf83e685aea7c10c5bdea8/two.apk

adb install -t two.apk

Installing the app resulted in the title of the app changing to ‘smali sounds like an Ikea bookcase’. In fairness, it totally does. Again, it looked like we’d need to find the password to unlock the flag.

Based on this title, it was a fair assumption we’d be diving head first into the decompiled smali code again.

apktool d two.apk

# Open the relevant Flag file in Sublime Text
subl one/smali/com/hellocmu/picoctf/FlagstaffHill.smali

This time the smali code was far more complex than the previous challenge. I decided that to investigate further, I would make use of the popular jadx decompiler to take the smali and attempt to convert it into something more readable, i.e. Java code.

After downloading jadx, I launched the GUI using the jadx-gui command and opened FlagstaffHill.smali. Thankfully, jadx did an excellent job of reversing the smali back into Java.

public class FlagstaffHill
{
    public static String getFlag(final String s, final Context context) {
        final String[] array = { "weatherwax", "ogg", "garlick", "nitt", "aching", "dismass" };
        final int n = 3 - 3;
        final int n2 = 3 / 3 + n;
        final int n3 = n2 + n2 - n;
        final int n4 = 3 + n3;
        if (s.equals("".concat(array[n4]).concat(".").concat(array[n2]).concat(".").concat(array[n]).concat(".").concat(array[n4 + n - n2]).concat(".").concat(array[3]).concat(".").concat(array[n3]))) {
            return sesame(s);
        }
        return "NOPE";
    }

    public static native String sesame(final String p0);
}

To obfuscate the password, it seems the app was doing some array and string manipulation. Based on a quick look at the code, it looked like the array of words would be placed in a specific order, separated by periods.

However, to avoid the potentially time consuming task of reverse engineering the code and then validating it, I created a new Java file and copied the decompiled code over. From there, I modified the code so it would print out the flag to ensure there was no chance for error and I would get the password immediately.

public class FlagstaffHillModified
{
    public static String getFlag() {
        final String[] array = { "weatherwax", "ogg", "garlick", "nitt", "aching", "dismass" };
        final int n = 3 - 3;
        final int n2 = 3 / 3 + n;
        final int n3 = n2 + n2 - n;
        final int n4 = 3 + n3;
        
        // Return the password
        return "".concat(array[n4]).concat(".").concat(array[n2]).concat(".").concat(array[n]).concat(".").concat(array[n4 + n - n2]).concat(".").concat(array[3]).concat(".").concat(array[n3]);
    }
    
    public static void main(String args[]) {
        // Print the password
        System.out.print("Password: ");
        System.out.println(getFlag());
    }
}

Once modified, compiling and running the code should provide the expected password.

javac FlagstaffHillModified.java

java FlagstaffHillModified

> Password: dismass.ogg.weatherwax.aching.nitt.garlick

Boom. There’s the password. With that in hand, I entered it into the app and successfully unlocked the flag picoCTF{what.is.your.favourite.colour}

Challenge 4: droids3

Find the pass, get the flag. Check out this file.

Knowing the drill at this point, I followed the same steps as the previous challenges.

https://jupiter.challenges.picoctf.org/static/06318765139795831859f843dd56ce60/three.apk

adb install -t three.apk

The hint this time was a little more cryptic

Make this app your own

Ok? Make it my own? How can I do that.

To see what was going on this time, I again made use of jadx-gui and opened the smali file.

public class FlagstaffHill {
    public static native String cilantro(String str);

    public static String nope(String input) {
        return "don't wanna";
    }

    public static String yep(String input) {
        return cilantro(input);
    }

    public static String getFlag(String input, Context ctx) {
        return nope(input);
    }
}

So it looks like the class only has 4 methods, the cilantro native method to get the flag from a compiled library, the getFlag method that calls the nope method and a seemingly unused yep method that gets the flag via cilantro.

Huh. Ok, I think we some how need to modify the app’s code to make getFlag call yep instead of nope.

Thankfully, I had just come across the excellent ‘Hacktricks’ website that documents numerous reverse engineering techniques. The section on Android pointed out that apktool could be used to recompile an app with modified smali.

So, I checked the smali for the getFlag method

.method public static getFlag(Ljava/lang/String;Landroid/content/Context;)Ljava/lang/String;
    .locals 1
    .param p0, "input"    # Ljava/lang/String;
    .param p1, "ctx"    # Landroid/content/Context;

    .line 19
    invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->nope(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v0

    .line 20
    .local v0, "flag":Ljava/lang/String;
    return-object v0
.end method

Could it be as simple as changing nope(Ljava/lang/String;)Ljava/lang/String; to use yep?

I modified the file and followed the steps to recompile the build with my modified code

# Re-compile the app
# three is the base folder of the decompiled app
apktool b three

# Change directory to the newly generated APK
cd three/dist

# Generate a new key to sign the build
keytool -genkeypair -v -keystore key.keystore -alias publishingdoc -keyalg RSA -keysize 2048 -validity 10000

# Sign the new build
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ./key.keystore three.apk publishingdoc

# Uninstall previous version of the app and install the new one
adb uninstall com.hellocmu.picoctf
adb install three.apk

Nervously I try clicking the button in the app and to my surprise it reveals the flag picoCTF{tis.but.a.scratch}

Final Challenge 5: droids4

Reverse the pass, patch the file, get the flag. Check out this file.

Having done this a few times by this point, I follow the steps from the previous challenges. It seems like that this time it will be a combination of figuring out the password and then modifying smali before recompiling.

wget https://jupiter.challenges.picoctf.org/static/926d4bfd7030b13dbc98ca26e608c740/four.apk

apktool d four.apk

jadx-gui

Opening the the smali file in jadx shows the following Java code that again makes use of obfuscation techniques to hide the password.

public class FlagstaffHill {
    public static native String cardamom(String str);

    public static String getFlag(String input, Context ctx) {
        StringBuilder ace = new StringBuilder("aaa");
        StringBuilder jack = new StringBuilder("aaa");
        StringBuilder queen = new StringBuilder("aaa");
        StringBuilder king = new StringBuilder("aaa");
        ace.setCharAt(0, (char) (ace.charAt(0) + 4));
        ace.setCharAt(1, (char) (ace.charAt(1) + 19));
        ace.setCharAt(2, (char) (ace.charAt(2) + 18));
        jack.setCharAt(0, (char) (jack.charAt(0) + 7));
        jack.setCharAt(1, (char) (jack.charAt(1) + 0));
        jack.setCharAt(2, (char) (jack.charAt(2) + 1));
        queen.setCharAt(0, (char) (queen.charAt(0) + 0));
        queen.setCharAt(1, (char) (queen.charAt(1) + 11));
        queen.setCharAt(2, (char) (queen.charAt(2) + 15));
        king.setCharAt(0, (char) (king.charAt(0) + 14));
        king.setCharAt(1, (char) (king.charAt(1) + 20));
        king.setCharAt(2, (char) (king.charAt(2) + 15));
        if (input.equals("".concat(queen.toString()).concat(jack.toString()).concat(ace.toString()).concat(king.toString()))) {
            return "call it";
        }
        return "NOPE";
    }
}

Following the previous challenge. I again made a modified version of the code to print out the password

public class FlagstaffHillModified {

    public static String getFlag() {
        StringBuilder ace = new StringBuilder("aaa");
        StringBuilder jack = new StringBuilder("aaa");
        StringBuilder queen = new StringBuilder("aaa");
        StringBuilder king = new StringBuilder("aaa");
        ace.setCharAt(0, (char) (ace.charAt(0) + 4));
        ace.setCharAt(1, (char) (ace.charAt(1) + 19));
        ace.setCharAt(2, (char) (ace.charAt(2) + 18));
        jack.setCharAt(0, (char) (jack.charAt(0) + 7));
        jack.setCharAt(1, (char) (jack.charAt(1) + 0));
        jack.setCharAt(2, (char) (jack.charAt(2) + 1));
        queen.setCharAt(0, (char) (queen.charAt(0) + 0));
        queen.setCharAt(1, (char) (queen.charAt(1) + 11));
        queen.setCharAt(2, (char) (queen.charAt(2) + 15));
        king.setCharAt(0, (char) (king.charAt(0) + 14));
        king.setCharAt(1, (char) (king.charAt(1) + 20));
        king.setCharAt(2, (char) (king.charAt(2) + 15));
        return "".concat(queen.toString()).concat(jack.toString()).concat(ace.toString()).concat(king.toString());
    }
    
    public static void main(String args[]) {
        System.out.print("Password: ");
        System.out.println(getFlag());
    }

}

Compiling and running this gave me the password as expected

javac FlagstaffHillModified.java

java FlagstaffHillModified
> Password: alphabetsoup

However, having seen the getFlag method was hard-coded to always return "call it", I also knew that we’d need to modify the smali code and recompile as per the previous challenge.

I located the smali code that returned the hardcoded string and observed the following

if-eqz v5, :cond_0

const-string v5, "call it"

return-object v5

I needed to modify this code to instead invoke the cardamom method, and return it.

Thankfully, the previous challenge actually provided the smali code to do this, so it was a simple case of copying it over and replacing the smali for the return method with the following

invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->cardamom(Ljava/lang/String;)Ljava/lang/String;

move-result-object v0

return-object v0

With these changes made, it was time to try it out!

# Re-compile the app
# four is the base folder of the decompiled app
apktool b four

# Change directory to the newly generated APK
cd four/dist

# Generate a new key to sign the build
keytool -genkeypair -v -keystore key.keystore -alias publishingdoc -keyalg RSA -keysize 2048 -validity 10000

# Sign the new build
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ./key.keystore four.apk publishingdoc

# Uninstall previous version of the app and install the new one
adb uninstall com.hellocmu.picoctf
adb install four.apk

Entering the previously discovered password and clicking the button reveals the final flag picoCTF{not.particularly.silly}

We’ve done it! ✨

Wrapping Up

picoCTF’s droid challenges proved to be a fun learning experience with the difficulty starting fairly easy but ramping up and requiring some background research.

In all these took me a couple of evenings to complete.

I would highly recommend giving this a go yourself as well as looking into the other challenges picoCTF has to offer!

Thanks 🌟

Thanks as always for reading! I hope you found this post interesting, please feel free to tweet me with any feedback at @Sp4ghettiCode and don’t forget to clap, like, tweet, share, star etc