Description: Here is a simple mobile application that will hand you the flag.. if you ask for it the right way.
P.S, it is meant to have a blank landing activity 🙂 Use string starting with Flag:
Note: For this challenge, we need install some things into our Android 5.1 device with Genymotion.
For example, an ARM Translator.
https://github.com/m9rco/Genymotion_ARM_Translation
Download APK: https://lautarovculic.com/my_files/flagstore.apk
Install the apk with adb
adb install -r flagstore.apk
Then, decompile it with apktool
apktool d flagstore.apk
We can see that we have an “empty” activity.
Let’s inspect the source code with jadx
We can see in strings.xml some interesting harcoded strings
LetMeIn
OsjPwhjaMjAzZGFmM
QklEsuOGNlZTRkMjEhNGUyZD
wgHoNi[nvVfptxF@hpsd9DhrM@sz]
Keep this in mind if we need this information in next steps.
Here’s the AndroidManifest.xml file
We can see four things
com.flagstore.ctf.flagstore
is the package name of the android app.- Two permissions,
ctf.permissions._MSG
andctf.permissionn._SEND
- Three activities,
MainActivity
,Send_to_Activity
andCTFReceiver
- And one receiver called
Send_to_Activity
(used in the activity with the same name)
We can see in the Manifest class, we just can look that the permissions are called.
And in the MainActivity
public class MainActivity extends Activity {
@Override // android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(getApplicationContext());
tv.setText("To-do: UI pending");
setContentView(tv);
IntentFilter filter = new IntentFilter();
filter.addAction("com.flagstore.ctf.INCOMING_INTENT");
BroadcastReceiver receiver = new Send_to_Activity();
registerReceiver(receiver, filter, Manifest.permission._MSG, null);
}
}
We can see that the intent is used for launch the Send_to_Activity when is received.
The Send_to_Activity look like
public class Send_to_Activity extends BroadcastReceiver {
@Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
String msgText = intent.getStringExtra("msg");
if (msgText.equalsIgnoreCase("OpenSesame")) {
Log.d("Here", "Intent");
Intent outIntent = new Intent(context, (Class>) CTFReceiver.class);
context.startActivity(outIntent);
return;
}
Toast.makeText(context, "Ah, ah, ah, you didn't say the magic word!", 1).show();
}
}
We can craft an adb command with this information of both classes.
adb shell am broadcast -a com.flagstore.ctf.INCOMING_INTENT -e "msg" "OpenSesame"
Send an intent with the extra string msg, and the value is OpenSesame
And we can see that a new activity is launched
We can see just an button with the label BROADCAST, which means that we need send an Broadcast message.
Here’s the onCreate method in CTFReceiver class
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText("Clever Person!");
Button button = new Button(this);
button.setText("Broadcast");
setContentView(button);
button.setOnClickListener(new View.OnClickListener() { // from class: com.flagstore.ctf.flagstore.CTFReceiver.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("com.flagstore.ctf.OUTGOING_INTENT");
String a = CTFReceiver.this.getResources().getString(R.string.str3) + "fpcMpwfFurWGlWu`uDlUge";
String b = Utilities.doBoth(CTFReceiver.this.getResources().getString(R.string.passphrase));
String name = getClass().getName().split("\\.")[4];
String c = Utilities.doBoth(name.substring(0, name.length() - 2));
String output = CTFReceiver.this.getPhrase(a, b, c);
intent.putExtra("msg", output);
CTFReceiver.this.sendBroadcast(intent);
}
});
}
static {
System.loadLibrary("native-lib");
}
Which load an native-lib binary, that we can inspect with ghidra
In the function Java_com_flagstore_ctf_flagstore_CTFReceiver_getPhrase
We can found this piece of code
local_14 = *(int *)(in_GS_OFFSET + 0x14);
__src = (char *)(**(code **)(*param_1 + 0x2a4))(param_1,param_3,0);
__src_00 = (char *)(**(code **)(*param_1 + 0x2a4))(param_1,param_4,0);
__src_01 = (char *)(**(code **)(*param_1 + 0x2a4))(param_1,param_5,0);
local_e4 = 0x5e;
local_e8 = 0x767d726c;
local_ec = 0x50696a4d;
local_f0 = 0x655f6f42;
local_f4 = 0x77644144;
local_f8 = 0x4e454866;
local_fc = 0x487e4140;
Following the logic of the code, we get the string @A~HfHENDAdwBo_eMjiPlr}v^
Which in the java code we have
String a = CTFReceiver.this.getResources().getString(R.string.str3) + "fpcMpwfFurWGlWu`uDlUge";
In the strings.xml file, we can complete the a variable
wgHoNi[nvVfptxF@hpsd9DhrM@sz]fpcMpwfFurWGlWu`uDlUge
Using Utilities and CTFReceiver classes, I do this javascript code for frida for hook functions and values
Java.perform(function() {
// Hook to CTFReceiver and Utilities class
var CTFReceiver = Java.use("com.flagstore.ctf.flagstore.CTFReceiver");
var Utilities = Java.use("com.flagstore.ctf.flagstore.Utilities");
// Hook for intercepting the getPhrase method
CTFReceiver.getPhrase.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c) {
console.log("Intercepting getPhrase");
console.log("Value of 'a':", a);
console.log("Value of 'b':", b);
console.log("Value of 'c':", c);
// Call original method
var result = this.getPhrase(a, b, c);
console.log("getPhrase:", result);
return result;
};
// Hook for intercept the doBoth Methods from Utilities class
Utilities.doBoth.overload('java.lang.String').implementation = function(input) {
console.log("Intercepting doBoth with passphrase:", input);
var result = this.doBoth(input);
console.log("doBoth:", result);
return result;
};
// Hook for capture sendBroadcast and see the intent
var Context = Java.use("android.content.Context");
Context.sendBroadcast.overload("android.content.Intent").implementation = function(intent) {
var action = intent.getAction();
var extras = intent.getExtras();
if (action === "com.flagstore.ctf.OUTGOING_INTENT") {
console.log("Intercepting Intent with ACTION:", action);
console.log("Send Intent:", extras.getString("msg"));
}
this.sendBroadcast(intent); // Call original method
};
});
Setup frida in your emulator and then, get the process app running the broadcast view activity.
Then, run automatically
frida -U -p $(frida-ps -Uai | grep "flagstore" | awk '{print $1}') -l script.js
The -U
parameter is for attach frida to the process (-P
) that is the output of the middle command, then, run the script that I share previously.
When we press the BROADCAST button, we get the following output
[Pixel 2::PID::8893 ]-> Intercepting getPhrase
Value of 'a': wgHoNi[nvVfptxF@hpsd9DhrM@sz]fpcMpwfFurWGlWu`uDlUge
Value of 'b': NTYxMDdjZTljZTkeYhQwNmRhMDhmMzZkOGNlZTRkMjEhNGUyZDhmNDEtZTVmMhYhODAeMGMyZTU?
Value of 'c': MzIWYmUWYzgyOTFkMmMaMjAzZGFmMDViNDMyODkiODYzMDEyMzMWZmFjMjghNhYtYmIwYTAiYTA?
getPhrase: CongratsGoodWorkYouFoundIBTZGxaOEUj[Q]@MFEu]GZjMS{\wndTDzx[HighR~p|KyZ{IWA}Y
So, we have half-flag. We need complete this.
Going back to the library, I notice that in some part of the code in C from Java_com_flagstore_ctf_flagstore_CTFReceiver_getPhrase
We have this
do {
param1 = local_ae[iVar1] ^ local_61[iVar1] ^ *(byte *)((int)&local_fc + iVar1);
local_149[iVar1] = param1;
printf("%c\n",param1);
iVar1 = iVar1 + 1;
} while (iVar1 != 0x4c);
local_fd = 0;
printf("Here is your Reply: %s",(char *)local_149);
(**(code **)(*param_1 + 0x29c))(param_1,local_149);
if (*(int *)(in_GS_OFFSET + 0x14) == local_14) {
return;
}
And now we just need resolve this with python
a = "@A~HfHENDAdwBo_eMjiPlr}v^wgHoNi[nvVfptxF@hpsd9DhrM@sz]fpcMpwfFurWGlWu`uDlUge"
b = "NTYxMDdjZTljZTkeYhQwNmRhMDhmMzZkOGNlZTRkMjEhNGUyZDhmNDEtZTVmMhYhODAeMGMyZTU?"
c = "MzIWYmUWYzgyOTFkMmMaMjAzZGFmMDViNDMyODkiODYzMDEyMzMWZmFjMjghNhYtYmIwYTAiYTA?"
output = ""
for char in range(len(a)):
output += chr(ord(a[char]) ^ ord(b[char]) ^ ord(c[char]))
print(output)
Run the python script and we get the flag
CongratsGoodWorkYouFoundItIHopeYouUsedADBFlag:TheseIntentsAreFunAndEasyToUse
I hope you found it useful (:
Leave a Reply