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

Flag Store

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

				
					<string name="passphrase">LetMeIn</string>  
<string name="str1">OsjPwhjaMjAzZGFmM</string>  
<string name="str2">QklEsuOGNlZTRkMjEhNGUyZD</string>  
<string name="str3">wgHoNi[nvVfptxF@hpsd9DhrM@sz]</string>
				
			

Keep this in mind if we need this information in next steps.

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>
				
			

We can see four things

  • com.flagstore.ctf.flagstore is the package name of the android app.
  • Two permissions, ctf.permissions._MSG and ctf.permissionn._SEND
  • Three activities, MainActivitySend_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();  
    }  
}
				
			

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

Your email address will not be published. Required fields are marked *