Ring 0x00

One ring to rule them all

Home About Posts Contact
Maintained by Iliya Dafchev Hosted on GitHub Pages — Theme by mattgraham

FlareOn 2019 Writeup - Part 1

Part 1 contains my solutions for challenges 1-3.

Table of Contents

Challenge 1 - Memecat_Battlestation
Challenge 2 - Overlong
Challenge 3 - flarebear

I moved this article to my new blog. Click here to read it there.

Challenge 1 - Memecat_Battlestation

Task:

Welcome to the Sixth Flare-On Challenge! 

This is a simple game. Reverse engineer it to figure out what "weapon codes" 
you need to enter to defeat each of the two enemies and the victory screen 
will reveal the flag. Enter the flag here on this site to score and move on 
to the next level.

* This challenge is written in .NET. If you don't already have a favorite 
.NET reverse engineering tool I recommend dnSpy

** If you already solved the full version of this game at our booth at 
BlackHat  or the subsequent release on twitter, congratulations, enter 
the flag from the victory screen now to bypass this level.

So, we’re dealing with a .NET binary. Let’s run it and see what it does:
1_memecat_battlestation_execute.gif

It appears it expects a code as input in order to shoot at the incoming cats. For decompiling and debugging .NET I use dnSpy.

Opening the executable in dnSpy we immediately notice several classes, one of which is called VictoryForm.
1_memecat_battlestation_forms.png

VictoryForm has a method called VictoryForm_Load and its decompiled source code is shown below:

private void VictoryForm_Load(object sender, EventArgs e)
{
	byte[] array = new byte[]
	{
		9,
		8,
		19,
		17,
		9,
		55,
		28,
		18,
		15,
		24,
		10,
		49,
		75,
		51,
		45,
		32,
		54,
		59,
		15,
		49,
		46,
		0,
		21,
		0,
		65,
		48,
		45,
		79,
		13,
		1,
		2
	};
	byte[] bytes = Encoding.UTF8.GetBytes(this.Arsenal);
	for (int i = 0; i < array.Length; i++)
	{
		byte[] array2 = array;
		int num = i;
		array2[num] ^= bytes[i % bytes.Length];
	}
	this.flagLabel.Text = Encoding.UTF8.GetString(array);
}

The Arsenal variable is used to decrypt the array which holds the flag. This variable is set in the Main method inside the Program class.
1_memecat_battlestation_main.png

The Arsenal string is a concatenation of the from stage1Form.WeaponCode and stage2Form.WeaponCode attributes. For stage1 the WeaponCode attribute is being set inside the FireButton_Click method.
1_memecat_battlestation_stage1.png

You can clearly see the check which needs to be passed. So the first code is “RAINBOW”. When it’s entered, Grumpy cat shoots a rainbow and kills the other cat.

The FireButton_Click method inside the stage2Form class is a little different. There is another function which performs the check.
1_memecat_battlestation_stage2a.png

Decompiling the method isValidWeaponCode reveals the following source code:
1_memecat_battlestation_stage2b.png

It takes the user input from the text box, then XORs every byte with the ASCII character ‘A’ and finally compares the result to some bytes. We can decode the expected input by XORing the expected bytes with the character ‘A’.

a = bytearray('\x03 &$-\x1e\x02 //./')
s = ''
for i in a:
    s += chr(i^ord('A'))
print s

Executing the script returns the string “Bagel_Cannon”. Running the executable again and entering the right codes provides us with the victory screen and the flag. 1_memecat_battlestation_flag.png

I also tried to get the flag statically by reimplementing the VictoryForm_Load method.

The following python code returns the flag:

key = bytearray("Bagel_Cannon,RAINBOW")
encrypted_flag = bytearray([9,8,19,17,9,55,28,18,15,24,10,49,75,51,45,32,54,59,15,49,46,0,21,0,65,48,45,79,13,1,2])

flag = ''

for i in xrange(len(encrypted_flag)):
	flag += chr(encrypted_flag[i] ^ key[i % len(key)])

print flag

And the flag is:

Kitteh_save_galixy@flare-on.com

Challenge 2 - Overlong

Task:

The secret of this next challenge is cleverly hidden. However, with the right 
approach, finding the solution will not take an <b>overlong</b> amount of time.

When you execute the binary the following message box appears:
2_overlong_execute.png

This challenge is written in C\C++, so I’ll use Ghidra for the dissasembly and decompilation.

There are only 3 functions, which I’ve already analysed and renamed with an appropriate names.
2_overlong_functions.png

The function DecodeByte takes two arguments. Each argument is a pointer to a byte. The function takes the byte from the second argument, decodes it and saves it at the address of the first argument.
2_overlong_decode_byte.png

The function DecodeBuffer has three arguments. The first argument is an empty buffer. The second argument is a source buffer and the last argument is the length of the source buffer. The function iterates through the bytes from the source buffer and calls DecodeByte on each byte. The result is saved in the empty buffer.
2_overlong_decode_buffer.png

In the main function DecodeBuffer is called with three arguments - an empty buffer with size 128 bytes, global byte array filled with bytes and length 0x1c (28 in decimal).
2_overlong_entry.png

If we check the length of the global byte array we’ll see it’s 176 bytes long, which is larger than the supplied length (28) in the DecodeBuffer function.
2_overlong_global_array.png

One way to obtain the flag is by patching the binary and fixing the bug or reimplement the algorithm without the bug.

The first way I did it is by patching the binary. Open the executable file in HEX editor and search for a sequence of bytes which correspond to the assembly instructions where the length (0x1c) is passed as an argument.
2_overlong_entry_asm.png

2_overlong_search.png

And finally change the 0x1c value to the length of the destination buffer (0x80 = 128)
2_overlong_patch.png

Now if you run the patched binary the message box contains the flag.
2_overlong_flag.png

Another way is to write a script which will decode the flag. Just extract the bytes from the global array and reimplement the decoding function.

buffer = bytearray("\xE0\x81\x89\xC0\xA0\xC1\xAE\xE0\x81\xA5\xC1\xB6\xF0\x80\x81\xA5\xE0\x81\xB2\xF0\x80\x80\xA0\xE0\x81\xA2\x72\x6F\xC1\xAB\x65\xE0\x80\xA0\xE0\x81\xB4\xE0\x81\xA8\xC1\xA5\x20\xC1\xA5\xE0\x81\xAE\x63\xC1\xAF\xE0\x81\xA4\xF0\x80\x81\xA9\x6E\xC1\xA7\xC0\xBA\x20\x49\xF0\x80\x81\x9F\xC1\xA1\xC1\x9F\xC1\x8D\xE0\x81\x9F\xC1\xB4\xF0\x80\x81\x9F\xF0\x80\x81\xA8\xC1\x9F\xF0\x80\x81\xA5\xE0\x81\x9F\xC1\xA5\xE0\x81\x9F\xF0\x80\x81\xAE\xC1\x9F\xF0\x80\x81\x83\xC1\x9F\xE0\x81\xAF\xE0\x81\x9F\xC1\x84\x5F\xE0\x81\xA9\xF0\x80\x81\x9F\x6E\xE0\x81\x9F\xE0\x81\xA7\xE0\x81\x80\xF0\x80\x81\xA6\xF0\x80\x81\xAC\xE0\x81\xA1\xC1\xB2\xC1\xA5\xF0\x80\x80\xAD\xF0\x80\x81\xAF\x6E\xC0\xAE\xF0\x80\x81\xA3\x6F\xF0\x80\x81\xAD\x00")

decoded_buffer = ''
offset = 0

i = 0
while i <= len(buffer):
    if buffer[offset] >> 3 == 0x1e:
        decoded_buffer += chr((((buffer[offset+2] & 0x3f) << 6) | (buffer[offset+3] & 0x3f)))
        offset += 4
    elif  buffer[offset] >> 4 == 0xe:
        decoded_buffer += chr((((buffer[offset+1] & 0x3f) << 6) | (buffer[offset+2] & 0x3f)))
        offset += 3
    elif buffer[offset] >> 5 == 6:
        decoded_buffer += chr((((buffer[offset] & 0x1f) << 6) | (buffer[offset+1] & 0x3f)))
        offset += 2
    else:
        decoded_buffer += chr(buffer[offset])
        offset += 1
    
    if decoded_buffer[i] == "\x00":
        print decoded_buffer
        break

    i = i + 1

Running the above python script produces the following output:

I never broke the encoding: I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com

Challenge 3 - flarebear

Task:

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.

This challenge is for reversing an Android apk file. The apk is a Tamagotchi game and we need see what conditions must be met in order to display the flag.

I had problems with running emulators inside the VM and I didn’t want to install new software on my host machine just for this challenge, so I used the online service https://appetize.io/ to run the apk.

The game starts with a menu that has options to continue game or create a new bear. When you create a new bear you need to give it a name and then the game starts. You have three options - feed, play and clean.
3_flarebear_execute.png

To decompile the apk:

  1. unzip the .apk contents
  2. use dex2jar to convert the classes.dex file to a .jar file
  3. open the newly created .jar file in jd-gui

Looking around, in the FlareBearActivity.class there’s a method called danceWithFlag. This method calls a couple of resources, obtains a key with the method getPassword, then uses this key to decrypt the resources and display them as a bitmap image.

  public final void danceWithFlag() {
    InputStream inputStream1 = getResources().openRawResource(2131427328);
    Intrinsics.checkExpressionValueIsNotNull(inputStream1, "ecstaticEnc");
    arrayOfByte1 = ByteStreamsKt.readBytes(inputStream1);
    InputStream inputStream2 = getResources().openRawResource(2131427329);
    Intrinsics.checkExpressionValueIsNotNull(inputStream2, "ecstaticEnc2");
    byte[] arrayOfByte2 = ByteStreamsKt.readBytes(inputStream2);
    String str = getPassword();
    try {
      arrayOfByte1 = decrypt(str, arrayOfByte1);
      arrayOfByte2 = decrypt(str, arrayOfByte2);
      Bitmap bitmap1 = BitmapFactory.decodeByteArray(arrayOfByte1, 0, arrayOfByte1.length);
      BitmapDrawable bitmapDrawable1 = new BitmapDrawable(getResources(), bitmap1);
      Bitmap bitmap2 = BitmapFactory.decodeByteArray(arrayOfByte2, 0, arrayOfByte2.length);
      BitmapDrawable bitmapDrawable2 = new BitmapDrawable(getResources(), bitmap2);
      dance((Drawable)bitmapDrawable1, (Drawable)bitmapDrawable2);
      return;
    } catch (Exception arrayOfByte1) {
      return;
    } 
  }

The danceWithFlag method is called from another method called setMood only when isHappy and isEcstatic both return true.

  public final void setMood() {
    if (isHappy()) {
      ((ImageView)_$_findCachedViewById(R.id.flareBearImageView)).setTag("happy");
      if (isEcstatic()) {
        danceWithFlag();
        return;
      } 
    } else {
      ((ImageView)_$_findCachedViewById(R.id.flareBearImageView)).setTag("sad");
    } 
  }

The isHappy method checks if the ratio of feed/play is between 2 and 2.5. If it is, it returns true.

  public final boolean isHappy() {
    int i = getStat('f');
    int j = getStat('p');
    double d = (i / j);
    return (d >= 2.0D && d <= 2.5D);
  }

The isEcstatic method checks the values of the mass, happy and clean states of the bear.

  public final boolean isEcstatic() {
    byte b = 0;
    int i = getState("mass", 0);
    int j = getState("happy", 0);
    int k = getState("clean", 0);
    int m = b;
    if (i == 72) {
      m = b;
      if (j == 30) {
        m = b;
        if (k == 0)
          m = 1; 
      } 
    } 
    return m;
  }

The mass, happy and clean states change in several methods, one of which is the play method.

  public final void play(@NotNull View paramView) {
    Intrinsics.checkParameterIsNotNull(paramView, "view");
    saveActivity("p");
    changeMass(-2);
    changeHappy(4);
    changeClean(-1);
    playUi();
  }

The changeX methods add the argument to the current state value.

  public final void changeMass(int paramInt) { setState("mass", getState("mass", 0) + paramInt); }
  public final void changeClean(int paramInt) { setState("clean", getState("clean", 0) + paramInt); }
  public final void changeHappy(int paramInt) { setState("happy", getState("happy", 0) + paramInt); }

The other two functions which change the these states are clean and feed.

  public final void clean(@NotNull View paramView) {
    Intrinsics.checkParameterIsNotNull(paramView, "view");
    saveActivity("c");
    removePoo();
    cleanUi();
    changeMass(0);
    changeHappy(-1);
    changeClean(6);
    setMood();
  }
  public final void feed(@NotNull View paramView) {
    Intrinsics.checkParameterIsNotNull(paramView, "view");
    saveActivity("f");
    changeMass(10);
    changeHappy(2);
    changeClean(-1);
    incrementPooCount();
    feedUi();
  }

Now to return to our isEcstatic method.

  public final boolean isEcstatic() {
    byte b = 0;
    int i = getState("mass", 0);
    int j = getState("happy", 0);
    int k = getState("clean", 0);
    int m = b;
    if (i == 72) {
      m = b;
      if (j == 30) {
        m = b;
        if (k == 0)
          m = 1; 
      } 
    } 
    return m;
  }

The mass needs to be 72, happy needs to be 30 and clean needs to be 0 for the method to return true. Also the ratio of feed/play needs to be between 2 and 2.5 for isHappy to return true. This means that we need to call the methods play, clean and feed in such order that these conditions are met. To summarize how these states change:

feed -> mass +10, happy +2, clean -1
play -> mass -2, happy +4, clean -1
clean -> mass +0, happy -1, clean +6

This can be represented as a system of linear equations:

10f - 2p + 0c = 72
2f + 4p - 1c = 30
-1f -1p + 6c = 0

This is easily solvable with wolfram alpha and representing the equations as matrices:
3_flarebear_matrix.png

The answer is f=8, p=4, and c=2 and the ratio feed/play is 8/4 which is 2 and satisfies the condition for isHappy.

Emulating the game and then clicking 8 times feed, 4 times play and 2 times clean gives us the flag.
3_flarebear_flag.png

The flag is:

th4t_was_be4rly_a_chall3nge@flare-on.com