droids0

Problem

Solution

Given the APK, I first unpacked it and did some naive searching (as in Mob psycho) with no luck. I guess I should see what the app does first. After booting up an emulator, I installed the APK and launched the app. It asks but one simple question - “where else can output go?” - the answer to which is “Logcat”.

After inputting the answer and clicking the button, the flag is printed to Logcat.

droids1

Problem

Solution

For this challenge, I thought I’d try out Android Studio’s “Profile or debug APK” functionality. It turns out the APK was built with debugging enabled so all debug symbols were preserved (the same is not true for the remaining APKs).

Taking a look at the strings, I noticed the following entry:

That looks interesting! Let’s try it:

droids2

Problem

Solution

Given the APK two.apk, I first decoded it using apktool d two.apk, which produced a two directory. After poking around a bit, I found FlagstaffHill.smali located in two/smali/com/hellocmu/picoctf which seemed promising. Within it is a getFlag method that builds a string, presumably the password. I’ll spare you the eyesore of the Smali code, and instead show you the equivalent Java code (from decompiler.com for convenience and formatted by hand for readability):

public static String getFlag(String input, Context ctx) {
	String[] witches = {"weatherwax", "ogg", "garlick", "nitt", "aching", "dismass"};
	int second = 3 - 3;
	int third = (3 / 3) + second;
	int fourth = (third + third) - second;
	int fifth = 3 + fourth;
	if (
		input.equals(""
			.concat(witches[fifth])
			.concat(".")
			.concat(witches[third])
			.concat(".")
			.concat(witches[second])
			.concat(".")
			.concat(witches[(fifth + second) - third])
			.concat(".")
			.concat(witches[3])
			.concat(".")
			.concat(witches[fourth])
		)
	) {
		return sesame(input);
	}

By making a few small changes to this code, we can print out the password:

public class TwoSol {
	public static void main(String args[]) {
		String[] witches = {"weatherwax", "ogg", "garlick", "nitt", "aching", "dismass"};
		int second = 3 - 3;
		int third = (3 / 3) + second;
		int fourth = (third + third) - second;
		int fifth = 3 + fourth;
		String password = ""
			.concat(witches[fifth])
			.concat(".")
			.concat(witches[third])
			.concat(".")
			.concat(witches[second])
			.concat(".")
			.concat(witches[(fifth + second) - third])
			.concat(".")
			.concat(witches[3])
			.concat(".")
			.concat(witches[fourth]);
		System.out.printf("%s\n", password);
	}
}

Running java TwoSol.java outputs dismass.ogg.weatherwax.aching.nitt.garlick, which, if input into the app, gets the flag!

droids3

Problem

Solution

Once again, I decoded using apktool d three.apk, took a look at the getFlag method in three/smali/com/hellocmu/picoctf/FlagstaffHill.smali. It is quite short and simple:

.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
 
.method public static nope(Ljava/lang/String;)Ljava/lang/String;
    .locals 1
    .param p0, "input"    # Ljava/lang/String;
 
    .line 11
    const-string v0, "don\'t wanna"
 
    return-object v0
.end method
 
.method public static yep(Ljava/lang/String;)Ljava/lang/String;
    .locals 1
    .param p0, "input"    # Ljava/lang/String;
 
    .line 15
    invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->cilantro(Ljava/lang/String;)Ljava/lang/String;
 
    move-result-object v0
 
    return-object v0
.end method

Line 19 in getFlag is of note:

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

In this line, the nope method is invoked with the value in p0 as a parameter, and its result is stored in register v0. By replacing nope with yep, yep will be invoked instead, thereby running the cilantro native code, thus returning the flag.

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

After making this change, I rebuilt the APK using apktool b three, which outputs to three/dist/three.apk.

Before installing the app on the emulator, it must be signed. First, I created a new keystore file using:

keytool -genkey -v -keystore mykeystore -keyalg RSA -keysize 2048 -validity 1  
0000

Then signed the new APK using this keystore file:

apksigner sign --ks mykeystore --v1-signing-enabled true --v2-signing-enabled  
true three/dist/three.apk

Installing the modified app on the emulator, any input will be accepted and print the flag.

droids4

Problem

Solution

This is the final boss. It’s basically just a combination of droids2 and droids3 so nothing too crazy.

Decoding the APK and inspecting four/smali/com/hellocmu/picoctf/FlagstaffHill.smali, I noticed that a native method cardamom is defined:

.method public static native cardamom(Ljava/lang/String;)Ljava/lang/String;
.end method

This method is what actually outputs the flag. However, running cat four/smali/com/hellocmu/picoctf/FlagstaffHill.smali | grep cardamom returns only one result: the above definition. Let’s change that!

Here is the most relevant part of getFlag for our purposes here:

.line 36
.local v4, "password":Ljava/lang/String;
invoke-virtual {p0, v4}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
 
move-result v5
 
if-eqz v5, :cond_0
 
const-string v5, "call it"
 
return-object v5
 
.line 37
:cond_0
const-string v5, "NOPE"
 
return-object v5

The authors of this problem want us to replace the line const-string v5, "call it" with our call to cardamom. Assuming cardamom independently checks the input against the password, I’ll play nice and not try to bypass the password check by replacing the “NOPE” condition. The following snippet calls cardamom, passing the value in p0 as a parameter, stores the result in v5, then returns it.

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

The snippet from before then becomes:

.line 36
.local v4, "password":Ljava/lang/String;
invoke-virtual {p0, v4}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
 
move-result v5
 
if-eqz v5, :cond_0
 
invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->cardamom(Ljava/lang/String;)Ljava/lang/String;
move-result-object v5
 
return-object v5
 
.line 37
:cond_0
const-string v5, "NOPE"
 
return-object v5

As in droids3, the APK is rebuilt using apktool b four, then signed using:

apksigner sign --ks mykeystore --v1-signing-enabled true --v2-signing-enabled  
true four/dist/four.apk

Now for the password. Like droids2, let’s drop the APK into decompiler.com:

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 cardamom(input);
	}
	return "NOPE";
}

And reverse the password checking:

public class FourSol {
	public static void main(String args[]) {
		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));
		String password = ""
			.concat(queen.toString())
			.concat(jack.toString())
			.concat(ace.toString())
			.concat(king.toString());
		System.out.printf("%s\n", password);
	}
}

This outputs alphabetsoup. Checking with our new APK, we see: