Skip to content
Platform
android
Last updated: September 29, 2023

Frida for Android

Frida supports interaction with the Android Java runtime though the Java API. You'll be able to hook and call both Java and native functions inside the process and its native libraries. Your JavaScript snippets have full access to memory, e.g. to read and/or write any structured data.

Here are some tasks that Frida APIs offers and are relevant or exclusive on Android:

  • Instantiate Java objects and call static and non-static class methods (Java API).
  • Replace Java method implementations (Java API).
  • Enumerate live instances of specific classes by scanning the Java heap (Java API).
  • Scan process memory for occurrences of a string (Memory API).
  • Intercept native function calls to run your own code at function entry and exit (Interceptor API).

Remember that on Android, you can also benefit from the built-in tools provided when installing Frida, that includes the Frida CLI (frida), frida-ps, frida-ls-devices and frida-trace, to name some of them.

Frida is often compared to Xposed, however this comparison is far from fair as both frameworks were designed with different goals in mind. This is important to understand as an app security tester so that you can know which framework to use in which situation:

  • Frida is standalone, all you need is to run the frida-server binary from a known location in your target Android device (see "Installing Frida" below). This means that, in contrast to Xposed, it is not deep installed in the target OS.
  • Reversing an app is an iterative process. As a consequence of the previous point, you obtain a shorter feedback loop when testing as you don't need to (soft) reboot to apply or simply update your hooks. So you might prefer to use Xposed when implementing more permanent hooks.
  • You may inject and update your Frida JavaScript code on the fly at any point during the runtime of your process (similarly to Cycript on iOS). This way you can perform the so-called early instrumentation by letting Frida spawn your app or you may prefer to attach to a running app that you might have brought to a certain state.
  • Frida is able to handle both Java as well as native code (JNI), allowing you to modify both of them. This is unfortunately a limitation of Xposed which lacks of native code support.

Note that Xposed, as of early 2019, does not work on Android 9 (API level 28) yet.

Installing Frida on Android

In order to set up Frida on your Android device:

We assume a rooted device here unless otherwise noted. Download the frida-server binary from the Frida releases page. Make sure that you download the right frida-server binary for the architecture of your Android device or emulator: x86, x86_64, arm or arm64. Make sure that the server version (at least the major version number) matches the version of your local Frida installation. PyPI usually installs the latest version of Frida. If you're unsure which version is installed, you can check with the Frida command line tool:

frida --version

Or you can run the following command to automatically detect Frida version and download the right frida-server binary:

wget https://github.com/frida/frida/releases/download/$(frida --version)/frida-server-$(frida --version)-android-arm.xz

Copy frida-server to the device and run it:

adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "su -c /data/local/tmp/frida-server &"

Using Frida on Android

With frida-server running, you should now be able to get a list of running processes with the following command (use the -U option to indicate Frida to use a connected USB devices or emulator):

$ frida-ps -U
  PID  Name
-----  --------------------------------------------------------------
  276  adbd
  956  android.process.media
  198  bridgemgrd
30692  com.android.chrome
30774  com.android.chrome:privileged_process0
30747  com.android.chrome:sandboxed
30834  com.android.chrome:sandboxed
 3059  com.android.nfc
 1526  com.android.phone
17104  com.android.settings
 1302  com.android.systemui
(...)

Or restrict the list with the -Uai flag combination to get all apps (-a) currently installed (-i) on the connected USB device (-U):

$ frida-ps -Uai
  PID  Name                                      Identifier
-----  ----------------------------------------  ------------------------------
  766  Android System                            android
30692  Chrome                                    com.android.chrome
 3520  Contacts Storage                          com.android.providers.contacts
    -  Uncrackable1                              sg.vantagepoint.uncrackable1
    -  drozer Agent                              com.mwr.dz

This will show the names and identifiers of all apps, if they are currently running it will also show their PIDs. Search for your app in the list and take a note of the PID or its name/identifier. From now on you'll refer to your app by using one of them. A recommendation is to use the identifiers, as the PIDs will change on each run of the app. For example let's take com.android.chrome. You can use this string now on all Frida tools, e.g. on the Frida CLI, on frida-trace or from a Python script.

Tracing Native Libraries with frida-trace

To trace specific (low-level) library calls, you can use the frida-trace command line tool:

frida-trace -U com.android.chrome -i "open"

This generates a little JavaScript in __handlers__/libc.so/open.js, which Frida injects into the process. The script traces all calls to the open function in libc.so. You can modify the generated script according to your needs with Frida JavaScript API.

Unfortunately tracing high-level methods of Java classes is not yet supported (but might be in the future).

Frida CLI and the Java API

Use the Frida CLI tool (frida) to work with Frida interactively. It hooks into a process and gives you a command line interface to Frida's API.

frida -U com.android.chrome

With the -l option, you can also use the Frida CLI to load scripts , e.g., to load myscript.js:

frida -U -l myscript.js com.android.chrome

Frida also provides a Java API, which is especially helpful for dealing with Android apps. It lets you work with Java classes and objects directly. Here is a script to overwrite the onResume function of an Activity class:

Java.perform(function () {
    var Activity = Java.use("android.app.Activity");
    Activity.onResume.implementation = function () {
        console.log("[*] onResume() got called!");
        this.onResume();
    };
});

The above script calls Java.perform to make sure that your code gets executed in the context of the Java VM. It instantiates a wrapper for the android.app.Activity class via Java.use and overwrites the onResume function. The new onResume function implementation prints a notice to the console and calls the original onResume method by invoking this.onResume every time an activity is resumed in the app.

The JADX decompiler (v1.3.3 and above) can generate Frida snippets through its graphical code browser. To use this feature, open the APK or DEX with jadx-gui, browse to the target method, right click the method name, and select "Copy as frida snippet (f)". For example using the MASTG UnCrackable App for Android Level 1:

The above steps place the following output in the pasteboard, which you can then paste in a JavaScript file and feed into frida -U -l.

let a = Java.use("sg.vantagepoint.a.a");
a["a"].implementation = function (bArr, bArr2) {
    console.log('a is called' + ', ' + 'bArr: ' + bArr + ', ' + 'bArr2: ' + bArr2);
    let ret = this.a(bArr, bArr2);
    console.log('a ret value is ' + ret);
    return ret;
};

The above code hooks the a method within the sg.vantagepoint.a.a class and logs its input parameters and return values.

Frida also lets you search for and work with instantiated objects that are on the heap. The following script searches for instances of android.view.View objects and calls their toString method. The result is printed to the console:

setImmediate(function() {
    console.log("[*] Starting script");
    Java.perform(function () {
        Java.choose("android.view.View", {
             "onMatch":function(instance){
                  console.log("[*] Instance found: " + instance.toString());
             },
             "onComplete":function() {
                  console.log("[*] Finished heap search")
             }
        });
    });
});

The output would look like this:

[*] Starting script
[*] Instance found: android.view.View{7ccea78 G.ED..... ......ID 0,0-0,0 #7f0c01fc app:id/action_bar_black_background}
[*] Instance found: android.view.View{2809551 V.ED..... ........ 0,1731-0,1731 #7f0c01ff app:id/menu_anchor_stub}
[*] Instance found: android.view.View{be471b6 G.ED..... ......I. 0,0-0,0 #7f0c01f5 app:id/location_bar_verbose_status_separator}
[*] Instance found: android.view.View{3ae0eb7 V.ED..... ........ 0,0-1080,63 #102002f android:id/statusBarBackground}
[*] Finished heap search

You can also use Java's reflection capabilities. To list the public methods of the android.view.View class, you could create a wrapper for this class in Frida and call getMethods from the wrapper's class property:

Java.perform(function () {
    var view = Java.use("android.view.View");
    var methods = view.class.getMethods();
    for(var i = 0; i < methods.length; i++) {
        console.log(methods[i].toString());
    }
});

This will print a very long list of methods to the terminal:

public boolean android.view.View.canResolveLayoutDirection()
public boolean android.view.View.canResolveTextAlignment()
public boolean android.view.View.canResolveTextDirection()
public boolean android.view.View.canScrollHorizontally(int)
public boolean android.view.View.canScrollVertically(int)
public final void android.view.View.cancelDragAndDrop()
public void android.view.View.cancelLongPress()
public final void android.view.View.cancelPendingInputEvents()
...

Resources