- Mon 11 May 2020
- challenges
- #challenge, #Flare
Finally I have found some time to participate in Flare-On 6 CTF :-) The challenges were fun and difficulty increased slightly on each. At the third challenge I got stuck and needed quite a bit of research to setup the needed environment to run the challenge. Of course Flarebear can be solved only by static analysis, but that's only for the very experienced reversers ;-) I find the combination of static and dynamic analysis the most efficient for me.
So this blog is about my journey to solve Flarebear. I will not write down the fastest possible way to solve this, but instead explain a little bit more the wrong paths I took during this adventure ;-) This is a three part blog post:
The challange can be found here. OK, let's start. Let's extract the challenge and see what we've got:
zep@base ~/D/f/3 - Flarebear> ls -l
total 3584
-rw-r--r-- 1 zep zep 3663929 May 22 13:25 flarebear.apk
-rw-r--r-- 1 zep zep 137 Aug 16 14:52 Message.txt
zep@base ~/D/f/3 - Flarebear> cat Message.txt
We at Flare have created our own Tamagotchi pet, the flarebear. He is very fussy. Keep him alive and happy and he will give you the flag.⏎
So we have an Android mobile application which behaves like a Tamagotchi. You have to feed, to play or to clean this Flarebear to make him happy and he will eventually give you the flag.
I never actually reversed an Android application, but I already developed some years ago. So I already knew that these applications run on top of a JVM and can therefore be easily decompiled. I searched for some tools to do the job and found this site giving an overview about the available Android reversing tools.
After playing with apktool I tried jadx and was very pleased. It instantly worked and decompilation of the given APK was easy and fast.
We can see the different Java packages of which the Flarebear app consists of:
- android, androidx: The used core Android libraries
- kotlin, kotlin.android: The used libraries of the used Kotlin programming language. This means this app was developed using Kotlin.
- org: The used libraries / extensions of IntelliJ IDE (probably of Android Studio)
- com.fireeye.flarebear: This is the main package of the Flarebear application
All the interesting stuff happens in the com.fireeye.flarebear package.
We can see already some interesting classes in this Java package, mainly the FlareBearActivity class. Since I've done some Android App development years ago, I vaguely remembered that Activities are important in Androind.. Extending the FlareBearActivity class tree, we can see the corresponding methods and attributes of this Java class. Instantly some method names catch our attention, mainly:
- danceWithFlag
- decrypt
- getPassword
Especially danceWithFlag() looks interesting since all we're looking for is the flag, right? ;-) JADX nicely decompiles the Java byte code of the selected method, in this case danceWithFlag.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | public final void danceWithFlag() {
// get InputStream for ressource "ecstaticEnc"
InputStream openRawResource = getResources().openRawResource(R.raw.ecstatic);
Intrinsics.checkExpressionValueIsNotNull(openRawResource, "ecstaticEnc");
// read data from InputStream to variable "readBytes"
byte[] readBytes = ByteStreamsKt.readBytes(openRawResource);
// get InputStream for ressource "ecstaticEnc2"
InputStream openRawResource2 = getResources().openRawResource(R.raw.ecstatic2);
Intrinsics.checkExpressionValueIsNotNull(openRawResource2, "ecstaticEnc2");
// read data from InputStream to variable readBytes2
byte[] readBytes2 = ByteStreamsKt.readBytes(openRawResource2);
// get a password by calling this method
String password = getPassword();
try {
// according to the method name, decrypt loaded ressources using
// the given password
byte[] decrypt = decrypt(password, readBytes);
byte[] decrypt2 = decrypt(password, readBytes2);
dance(
// interpret loaded and decrypted ressource data as BitmapDrawable
// -> flag is probably stored as encrypted image
new BitmapDrawable( getResources(),
BitmapFactory.decodeByteArray(decrypt, 0, decrypt.length)),
new BitmapDrawable( getResources(),
BitmapFactory.decodeByteArray(decrypt2, 0, decrypt2.length))
);
}
catch (Exception unused) {
}
}
|
The decompiled Java byte code looks promising. First data is fetched from two Android resources named "ecstaticEnc" and "ecstaticEnc2", which probably means "encrypted ecstatic". Then a password is retrieved using the getPassword method. We have certainly to have a look at this method. Then the fetched resources are decrypted by a decrypt method using the retrieved password. The resulting data is interpreted as BitmapDrawable, therefore the searched flag is probably stored as encrypted image.
First let's have a look at the getPassword method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | public final String getPassword() {
String str;
int stat = getStat('f');
int stat2 = getStat('p');
int stat3 = getStat('c');
String str2 = "*";
String str3 = "&";
String str4 = "@";
String str5 = "#";
String str6 = "+";
String str7 = "$";
String str8 = "_";
String str9 = "";
switch (stat % 9) {
case 0:
str = str8;
break;
case 1:
str = "-";
break;
case 2:
str = str7;
break;
case 3:
str = str6;
break;
case 4:
str = "!";
break;
case 5:
str = str5;
break;
case 6:
str = str4;
break;
case 7:
str = str3;
break;
case 8:
str = str2;
break;
default:
str = str9;
break;
}
switch (stat3 % 7) {
case 0:
str2 = str7;
break;
case 1:
str2 = str8;
break;
case 2:
str2 = str6;
break;
case 3:
str2 = str5;
break;
case 4:
str2 = str3;
break;
case 5:
break;
case 6:
str2 = str4;
break;
default:
str2 = str9;
break;
}
String repeat = StringsKt.repeat("flare", stat / stat3);
String repeat2 = StringsKt.repeat(rotN("bear", stat * stat2), stat2 * 2);
String repeat3 = StringsKt.repeat("yeah", stat3);
StringBuilder sb = new StringBuilder();
sb.append(repeat);
sb.append(str);
sb.append(repeat2);
sb.append(str2);
sb.append(repeat3);
return sb.toString();
}
|
First the getPassword method uses an other method named getStat to get three integer values:
- stat
- stat2
- stat3
These integers are used to build the needed password string. Without the knowledge of these three integers, we cannot "rebuild" the needed password. So let's have a look at the used method getStat.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public final int getStat(char c) {
String string = PreferenceManager.getDefaultSharedPreferences(this).getString("activity", "");
Intrinsics.checkExpressionValueIsNotNull(string, "act");
CharSequence charSequence = string;
int i = 0;
for (int i2 = 0; i2 < charSequence.length(); i2++) {
if (charSequence.charAt(i2) == c) {
i++;
}
}
return i;
}
|
This method retrieves a string stored in the SharedPreferences store of this Android application. Then the for loop counts the occurrence of the supplied char variable content c in the corresponding string (c is an argument of this method).
The method getPassword uses these calls to getStat:
1 2 3 | int stat = getStat('f');
int stat2 = getStat('p');
int stat3 = getStat('c');
|
This means that the getPassword method needs as input three integers, which denote the occurrence of the three chars f, p, c in a string stored in the sharedPreferences of this Android application.
The questions are now:
- What does this string saved in the sharedPreferences denotes?
- How is this string stored there ?
One way would be to search for the string getDefaultSharedPreferences to find other methods, which store data in the sharedPreferences. But first it's better to lookup what SharedPreferences are exactly on the Android platform. As it seems, SharedPreferences are a mechanism on Android store data with "strong consistency guarantees". In the end, this is usable for an developer like an shared key / value store. If we have a closer look at the various usages of the SharedPreferences store, we find following methods using it:
- setState and its overloaded variants
1 2 3 4 5 6 7 8 9 10 11 12 | public final void setState(@NotNull String str, @NotNull String str2) {
Intrinsics.checkParameterIsNotNull(str, "key");
Intrinsics.checkParameterIsNotNull(str2, "value");
Editor edit = PreferenceManager.getDefaultSharedPreferences(this).edit();
// store data using key 'str' and value 'str2'
edit.putString(str, str2);
edit.commit();
}
|
Now it would be interesting to find out, which keys are used to save data, as this method above is generic and takes key and value as function arguments. A quick search reveals following used keys:
- activity, used by the saveActivity function
- clean, used by the changeClean function
- happy, used by the changeHappy function
- mass, used by the changeMass function
By the looking through the decompiled source code, three methods stand out:
- clean
- feed
- play
Maybe we remember the text given from the challenge creators:
"We at Flare have created our own Tamagotchi pet, the flarebear. He is very fussy. Keep him alive and happy and he will give you the flag"
Our Flarebear is a Tamagotchi and this being has basically three needs: eating, playing and to be clean. This maps exactly to the three discovered methods above. Let's have a look at the clean function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public final void clean(@NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
saveActivity("c");
removePoo();
cleanUi();
changeMass(0);
changeHappy(-1);
changeClean(6);
setMood();
}
|
This function modifies the state of the Tamagotchi, in particular it uses the saveActivity function to add char c to the key activity in the sharedPrefercnes. Furthermore the state of mass, happy and clean are also changed. As showed above, the function changeClean saves its state to the clean key in the sharedPreferences, as the other two methods do.
Above we found the getPassword function, which called three times the getStat function to retrieve the occurrence of the chars f, c, and p from the key activity from the sharedPreferences. As we have found out, the function clean modifies the values from the activity key. As so the other two functions play and feed.
We can now assume that this three functions control the state of the Tamagotchi and the correct combination of calls will set the correct state in the activity sharedPreferences. This correct state will lead to the correct password generated by the getPassword function, which will decrypt the flag, which is stored as Bitmap graphic.
For now this is it. In part two of this blog, we'll setup the needed environment to actually run the Flarebear Android application to have fun with Frida ;-)