Hacktricks-skills frida-android-pentesting
How to use Frida for Android app security testing and reverse engineering. Use this skill whenever the user needs to hook Android Java methods, intercept function calls, modify runtime behavior, find class instances, or create Python-Frida automation scripts. Trigger for any Android pentesting task involving Frida, Java method hooking, runtime instrumentation, or dynamic analysis of APKs.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/mobile-pentesting/android-app-pentesting/frida-tutorial/frida-tutorial-2/SKILL.MDFrida Android Pentesting
A skill for performing dynamic analysis and security testing on Android applications using Frida.
Core Concepts
Frida is a dynamic instrumentation toolkit that lets you inject JavaScript into running processes. For Android, this means you can hook Java methods, intercept calls, modify behavior, and extract data at runtime.
Quick Start
Basic Setup
-
Install Frida on your machine:
pip install frida frida-tools -
Install Frida Server on the Android device:
adb push frida-server-<version>-android-x86 /data/local/tmp/frida-server adb shell chmod 755 /data/local/tmp/frida-server adb shell /data/local/tmp/frida-server & -
Connect and spawn the target app:
frida-ps -U # List running processes frida -U -f com.example.targetapp -l hook.js -H 127.0.0.1:27042
Hooking Patterns
Pattern 1: Hook Methods with Overloads
When a method has multiple overloads (same name, different parameters), you must specify which one to hook:
Java.perform(function() { var myClass = Java.use("com.example.target.MyClass"); // Hook method with specific parameter types myClass.methodName.overload("int", "int").implementation = function(a, b) { console.log("Hooked: methodName(" + a + ", " + b + ")"); // Call original with modified parameters return this.methodName(10, 20); }; // Hook method with String parameter myClass.methodName.overload("java.lang.String").implementation = function(str) { console.log("Original string: " + str); return this.methodName("modified value"); }; });
Pattern 2: Find and Call Class Instances
Use
Java.choose() to find instances of a class and call methods on them:
Java.perform(function() { Java.choose("com.example.target.MyClass", { onMatch: function(instance) { console.log("Found instance: " + instance); // Call a method on the instance var result = instance.privateMethod(); console.log("Result: " + result); }, onComplete: function() { console.log("Search complete"); } }); });
Pattern 3: Create New Objects
To create new Java objects in Frida:
Java.perform(function() { var stringClass = Java.use("java.lang.String"); var newString = stringClass.$new("My custom string"); // Or for other classes var myClass = Java.use("com.example.target.MyClass"); var instance = myClass.$new(); });
Pattern 4: Expose Functions to Python
Use
rpc.exports to make JavaScript functions callable from Python:
function myHookFunction() { Java.perform(function() { var myClass = Java.use("com.example.target.MyClass"); myClass.secretMethod.overload().implementation = function() { return "hacked value"; }; }); } function callInstanceMethod() { Java.perform(function() { Java.choose("com.example.target.MyClass", { onMatch: function(instance) { console.log(instance.privateMethod()); } }); }); } rpc.exports = { myHookFunction: myHookFunction, callInstanceMethod: callInstanceMethod };
Pattern 5: Python-JS Communication
Bidirectional communication using
send() and recv():
JavaScript side:
Java.perform(function() { var textViewClass = Java.use("android.widget.TextView"); textViewClass.setText.overload("java.lang.CharSequence").implementation = function(text) { // Send data to Python send(text.toString()); // Wait for response from Python var response = recv(function(msg) { return msg.modified_data; }).wait(); console.log("Received: " + response); return this.setText(response); }; });
Python side:
import frida import base64 import time def message_handler(message, data): if message["type"] == "send": payload = message["payload"] # Process and respond modified = payload.replace("old", "new") script.post({"modified_data": modified}) device = frida.get_usb_device() pid = device.spawn(["com.example.target"]) device.resume(pid) time.sleep(1) # Critical: Java.perform needs time to initialize session = device.attach(pid) script = session.create_script(open("hook.js").read()) script.on("message", message_handler) script.load()
Python Loader Template
Use this as a starting point for Frida automation:
#!/usr/bin/env python3 import frida import time import sys def main(): if len(sys.argv) < 2: print("Usage: python loader.py <package_name>") sys.exit(1) package_name = sys.argv[1] # Get USB device device = frida.get_usb_device() # Spawn the app (or attach to running process) pid = device.spawn([package_name]) device.resume(pid) # Wait for Java VM to initialize time.sleep(2) # Attach and load script session = device.attach(pid) script = session.create_script(open("hook.js").read()) # Optional: Add message handler def message_handler(message, data): print(f"[+] {message}") script.on("message", message_handler) script.load() # Keep script running try: while True: time.sleep(1) except KeyboardInterrupt: print("\n[!] Stopping...") script.unload() if __name__ == "__main__": main()
Common Use Cases
Intercepting Sensitive Data
Java.perform(function() { // Hook SharedPreferences var prefs = Java.use("android.content.SharedPreferences"); prefs.getString.overload("java.lang.String", "java.lang.String").implementation = function(key, def) { console.log("SharedPreferences.get: " + key); return this.getString(key, def); }; // Hook network calls var urlConnection = Java.use("java.net.HttpURLConnection"); urlConnection.connect.overload().implementation = function() { console.log("URL: " + this.getURL()); return this.connect(); }; });
Bypassing SSL Pinning
Java.perform(function() { var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager"); var trustAll = { checkClientTrusted: function(chain, authType) {}, checkServerTrusted: function(chain, authType) {}, getAcceptedIssuers: function() { return []; } }; var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl"); TrustManagerImpl.checkServerTrusted.overload( "[Ljavax.net.ssl.X509Certificate;", "java.lang.String" ).implementation = function(chain, authType) { trustAll.checkServerTrusted(chain, authType); }; });
Logging All Method Calls
Java.perform(function() { var myClass = Java.use("com.example.target.MyClass"); // Hook all methods for (var method in myClass) { if (typeof myClass[method] === 'object') { myClass[method].overload().implementation = function() { console.log("Called: " + method + " with args: " + arguments); return myClass[method].overload().apply(this, arguments); }; } } });
Best Practices
- Always use
- Ensures code runs on the Java VM threadJava.perform() - Add
in Python - Java.perform silently fails without ittime.sleep(1) - Handle overloads explicitly - Don't assume single overload
- Use
- To call the original methodthis.methodName() - Store instances in arrays - For repeated access to found instances
- Use
- For interactive control from Pythonrpc.exports - Test on debug builds first - Release builds may have obfuscation
Troubleshooting
| Issue | Solution |
|---|---|
| Add after |
| Check package name, use to verify process |
| Use to discover method signatures |
| Check Frida server is running on device |
| Verify device is connected via USB and ADB works |
Tools
- List processesfrida-ps
- Trace function callsfrida-trace
- List classes and methodsfrida-ls
- Decrypt obfuscated stringsfrida-decrypt