Flare On 2023

Posted on | 1990 words | ~10mins
Security

Flare-on is a premier windows based reverse engineering CTF that has been running for 10 years now. I’ve attempted the challenge every year and I’ve not been able to go past the first challenge (which is usually a warm-up that takes about 15-20 min to solve).

However, that changed this year, when I solved two (yes TWO! 😁) challenges. By the time I reached the third challenge, I had read enough about it from people who had solved it that it was way above my skill levels at this point of time. So, I’m officially throwing the towel for this year. Hopefully, I will be able to get beyond the second challenge next year.

I’ve added the write-ups for the two challenges I’ve solved, they seem pretty simple after I’ve solved them, but I’ve also added some pitfalls and rabbit holes I went down that sucked up a lot of time.

Enjoy the read.

Challenge 1: X

The first challenge in all Flare-on editions is usually a warm-up and should not take more than 15-20 min to solve. Continuing on this theme, we get an executable to solve. Running the program shows us the following screen.

Launch screen

Looking at this, it’s obvious that we need to enter the correct two-digit combination and hit the unlock button to retrieve the flag. Relatively easy to brute-force, but since it’s a reverse engineering challenge, let’s RE.

We see that this is a .NET application,so let’s quickly load up the main executable in my favourite .NET decompiled - dotPeek. Once, we do, we narrow down to the function that handles the button click for the lock button.

Button Click Code

Immediately, we see that the magic number is 42 (we also see the hard coded flag, but let’s display it via the app). Entering 42 and hitting unlock gives us the flag : [email protected]

Flag

Challenge 2: It’s On Fire

We get an android APK for the challenge, and the flag is hidden somewhere within. Let’s load it in AVD to figure out what it does - turns out it seems to be a clone of space invaders

space invaders

Playing this obviously does not get us anywhere. So, let’s throw this into jad-x GUI to see what this is all about.

At a high level, this looks like a standard android application. Let’s dig into it though.

Looking at the resources is the first stop. Navigating to Resources → Assets → res → raw, we see the following. Not sure at this point of time if this will be significant (spoiler : This is the key 🙂 )

We also see a huge number of secrets / static hard coded values stored in Resources → res → resources.arsc → res → values →strings.xml. This opens up multiple rabbit holes - there are tons of secrets and resources here that lead down to multiple false analyses paths.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="abc_action_bar_home_description">Navigate home</string>
    <string name="abc_action_bar_up_description">Navigate up</string>
    <string name="abc_action_menu_overflow_description">More options</string>
    <string name="abc_action_mode_done">Done</string>
    <string name="abc_activity_chooser_view_see_all">See all</string>
    <string name="abc_activitychooserview_choose_application">Choose an app</string>
    <string name="abc_capital_off">OFF</string>
    <string name="abc_capital_on">ON</string>
    <string name="abc_menu_alt_shortcut_label">Alt+</string>
    <string name="abc_menu_ctrl_shortcut_label">Ctrl+</string>
    <string name="abc_menu_delete_shortcut_label">delete</string>
    <string name="abc_menu_enter_shortcut_label">enter</string>
    <string name="abc_menu_function_shortcut_label">Function+</string>
    <string name="abc_menu_meta_shortcut_label">Meta+</string>
    <string name="abc_menu_shift_shortcut_label">Shift+</string>
    <string name="abc_menu_space_shortcut_label">space</string>
    <string name="abc_menu_sym_shortcut_label">Sym+</string>
    <string name="abc_prepend_shortcut_label">Menu+</string>
    <string name="abc_search_hint">Search…</string>
    <string name="abc_searchview_description_clear">Clear query</string>
    <string name="abc_searchview_description_query">Search query</string>
    <string name="abc_searchview_description_search">Search</string>
    <string name="abc_searchview_description_submit">Submit query</string>
    <string name="abc_searchview_description_voice">Voice search</string>
    <string name="abc_shareactionprovider_share_with">Share with</string>
    <string name="abc_shareactionprovider_share_with_application">Share with %s</string>
    <string name="abc_toolbar_collapse_description">Collapse</string>
    <string name="ad">android.intent.action.DIAL</string>
    <string name="ag">AES</string>
    <string name="aias">android.intent.action.SEND</string>
    <string name="alg">AES/CBC/PKCS5Padding</string>
    <string name="androidx_startup">androidx.startup</string>
    <string name="app_name">ItsOnFire!</string>
    <string name="av">android.intent.action.VIEW</string>
    <string name="bd">Best Day of the Week?</string>
    <string name="c2">https://flare-on.com/evilc2server/report_token/report_token.php?token=</string>
    <string name="close_drawer">Close navigation menu</string>
    <string name="close_sheet">Close sheet</string>
    <string name="common_google_play_services_enable_button">Enable</string>
    <string name="common_google_play_services_enable_text">%1$s won\'t work unless you enable Google Play services.</string>
    <string name="common_google_play_services_enable_title">Enable Google Play services</string>
    <string name="common_google_play_services_install_button">Install</string>
    <string name="common_google_play_services_install_text">%1$s won\'t run without Google Play services, which are missing from your device.</string>
    <string name="common_google_play_services_install_title">Get Google Play services</string>
    <string name="common_google_play_services_notification_channel_name">Google Play services availability</string>
    <string name="common_google_play_services_notification_ticker">Google Play services error</string>
    <string name="common_google_play_services_unknown_issue">%1$s is having trouble with Google Play services. Please try again.</string>
    <string name="common_google_play_services_unsupported_text">%1$s won\'t run without Google Play services, which are not supported by your device.</string>
    <string name="common_google_play_services_update_button">Update</string>
    <string name="common_google_play_services_update_text">%1$s won\'t run unless you update Google Play services.</string>
    <string name="common_google_play_services_update_title">Update Google Play services</string>
    <string name="common_google_play_services_updating_text">%1$s won\'t run without Google Play services, which are currently updating.</string>
    <string name="common_google_play_services_wear_update_text">New version of Google Play services needed. It will update itself shortly.</string>
    <string name="common_open_on_phone">Open on phone</string>
    <string name="common_signin_button_text">Sign in</string>
    <string name="common_signin_button_text_long">Sign in with Google</string>
    <string name="default_error_message">Invalid input</string>
    <string name="default_popup_window_title">Pop-Up Window</string>
    <string name="default_web_client_id">524049307205-o7s97a5fv2sns9sspckf4so4jipijqjt.apps.googleusercontent.com</string>
    <string name="dropdown_menu">Dropdown menu</string>
    <string name="es">android.intent.extra.STREAM</string>
    <string name="f1">friday</string>
    <string name="f3">twitter://user?screen_name=CraigWeekend</string>
    <string name="fcm_fallback_notification_channel_label">Miscellaneous</string>
    <string name="file">myFile.png</string>
    <string name="fo">https://www.flare-on.com</string>
    <string name="gcm_defaultSenderId">524049307205</string>
    <string name="google_api_key">AIzaSyAITl4_Aj1Qamtb_lbG57m_YA7oHBhtBhs</string>
    <string name="google_app_id">1:524049307205:android:816f3e769403eebe93f52c</string>
    <string name="google_crash_reporting_api_key">AIzaSyAITl4_Aj1Qamtb_lbG57m_YA7oHBhtBhs</string>
    <string name="google_storage_bucket">myevilapp-2e278.appspot.com</string>
    <string name="in_progress">In progress</string>
    <string name="indeterminate">Neither checked nor unchecked</string>
    <string name="iv">abcdefghijklmnop</string>
    <string name="jn">tel:8675309</string>
    <string name="key">my_custom_key</string>
    <string name="m1">monday</string>
    <string name="m2">tel:2028675309</string>
    <string name="mc">My Channel One</string>
    <string name="mime">image/png</string>
    <string name="mn">onMessageReceived</string>
    <string name="navigation_menu">Navigation menu</string>
    <string name="nc">Notification Channel</string>
    <string name="not_selected">Not selected</string>
    <string name="oc">one-channel</string>
    <string name="off">Off</string>
    <string name="on">On</string>
    <string name="playerdata">playerscore.png</string>
    <string name="prdr">.provider</string>
    <string name="project_id">myevilapp-2e278</string>
    <string name="roll">roll</string>
    <string name="s1">saturday</string>
    <string name="s2">sunday</string>
    <string name="s3">https://youtu.be/dsgBpsNPQ50</string>
    <string name="search_menu_title">Search</string>
    <string name="selected">Selected</string>
    <string name="status_bar_notification_info_overflow">999+</string>
    <string name="t1">tuesday</string>
    <string name="t2">thursday</string>
    <string name="t3">google.streetview:panoid=gT28ssf0BB2LxZ63JNcL1w&amp;cbp=0,288,0,0,0</string>
    <string name="tab">Tab</string>
    <string name="template_percent">%1$d percent.</string>
    <string name="title">Notification!</string>
    <string name="w1">wednesday</string>
</resources> 

There’s lots of strings here that lead down various rabbit holes - and I went down quite a few - there’s a YouTube video link, a google maps link, a twitter handle, a google API key, firebase app ID etc.

Initially, I thought this was the correct path and I spent an inordinate amount of time searching for exposed GCS buckets first and then trying to trigger a firebase push notification to the device (AVD). None of this worked unfortunately. So, back to square one it was, searching through the codebase for signs of interesting code. Then I spotted something promising.

package f;

import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import androidx.compose.runtime.internal.StabilityInferred;
import androidx.core.content.FileProvider;
import com.google.android.gms.common.GoogleApiAvailabilityLight;
import com.secure.itsonfire.R;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.zip.CRC32;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import kotlin.Metadata;
import kotlin.io.FilesKt__FileReadWriteKt;
import kotlin.jvm.internal.Intrinsics;
import kotlin.ranges.IntRange;
import kotlin.text.Charsets;
import kotlin.text.StringsKt___StringsKt;
import org.jetbrains.annotations.NotNull;

@StabilityInferred(parameters = 0)
@Metadata(bv = {}, d1 = {"\u0000L\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0012\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\t\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\b\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0004\bÇ\u0002\u0018\u00002\u00020\u0001B\t\b\u0002¢\u0006\u0004\b\u001a\u0010\u001bJ*\u0010\n\u001a\u00020\u00042\u0006\u0010\u0003\u001a\u00020\u00022\b\u0010\u0005\u001a\u0004\u0018\u00010\u00042\u0006\u0010\u0007\u001a\u00020\u00062\u0006\u0010\t\u001a\u00020\bH\u0002J\u0010\u0010\r\u001a\u00020\f2\u0006\u0010\u000b\u001a\u00020\u0004H\u0002J\u0010\u0010\u0010\u001a\u00020\u00022\u0006\u0010\u000f\u001a\u00020\u000eH\u0002J\u0018\u0010\u0014\u001a\u00020\u00132\u0006\u0010\u0012\u001a\u00020\u00112\u0006\u0010\u000f\u001a\u00020\u000eH\u0002J\u001a\u0010\u0017\u001a\u0004\u0018\u00010\u00042\u0006\u0010\u0016\u001a\u00020\u00152\u0006\u0010\u0012\u001a\u00020\u0011H\u0002J\u0016\u0010\u0019\u001a\u00020\u00182\u0006\u0010\u000f\u001a\u00020\u000e2\u0006\u0010\u0012\u001a\u00020\u0011¨\u0006\u001c"}, d2 = {"Lf/b;", "", "", "algorithm", "", "input", "Ljavax/crypto/spec/SecretKeySpec;", "key", "Ljavax/crypto/spec/IvParameterSpec;", "iv", "b", "value", "", "a", "Landroid/content/Context;", "context", GoogleApiAvailabilityLight.TRACKING_SOURCE_DIALOG, "", "resourceId", "Ljava/io/File;", "c", "Landroid/content/res/Resources;", "res", "e", "Landroid/content/Intent;", "f", "<init>", "()V", "app_release"}, k = 1, mv = {1, 6, 0})
/* loaded from: classes.dex */
public final class b {
    @NotNull

    /* renamed from: a  reason: collision with root package name */
    public static final b f360a = new b();

    /* renamed from: b  reason: collision with root package name */
    public static final int f361b = 0;

    private b() {
    }

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

    private final byte[] b(String str, byte[] bArr, SecretKeySpec secretKeySpec, IvParameterSpec ivParameterSpec) {
        Cipher cipher = Cipher.getInstance(str);
        cipher.init(2, secretKeySpec, ivParameterSpec);
        byte[] doFinal = cipher.doFinal(bArr);
        Intrinsics.checkNotNullExpressionValue(doFinal, "cipher.doFinal(input)");
        return doFinal;
    }

    private final File c(int i2, Context context) {
        Resources resources = context.getResources();
        Intrinsics.checkNotNullExpressionValue(resources, "context.resources");
        byte[] e2 = e(resources, i2);
        String d2 = d(context);
        Charset charset = Charsets.UTF_8;
        byte[] bytes = d2.getBytes(charset);
        Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
        SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, context.getString(R.string.ag));
        String string = context.getString(R.string.alg);
        Intrinsics.checkNotNullExpressionValue(string, "context.getString(R.string.alg)");
        String string2 = context.getString(R.string.iv);
        Intrinsics.checkNotNullExpressionValue(string2, "context.getString(\n     …             R.string.iv)");
        byte[] bytes2 = string2.getBytes(charset);
        Intrinsics.checkNotNullExpressionValue(bytes2, "this as java.lang.String).getBytes(charset)");
        byte[] b2 = b(string, e2, secretKeySpec, new IvParameterSpec(bytes2));
        File file = new File(context.getCacheDir(), context.getString(R.string.playerdata));
        FilesKt__FileReadWriteKt.writeBytes(file, b2);
        return file;
    }

    private final String d(Context context) {
        String slice;
        String string = context.getString(R.string.c2);
        Intrinsics.checkNotNullExpressionValue(string, "context.getString(R.string.c2)");
        String string2 = context.getString(R.string.w1);
        Intrinsics.checkNotNullExpressionValue(string2, "context.getString(R.string.w1)");
        StringBuilder sb = new StringBuilder();
        sb.append(string.subSequence(4, 10));
        sb.append(string2.subSequence(2, 5));
        String sb2 = sb.toString();
        Intrinsics.checkNotNullExpressionValue(sb2, "StringBuilder().apply(builderAction).toString()");
        byte[] bytes = sb2.getBytes(Charsets.UTF_8);
        Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
        long a2 = a(bytes);
        StringBuilder sb3 = new StringBuilder();
        sb3.append(a2);
        sb3.append(a2);
        String sb4 = sb3.toString();
        Intrinsics.checkNotNullExpressionValue(sb4, "StringBuilder().apply(builderAction).toString()");
        slice = StringsKt___StringsKt.slice(sb4, new IntRange(0, 15));
        return slice;
    }

    /* JADX WARN: Multi-variable type inference failed */
    /* JADX WARN: Type inference failed for: r2v0, types: [android.content.res.Resources] */
    /* JADX WARN: Type inference failed for: r2v2 */
    /* JADX WARN: Type inference failed for: r2v4, types: [java.lang.Object, java.io.InputStream] */
    /* JADX WARN: Type inference failed for: r2v6 */
    /* JADX WARN: Type inference failed for: r2v8, types: [java.lang.Throwable, java.io.IOException] */
    private final byte[] e(Resources e2, int i2) {
        Throwable th;
        InputStream inputStream;
        try {
            try {
                inputStream = e2.openRawResource(i2);
            } catch (IOException e3) {
                e = e3;
                inputStream = null;
            } catch (Throwable th2) {
                e2 = 0;
                th = th2;
                try {
                    Intrinsics.checkNotNull(e2);
                    e2.close();
                } catch (IOException e4) {
                    e4.printStackTrace();
                }
                throw th;
            }
            try {
                byte[] bArr = new byte[inputStream.available()];
                inputStream.read(bArr);
                try {
                    Intrinsics.checkNotNull(inputStream);
                    inputStream.close();
                } catch (IOException e5) {
                    e5.printStackTrace();
                }
                return bArr;
            } catch (IOException e6) {
                e = e6;
                e.printStackTrace();
                try {
                    Intrinsics.checkNotNull(inputStream);
                    inputStream.close();
                    return null;
                } catch (IOException e7) {
                    e2 = e7;
                    e2.printStackTrace();
                    return null;
                }
            }
        } catch (Throwable th3) {
            th = th3;
            Intrinsics.checkNotNull(e2);
            e2.close();
            throw th;
        }
    }

    @NotNull
    public final Intent f(@NotNull Context context, int i2) {
        Intrinsics.checkNotNullParameter(context, "context");
        Uri uriForFile = FileProvider.getUriForFile(context, Intrinsics.stringPlus(context.getApplicationContext().getPackageName(), context.getString(R.string.prdr)), c(i2, context));
        Intent intent = new Intent(context.getString(R.string.aias));
        intent.addFlags(32768);
        intent.addFlags(268435456);
        intent.addFlags(1);
        intent.setType(context.getString(R.string.mime));
        intent.putExtra(context.getString(R.string.es), uriForFile);
        return intent;
    }
}

There’s a good amount of obfuscation happening here - the way this is done is to pick up string constants from the strings.xml and replace at runtime. As an example,

String string = context.getString(R.string.c2)

Gets de-obfuscated to

String string = "https://flare-on.com/evilc2server/report_token/report_token.php?token="

Stripping away all of the string references and unnecessary Kotlin code gives us the final working code to solve the challenge.

import java.io.File;
import java.io.IOException;
import java.nio.charset;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.CRC32;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class StringDemo {

    public static void main (String[] args) throws Exception {
    
        byte[] filebytes = ReadFiletoByteArr ("C:\\Downloads\\Flareon10\\iv.png");
        String m2238d = "4508305374508305";
        //byte[] bytes = m2238d.getBytes(Charset.forName("UTF-8"));
        byte[] bytes = m2238d.getBytes(Charset.forName("UTF-8"));
        SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, "AES");
        String string2 = "abcdefghijklmnop";
        byte[] bytes2 = string2.getBytes(Charset.forName("UTF-8"));
        byte[] doEncryptBytes = doEncrypt(filebytes, secretKeySpec, new IvParameterSpec(bytes2));
        File outputfile = new File("C:\\Downloads\\Flareon10\\playerscore.png");
        Files.write(outputfile.toPath(),doEncryptBytes);
    }

    private static byte[] ReadFiletoByteArr(String inputFile) throws IOException {

      File target = new File (inputFile);

      byte[] bArr = Files.readAllBytes(target.toPath());
    
      return bArr;
       
    }

    private static byte[] doEncrypt(byte[] bArr, SecretKeySpec secretKeySpec, IvParameterSpec ivParameterSpec) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(2, secretKeySpec, ivParameterSpec);
        byte[] doFinal = cipher.doFinal(bArr);
        return doFinal;
    }

      private static long crc32(byte[] bArr) {
        //returns the CRC32 of a byte array bArr
        CRC32 crc32 = new CRC32();
        crc32.update(bArr);
        return crc32.getValue();
    }

    /* public static m2238d(){

    String string2 = "wednesday";
    String string = "https://flare-on.com/evilc2server/report_token/report_token.php?token=";
    StringBuilder sb = new StringBuilder();

    sb.append(string.subSequence(4, 10)); // s://fl
    sb.append(string2.subSequence(2, 5));// dne

    String sb2 = sb.toString();

    System.out.println(sb2);
    
    byte[] bytes = sb2.getBytes(Charset.forName("UTF-8"));
    
    long checksum = crc32(bytes);

    StringBuilder sb3 = new StringBuilder();
    sb3.append(checksum);
    sb3.append(checksum);
    String sb4 = sb3.toString();
    
    System.out.println(sb4); // prints 450830537450830537

    //slice = StringsKt___StringsKt.slice(sb4, new IntRange(0, 15));

    // should return 4508305374508305
    
    } */
}

executing the code with the iv.png input gives us the flag - [email protected]

flag

Of course, there had to be one more rabbit-hole. Executing the same code with ps.png as the input gives

Looking at the problem after solving it, I spent an excessive amount of time in the following - these are some of the gotchas that are good for a future reference.

  1. Resources have binary files as well (like the encrypted png files here). These don’t show up readily in jadx and we need to check the decompiled files in explorer / finder
  2. Navigate the codebase to get interesting code - I’m not quite sure of how to do this much faster, but I spent an excessive amount of time in locating the code that would yield the flag. Perhaps automation could help?
  3. Check out the strings.xml first. This gives a good pointer into what’s happening internally within the app. Also, this is a good place to get lost 😞
  4. Java - I was made painfully aware of why I hate this language so much. I spent a huge amount of time to figure out why a simple function that read a file into a byte array would not compile. Turns out, I had to add a throws IOException to the function prototype for it to compile properly.

All in all, I’m happy I persisted with this challenge and finally solving it. Hopefully, I’ll move beyond this level next year.