MASTG-TOOL-0001: 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:
- If your device is not rooted, you can also use Frida, please refer to Dynamic Analysis on Non-Rooted Devices.
- If you have a rooted device, simply follow the official instructions or follow the hints below.
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.
jadx 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 Android UnCrackable L1:
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()
...