BSides San Francisco CTF 2017 flag receiver¶
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
Then, decompile it with apktool
We can see that we have an "empty" activity. Let's inspect the source code with jadxWe can see in strings.xml some interesting harcoded strings
<string name="passphrase">LetMeIn</string>
<string name="str1">OsjPwhjaMjAzZGFmM</string>
<string name="str2">QklEsuOGNlZTRkMjEhNGUyZD</string>
<string name="str3">wgHoNi[nvVfptxF@hpsd9DhrM@sz]</string>
Here's the AndroidManifest.xml file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
package="com.flagstore.ctf.flagstore"
platformBuildVersionCode="24"
platformBuildVersionName="7">
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="24"/>
<permission
android:name="ctf.permissions._MSG"
android:protectionLevel="signature"
android:description="@string/res_0x7f060021_android_permission__msg"/>
<permission
android:name="ctf.permissionn._SEND"
android:description="@string/res_0x7f060021_android_permission__msg"/>
<application
android:theme="@style/AppTheme"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:supportsRtl="true">
<activity android:name="com.flagstore.ctf.flagstore.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="com.flagstore.ctf.flagstore.Send_to_Activity"/>
<activity android:name="com.flagstore.ctf.flagstore.CTFReceiver"/>
<receiver
android:name="com.flagstore.ctf.flagstore.Send_to_Activity"
android:exported="true"/>
</application>
</manifest>
com.flagstore.ctf.flagstore
is the package name of the android app. - Two permissions, ctf.permissions._MSG
and ctf.permissionn._SEND
- Three activities, MainActivity
, Send_to_Activity
and CTFReceiver
- 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();
}
}
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
```java
String a = CTFReceiver.this.getResources().getString(R.string.str3) + "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
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
I hope you found it useful (: