Introduction to Frida for Mobile App Analysis
Frida is a dynamic instrumentation toolkit that allows developers and security researchers to inject JavaScript or C-like code into running processes on various platforms, including Android. Its power lies in its ability to hook into functions, inspect memory, and modify execution flow at runtime, making it an invaluable tool for reverse engineering, debugging, and penetration testing of mobile applications. While traditional proxy tools like Burp Suite are excellent for intercepting network traffic, they often fall short when applications implement SSL pinning or custom encryption mechanisms. This is where Frida shines, allowing us to bypass these controls by interacting directly with the application’s memory and execution context.
This guide will walk you through the process of setting up Frida, identifying target methods, and writing scripts to capture sensitive network request details and session tokens that reside in an Android app’s memory during runtime.
Prerequisites and Setup
Setting up your Android Environment
To follow this tutorial, you’ll need an Android device or emulator with root access. This is essential for running the Frida server with the necessary permissions.
- Rooted Android Device/Emulator: Ensure you have a rooted device or an emulator like Genymotion or Android Studio’s AVD with root enabled.
- ADB (Android Debug Bridge): Make sure ADB is installed on your host machine and configured correctly. You can verify it by running:
adb devices
If your device appears, you’re good to go. For rooted devices, you might need to run adb root to switch to root shell if the daemon is not already running as root.
Installing Frida
Frida consists of two main components: the Frida server, which runs on the target Android device, and Frida-tools, a Python package for your host machine.
- Frida Server on Android: Download the appropriate Frida server binary for your device’s architecture (e.g.,
frida-server-*-android-arm64) from the Frida releases page.
Push the server to your device, make it executable, and run it:
# Replace <architecture> with your device's architecture (e.g., arm64) and <version> with the downloaded version.cd /path/to/downloaded/frida-serveradb push frida-server-<version>-android-<architecture> /data/local/tmp/frida-serveradb shell "chmod 755 /data/local/tmp/frida-server"adb shell "/data/local/tmp/frida-server &"
- Frida-tools on Host: Install the Python tools on your host machine:
pip install frida-tools
Verify installation by listing running processes on your device:
frida-ps -U
The Challenge: Encrypted Traffic and Obfuscated Storage
Modern Android applications often employ robust security measures. SSL pinning prevents proxy tools from intercepting encrypted HTTPS traffic by validating server certificates. Sensitive data like session tokens might be stored in obfuscated or encrypted forms within SharedPreferences, SQLite databases, or custom storage mechanisms. Furthermore, crucial data might only exist transiently in memory, making direct extraction challenging without runtime instrumentation. Frida allows us to bypass SSL pinning, decrypt traffic at the application layer, and grab tokens directly from memory before or after they are encrypted/decrypted.
Identifying Target Code with Static and Dynamic Analysis
Before writing a Frida script, you need to know what to hook. This involves analyzing the application to pinpoint relevant classes and methods.
Static Analysis (Decompilation)
Tools like JADX or Ghidra can decompile APKs into Java or Smali code. Look for keywords such as token, session, auth, http, okhttp, retrofit, encrypt, decrypt, SharedPreferences, SQLiteOpenHelper, or specific package names related to networking (e.g., okhttp3, org.apache.http) or authentication. This helps you identify potential classes responsible for network requests or token management.
Dynamic Analysis (Frida Enumeration)
Frida can also help discover classes and methods at runtime. You can attach to an app and enumerate loaded classes:
Java.perform(function () { Java.enumerateLoadedClasses({ onMatch: function (className) { if (className.includes("okhttp") || className.includes("auth") || className.includes("session")) { console.log(className); } }, onComplete: function () { console.log("Class enumeration complete!"); } });});
Save this as enum_classes.js and run it:
frida -U -f com.example.app -l enum_classes.js --no-pause
Replace com.example.app with your target package name. This will print relevant class names that you can then investigate further.
Capturing Network Request Details with Frida
Many Android apps use OkHttp for networking. We can hook into its internal methods to capture requests and responses.
Hooking OkHttp for Request/Response Inspection
A good target is the okhttp3.Request$Builder.build() method for outgoing requests and okhttp3.Callback.onResponse() or okhttp3.RealCall.getResponseWithInterceptorChain() for incoming responses.
Here’s a script to capture request details:
Java.perform(function () { var RequestBuilder = Java.use("okhttp3.Request$Builder"); RequestBuilder.build.implementation = function () { var request = this.build(); console.log("n[+] Intercepted Request:"); console.log(" URL: " + request.url()); console.log(" Method: " + request.method()); console.log(" Headers:n" + request.headers()); var requestBody = request.body(); if (requestBody) { try { var buffer = Java.use("okio.Buffer").$new(); requestBody.writeTo(buffer); console.log(" Body: " + buffer.readUtf8()); } catch (e) { console.log(" Body (Error reading): " + e.message); } } return request; }; var Callback = Java.use("okhttp3.Callback"); Callback.onResponse.implementation = function (call, response) { console.log("n[+] Intercepted Response:"); console.log(" URL: " + response.request().url()); console.log(" Code: " + response.code()); console.log(" Headers:n" + response.headers()); // Read response body. Note: response.body().string() consumes the body, handle with care. // For simplicity, we'll read it once here. Real apps might need cloning. try { var responseBody = response.body().string(); console.log(" Body: " + responseBody); } catch (e) { console.log(" Body (Error reading): " + e.message); } this.onResponse(call, response); // Call original method };});
Save this as okhttp_monitor.js and run it with frida -U -f com.example.app -l okhttp_monitor.js --no-pause. As the app makes network requests, you’ll see the details printed to your console.
Extracting Session Tokens from Memory
Session tokens can be stored in various places. We’ll look at SharedPreferences and a hypothetical custom authenticator class.
Targeting Common Storage Mechanisms
Tokens are frequently stored in SharedPreferences, passed as arguments to methods, or held as fields within singleton classes like an AuthManager or SessionHandler. Your static analysis will guide you to these locations.
Example: Intercepting SharedPreferences
We can hook SharedPreferences.Editor.putString() when a token is being saved, and SharedPreferences.getString() when it’s being retrieved.
Java.perform(function () { // Hook SharedPreferences.Editor.putString var Editor = Java.use("android.content.SharedPreferences$Editor"); Editor.putString.overload('java.lang.String', 'java.lang.String').implementation = function (key, value) { if (key.includes("token") || key.includes("session") || key.includes("jwt")) { console.log("[SharedPreferences] PUT: Key='" + key + "', Value='" + value + "'"); } return this.putString(key, value); // Call original method }; // Hook SharedPreferences.getString var SharedPreferences = Java.use("android.content.SharedPreferences"); SharedPreferences.getString.overload('java.lang.String', 'java.lang.String').implementation = function (key, defValue) { var value = this.getString(key, defValue); // Call original method first if (key.includes("token") || key.includes("session") || key.includes("jwt")) { console.log("[SharedPreferences] GET: Key='" + key + "', Value='" + value + "'"); } return value; };});
Example: Dumping a Custom Authenticator Class Field
If your static analysis reveals a class like com.example.app.security.Authenticator that holds a sessionToken field, you can hook its constructor or a method that sets the token. For private fields, reflection might be necessary.
Java.perform(function () { try { var CustomAuthenticator = Java.use("com.example.app.security.Authenticator"); CustomAuthenticator.$init.implementation = function () { this.$init(); // Call original constructor console.log("[Authenticator] Instance created."); // After object creation, if 'sessionToken' is a private field, // we need to access it via reflection. var instance = this; // Wait a bit for the token to potentially be set after construction setTimeout(function () { try { var tokenField = CustomAuthenticator.class.getDeclaredField("sessionToken"); tokenField.setAccessible(true); // Bypass private access control var sessionToken = tokenField.get(instance); if (sessionToken) { console.log("[Authenticator] Extracted Session Token: " + sessionToken.toString()); } } catch (e) { console.log("[Authenticator] Error accessing sessionToken field: " + e.message); } }, 1000); // Adjust delay as needed }; // Or hook a method that explicitly sets the token, e.g., setSessionToken(String token) CustomAuthenticator.setSessionToken.implementation = function(token) { console.log("[Authenticator] setSessionToken called with: " + token); this.setSessionToken(token); // Call original method }; } catch (e) { console.log("Error hooking CustomAuthenticator: " + e.message); }});
Executing the Script and Analyzing Results
Once you’ve crafted your Frida script (e.g., combined_monitor.js combining network and token hooks), execute it against your target application:
frida -U -f com.example.app -l combined_monitor.js --no-pause
Interact with the application, perform actions that involve network requests or authentication, and observe the output in your terminal. You should see detailed information about intercepted requests, responses, and extracted session tokens. The --no-pause flag ensures the app starts immediately after Frida attaches, which is often necessary for capturing early initialization activities.
Conclusion
Frida is an exceptionally powerful tool for Android app penetration testing and reverse engineering. By leveraging its runtime instrumentation capabilities, you can bypass common security controls like SSL pinning, inspect encrypted network traffic at the application layer, and extract sensitive data such as session tokens directly from memory. This level of control provides unparalleled insight into an application’s internal workings, making it indispensable for identifying and exploiting vulnerabilities that would otherwise remain hidden. Always use these techniques ethically and with proper authorization.
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 →