Doremi - BSIDESF CTF Writeup
Walkthrough for the Doremi Android reverse engineering challenge from BSIDESF CTF.
Challenge Info
| Field | Details |
|---|---|
| CTF | BSIDESF |
| Challenge | Doremi |
| Category | Android / Reverse Engineering |
Description
1
2
3
4
Mobile 101
Can you find the flag on the app?
Author: itsc0rg1
Solution
Overview
We can start by decompiling the APK in jadx,The APK is written in Kotlin, and has 2 classes.
- MainActivity
- FrameActivity
MainActivity
This is the decompiled code of MainActivity.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final class MainActivity extends ComponentActivity {
public static final int $stable = 8;
@Override // androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable$default(this, null, null, 3, null);
setContentView(R.layout.activity_main);
Button startButton = (Button) findViewById(R.id.buttonEnter);
startButton.setOnClickListener(new View.OnClickListener() { // from class: com.bsidessf.doremi.MainActivity$$ExternalSyntheticLambda0
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
MainActivity.onCreate$lambda$0(this.f$0, view);
}
});
}
static final void onCreate$lambda$0(MainActivity this$0, View it) {
Intent intent = new Intent(this$0, (Class<?>) FrameActivity.class);
this$0.startActivity(intent);
}
}
The MainActivty is simple, it is just a Welcome Screen with a button.When clicked on that button, an intent is created to start the FrameActivity
FrameActivity
This is the decompiled code of FrameActivity.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class FrameActivity extends ComponentActivity {
public static final int $stable = 8;
@Override // androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable$default(this, null, null, 3, null);
setContentView(R.layout.activity_frame);
List imageViewIds = CollectionsKt.listOf((Object[]) new Integer[]{Integer.valueOf(R.id.imageOneCTF), Integer.valueOf(R.id.imageTwoCTF), Integer.valueOf(R.id.imageThreeCTF), Integer.valueOf(R.id.imageFourCTF), Integer.valueOf(R.id.imageFiveCTF)});
List drawableIds = CollectionsKt.listOf((Object[]) new Integer[]{Integer.valueOf(R.drawable.aria_with_bg), Integer.valueOf(R.drawable.sonnet_with_bg), Integer.valueOf(R.drawable.cadence_with_bg), Integer.valueOf(R.drawable.lyra_with_bg), Integer.valueOf(R.drawable.tempo_with_bg)});
for (Pair pair : CollectionsKt.zip(imageViewIds, drawableIds)) {
int iIntValue = ((Number) pair.component1()).intValue();
int iIntValue2 = ((Number) pair.component2()).intValue();
ImageView imageView = (ImageView) findViewById(iIntValue);
imageView.setImageResource(iIntValue2);
Drawable drawable = imageView.getDrawable();
Intrinsics.checkNotNull(drawable, "null cannot be cast to non-null type android.graphics.drawable.LayerDrawable");
((LayerDrawable) drawable).findDrawableByLayerId(R.id.bg).setTint(0);
}
}
}
Again, no flag logic in this activity.Since, there is no flag logic in java or native lib, the flag must in hidden somewhere in the assets with which the APK is shipped.
We see in this activity that this loads 6 drawables (images) to ImagesViews. These files are available as .xml files in res/drawable, which we can inspect in jadx.
Sus Images
Instead of usual formats like .png or .jpeg , Android stores these images in .xml file
In this case , it looks like :
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
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:height="1920dp"
android:width="1080dp"
android:viewportWidth="1080"
android:viewportHeight="1920">
<path
android:fillColor="#551f7a"
android:pathData="M720 444L729.5 444L730 444.5L730 527L720.5 527L720 526.5L720 444Z"
android:strokeColor="#551f7a"
android:strokeWidth="1"
android:strokeAlpha="0.898"
android:fillAlpha="0.898"/>
<path
android:fillColor="#551f7a"
android:pathData="M764 449L771.5 449L772 449.5L772 527L762.5 527L762 526.5L762 471L744 471L744 461L758.5 461L762 457.5L764 449Z"
android:strokeColor="#551f7a"
android:strokeWidth="1"
android:strokeAlpha="0.898"
android:fillAlpha="0.898"/>
<path
android:fillColor="#551f7a"
android:pathData="M678.5 467L690.5 469L701 477.5L703 486L693.5 486L693 483.5L688.5 478L681.5 476L676.5 476L671.5 477L667 480.5L666 486.5L671.5 491L691.5 496L702 503.5Q705.5 507.5 704 516.5L698.5 524L689.5 528L677.5 529L668.5 527L659 520.5Q655 516.3 655 509L664.5 509L665 511.5L670.5 518L677.5 520L684.5 520Q691.1 519.1 694 514.5L695 511.5L690.5 505L682.5 502L667.5 499L658 491.5Q655.3 488.2 656 481.5Q657.8 474.3 663.5 471L670.5 468Q676.8 469.8 678.5 467Z"
android:strokeColor="#551f7a"
android:strokeWidth="1"
android:strokeAlpha="0.898"
android:fillAlpha="0.898"/>
<path
android:fillColor="#551f7a"
android:pathData="M817.5 467L832.5 470L844 479.5L848 490L838.5 490L838 488.5L830.5 480L822.5 477L814.5 477Q806.2 479.1 802 485.5Q797.4 491.4 799 503.5Q801.2 511.9 807.5 516Q812.6 520.3 823.5 519Q831.9 516.9 836 510.5L839.5 505L848 505L848 508.5Q844.5 517.5 837.5 523L827.5 528L815.5 529L801.5 524L794 516.5L789 504.5L789 491.5Q792 479.5 800.5 473L807.5 469L817.5 467Z"
android:strokeColor="#551f7a"
android:strokeWidth="1"
android:strokeAlpha="0.898"
android:fillAlpha="0.898"/>
<path
android:fillColor="#551f7a"
android:pathData="M888.5 467L900.5 469Q908.6 471.9 913 478.5L918 487.5L920 502L870.5 502L870 505.5Q872.3 512.3 877.5 516Q883 521 895.5 519Q903.7 516.7 907.5 510L917.5 510L917 511.5Q914.4 518.9 908.5 523L898.5 528L885.5 529Q873.4 527.1 867 519.5Q857.5 511 860 490.5Q862.9 477.9 872.5 472L881.5 468Q887 469.5 888.5 467ZM885 477L877 481L870 491L871 493L909 493L906 485Q900 476 885 477Z"
android:strokeColor="#551f7a"
android:strokeWidth="1"
android:strokeAlpha="0.898"
android:fillAlpha="0.898"/>
<path
android:fillColor="#8dc63f"
android:pathData="M727 581L764.5 581Q774.6 583.4 780 590.5Q786 597.5 784 612.5Q782 622.5 774.5 627L767 630L785 659L772.5 659L772 657.5L753.5 631L738 631L738 659L727 659L727 581ZM738 591L738 621L762 621L769 618L773 612L773 601L772 599Q769 593 762 591L738 591Z"
android:strokeColor="#8dc63f"
android:strokeWidth="1"
android:strokeAlpha="0.898"
android:fillAlpha="0.898"/>
<path
android:fillColor="#8dc63f"
android:pathData="M818.5 599Q837 598.5 845 608.5L852 622.5L853 634L803.5 634L803 636.5Q804.5 643.4 809.5 647Q814.8 652.7 827.5 651Q835.1 649.1 839 643.5L839.5 642L851 642Q848.9 651.3 840.5 656L827.5 661L817.5 661Q804.6 658.4 798 649.5L793 639.5L792 627.5L794 618.5L802.5 606L810.5 601L818.5 599ZM819 609L812 612Q805 616 803 624L804 625L842 625L838 616Q834 610 827 609L819 609Z"
android:strokeColor="#8dc63f"
android:strokeWidth="1"
android:strokeAlpha="0.898"
android:fillAlpha="0.898"/>
</vector>
It is not possible to decode the image in this format, we must convert the image vector to svg
For that we can use a online tool
link:https://vd.floo.app/
From jadx we can export
- aria.xml
- cadence.xml
- lyra.xml
- rythm.xml
- sonnect.xml
- tempo.xml
And then upload these individually to the converter.After converting, we see the flag parts written in purple text in each svg files.
- aria.svg -> CTF{
- candence.svg -> sl1ce
- lyra.svg -> and
- sonnet.svg -> d1ce
- tempi -> th3m}
We can combine these to get the flag
Flag
1
CTF{sl1ceandd1ceth3m}