Android App Penetration Testing & Frida Hooks

Android RE Lab: Tracing & Manipulating startActivity Lifecycle Events with Frida

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: The Power of Runtime Manipulation

Android reverse engineering often involves understanding how applications behave at runtime. While static analysis provides insights into code structure, dynamic analysis with tools like Frida allows us to observe and even alter an app’s execution flow in real-time. This lab focuses on a critical component of Android application navigation: the startActivity method. By hooking and manipulating startActivity using Frida, we can trace activity launches, inspect the Intents being passed, and even redirect or prevent activity transitions, offering powerful capabilities for penetration testing, security research, and debugging.

Understanding and manipulating startActivity events provides a foundational skill for deeper dives into Android application behavior, enabling us to bypass restrictions, explore hidden functionalities, or test injection vectors.

Prerequisites and Lab Setup

Tools You’ll Need

  • Frida: The dynamic instrumentation toolkit. Ensure frida-server is running on your Android device/emulator and frida-tools are installed on your host machine.
  • ADB (Android Debug Bridge): For interacting with your Android device.
  • A Rooted Android Device or Emulator: Necessary for running frida-server.
  • Target Android Application: For this lab, we’ll assume a simple application with at least two activities, say MainActivity and SecondActivity. A button in MainActivity triggers startActivity(new Intent(this, SecondActivity.class)).
  • Android SDK (Optional but Recommended): For access to adb logcat and device management.

Setting up Our Target Application

For demonstration, let’s consider a minimal Android application. Imagine a MainActivity with a button that, when clicked, launches SecondActivity. This provides a clear target for our startActivity hooks.

// MainActivity.java snippetpublic class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.button_next).setOnClickListener(view -> {            Intent intent = new Intent(MainActivity.this, SecondActivity.class);            intent.putExtra("message", "Hello from MainActivity!");            startActivity(intent);        });    }}// SecondActivity.java snippetpublic class SecondActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_second);        String message = getIntent().getStringExtra("message");        // Display message or handle it    }}

Diving into startActivity: A Brief Overview

In Android, startActivity is the primary method used to launch a new activity. It takes an Intent object as an argument, which describes the action to be performed (e.g., `ACTION_VIEW`, `ACTION_SEND`) and the data to operate on, including the component (the target activity’s class name) if an explicit Intent is used. Understanding the contents of the Intent is crucial for manipulating activity flow.

Phase 1: Tracing startActivity Invocations

Our first goal is to observe every call to startActivity, inspect the Intent, and identify where the call originated from within the application’s code.

Identifying the Target Method

The startActivity method is overloaded, but the most common one for explicit activity launching is startActivity(Intent intent), often called from an Activity or Context instance.

Frida Script for Tracing

Let’s create a Frida script, trace_startactivity.js, to hook this method:

// trace_startactivity.jsconsole.log("Frida script loaded: Tracing startActivity");Java.perform(function () {    var Activity = Java.use('android.app.Activity');    Activity.startActivity.overload('android.content.Intent').implementation = function (intent) {        console.log("-----------------------------------------------------");        console.log("[+] startActivity called!");        // Cast the Intent object to its Java counterpart for full access        var Intent = Java.use('android.content.Intent');        var currentIntent = Java.cast(intent, Intent);        console.log("    Intent Action: " + currentIntent.getAction());        console.log("    Intent Component: " + currentIntent.getComponent());        console.log("    Intent Data: " + currentIntent.getData());        console.log("    Intent Extras: " + currentIntent.getExtras());        // Get a stack trace to see who called startActivity        var stackTrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());        console.log("    Call Stack:n" + stackTrace);        console.log("-----------------------------------------------------");        // Call the original method to ensure the activity still launches        this.startActivity(intent);    };    console.log("Hooked android.app.Activity.startActivity(Intent intent).");});

Executing the Trace

1. Ensure frida-server is running on your device (e.g., adb shell /data/local/tmp/frida-server).
2. Forward the Frida port: adb forward tcp:27042 tcp:27042 (if needed for remote connection).
3. Run Frida, attaching to your target app’s package name:

frida -U -l trace_startactivity.js -f com.example.your_app_package --no-pause

Now, interact with your application. Whenever startActivity is called (e.g., by clicking the button in MainActivity), you will see detailed output in your console, showing the Intent’s contents and the call stack, helping you pinpoint the exact source of the activity launch.

Phase 2: Manipulating startActivity Intents

Now, let’s get more aggressive and modify the Intent object before startActivity is called. We’ll explore two scenarios: redirecting to a different activity and preventing the launch altogether.

Modifying the Intent’s Target Component

Let’s say our target app has a hidden activity, com.example.your_app_package.HiddenActivity, that is never explicitly launched. We can redirect the Intent to it.

// redirect_startactivity.jsconsole.log("Frida script loaded: Redirecting startActivity");Java.perform(function () {    var Activity = Java.use('android.app.Activity');    Activity.startActivity.overload('android.content.Intent').implementation = function (intent) {        console.log("-----------------------------------------------------");        console.log("[+] Original startActivity called!");        var Intent = Java.use('android.content.Intent');        var ComponentName = Java.use('android.content.ComponentName');        var currentIntent = Java.cast(intent, Intent);        var originalComponent = currentIntent.getComponent();        console.log("    Original Component: " + originalComponent);        // Create a new ComponentName pointing to our desired activity        var newComponent = ComponentName.$new('com.example.your_app_package', 'com.example.your_app_package.HiddenActivity');        // Set the new component on the original intent        currentIntent.setComponent(newComponent);        // You can also add/modify extras        currentIntent.putExtra("redirected_by_frida", true);        console.log("    Redirected to Component: " + newComponent);        console.log("    New Intent Extras: " + currentIntent.getExtras());        // Call the original method with the modified intent        this.startActivity(currentIntent);        console.log("-----------------------------------------------------");    };    console.log("Hooked and ready to redirect android.app.Activity.startActivity.");});

Preventing Activity Launch

Sometimes, we might want to prevent an activity from launching. This is useful for bypassing checks or preventing unwanted transitions.

// prevent_startactivity.jsconsole.log("Frida script loaded: Preventing startActivity");Java.perform(function () {    var Activity = Java.use('android.app.Activity');    Activity.startActivity.overload('android.content.Intent').implementation = function (intent) {        console.log("-----------------------------------------------------");        console.log("[+] startActivity called! Preventing launch.");        var Intent = Java.use('android.content.Intent');        var currentIntent = Java.cast(intent, Intent);        console.log("    Attempted to launch: " + currentIntent.getComponent());        console.log("-----------------------------------------------------");        // Simply return without calling the original method to prevent launch        // return;    };    console.log("Hooked and ready to prevent android.app.Activity.startActivity.");});

Executing the Manipulation

To run these manipulation scripts, follow the same Frida execution steps as with tracing, but replace trace_startactivity.js with either redirect_startactivity.js or prevent_startactivity.js:

frida -U -l redirect_startactivity.js -f com.example.your_app_package --no-pause

Observe how the application’s flow changes. With redirect_startactivity.js, clicking the button in MainActivity should now launch HiddenActivity instead of SecondActivity. With prevent_startactivity.js, the button click will simply do nothing in terms of activity transition, as the startActivity call is intercepted and dropped.

Advanced Considerations

  • Overloaded Methods: Be aware that startActivity has many overloads (e.g., with Bundle options, or for results). You might need to hook multiple overloads using .overload('*') and then differentiate based on argument types, or hook specific ones.
  • Conditional Logic: Implement conditional logic within your Frida hooks to only modify/prevent startActivity calls under specific circumstances (e.g., based on the target ComponentName or specific Intent extras).
  • Persistence: For more persistent changes, consider patching the APK directly, but Frida offers a non-intrusive way to experiment rapidly.
  • Context vs. Activity: startActivity is also a method of android.content.Context. Depending on the app’s implementation, you might need to hook ContextWrapper.startActivity or specific `Activity` subclasses.

Conclusion

This lab demonstrates the immense power of Frida for dynamic instrumentation of Android applications. By focusing on startActivity, we’ve learned how to observe critical navigation events, extract valuable information from Intent objects, and fundamentally alter the application’s runtime behavior by redirecting or preventing activity launches. These techniques are invaluable for anyone performing security audits, penetration testing, or complex debugging of Android applications, opening doors to uncover hidden functionalities, bypass security mechanisms, and gain deeper control over an app’s execution.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner