Frida Dynamic Instrumentation Guide
Frida is a dynamic instrumentation toolkit that revolutionizes the way security researchers analyze applications. By injecting JavaScript into running processes, Frida enables real-time code manipulation, function hooking, and memory inspection without recompilation or static patching.
What is Frida?
Frida is an open-source framework that allows you to:
- Hook functions in native and managed code
- Modify behavior of running applications at runtime
- Trace execution and log function calls
- Bypass security mechanisms like SSL pinning and root detection
- Extract sensitive data from memory during execution
Why Frida?
Traditional static analysis has limitations:
- Obfuscated code is hard to understand
- Anti-tampering mechanisms detect modifications
- Dynamic behavior is difficult to predict
Frida solves these problems by operating at runtime, allowing you to: ✅ See actual execution flow ✅ Bypass anti-debugging checks ✅ Test hypothesis immediately ✅ Automate complex analysis tasks
Installation and Setup
Installing Frida
Python package (includes frida-tools):
pip install frida-tools
Verify installation:
frida --version
frida-ps --version
Setting Up for Mobile Analysis
Android Setup:
# Download frida-server for your device architecture
wget https://github.com/frida/frida/releases/download/16.0.0/frida-server-16.0.0-android-arm64.xz
unxz frida-server-16.0.0-android-arm64.xz
# Push to device
adb push frida-server-16.0.0-android-arm64 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
# Run frida-server (requires root)
adb shell "/data/local/tmp/frida-server &"
iOS Setup (jailbroken device):
# Install via Cydia/Sileo
# Add repository: https://build.frida.re
# Install "Frida" package
# Verify connection
frida-ps -U
Basic Frida Concepts
Process Attachment
Frida can attach to processes in three ways:
1. Attach to running process:
frida -U -n "com.example.app" # By name
frida -U -p 1234 # By PID
2. Spawn new process:
frida -U -f com.example.app --no-pause
3. Script injection:
frida -U -l script.js com.example.app
JavaScript API Basics
Frida scripts use JavaScript with special APIs:
// Entry point
Java.perform(function() {
console.log("[*] Script loaded");
// Your instrumentation code here
});
Hooking Techniques
Hooking Java Methods (Android)
Basic method hooking:
Java.perform(function() {
var MainActivity = Java.use("com.example.MainActivity");
// Hook method with no arguments
MainActivity.checkLicense.implementation = function() {
console.log("[+] checkLicense called");
return true; // Always return true
};
});
Method with arguments and return value:
Java.perform(function() {
var Crypto = Java.use("com.example.security.Crypto");
Crypto.encrypt.overload('java.lang.String', 'java.lang.String')
.implementation = function(plaintext, key) {
console.log("[+] encrypt() called");
console.log(" Plaintext: " + plaintext);
console.log(" Key: " + key);
// Call original method
var result = this.encrypt(plaintext, key);
console.log(" Result: " + result);
return result;
};
});
Handling method overloads:
Java.perform(function() {
var Utils = Java.use("com.example.Utils");
// Hook specific overload
Utils.process.overload('int').implementation = function(value) {
console.log("[+] process(int): " + value);
return this.process(value);
};
Utils.process.overload('java.lang.String').implementation = function(text) {
console.log("[+] process(String): " + text);
return this.process(text);
};
});
Hooking Native Functions
Hooking C/C++ functions in shared libraries:
// Find module
var libcrypto = Process.getModuleByName("libcrypto.so");
// Find function by name
var EVP_EncryptInit = libcrypto.getExportByName("EVP_EncryptInit");
// Hook the function
Interceptor.attach(EVP_EncryptInit, {
onEnter: function(args) {
console.log("[+] EVP_EncryptInit called");
console.log(" ctx: " + args[0]);
console.log(" cipher: " + args[1]);
},
onLeave: function(retval) {
console.log(" Return value: " + retval);
}
});
Hooking by address:
// Hook function at specific address
var targetAddress = ptr("0x12345678");
Interceptor.attach(targetAddress, {
onEnter: function(args) {
console.log("[+] Function at 0x12345678 called");
console.log(" arg0: " + args[0]);
console.log(" arg1: " + args[1]);
}
});
Hooking Objective-C Methods (iOS)
Basic method hooking:
if (ObjC.available) {
var className = "ViewController";
var hook = ObjC.classes[className];
// Hook instance method
var checkPremium = hook["- checkPremiumStatus"];
Interceptor.attach(checkPremium.implementation, {
onEnter: function(args) {
console.log("[+] checkPremiumStatus called");
},
onLeave: function(retval) {
retval.replace(ptr("0x1")); // Return true
}
});
}
Method swizzling:
var hook = ObjC.classes.ViewController;
hook['- isJailbroken'].implementation = ObjC.implement(hook['- isJailbroken'], function(handle, selector) {
console.log("[+] Jailbreak check bypassed");
return 0; // Return NO
});
Practical Attack Scenarios
1. Bypassing SSL Pinning
Universal Android SSL pinning bypass:
Java.perform(function() {
// Hook OkHttp3
try {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {
console.log('[+] OkHttp3 pinning bypassed');
};
} catch(e) {}
// Hook TrustManager
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
var SSLContext = Java.use('javax.net.ssl.SSLContext');
var TrustManager = Java.registerClass({
name: 'com.frida.TrustManager',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() { return []; }
}
});
var TrustManagers = [TrustManager.$new()];
var SSLContext_init = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');
SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
console.log('[+] SSLContext.init bypassed');
SSLContext_init.call(this, keyManager, TrustManagers, secureRandom);
};
});
2. Defeating Root Detection
Android root detection bypass:
Java.perform(function() {
// Hook common root check methods
var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer.isRooted.implementation = function() {
console.log("[+] Root check bypassed");
return false;
};
RootBeer.detectRootManagementApps.implementation = function() {
console.log("[+] Root management apps check bypassed");
return false;
};
RootBeer.detectTestKeys.implementation = function() {
console.log("[+] Test keys check bypassed");
return false;
};
// Hook su binary check
var Runtime = Java.use('java.lang.Runtime');
Runtime.exec.overload('[Ljava.lang.String;').implementation = function(cmd) {
var command = cmd[0];
if (command.indexOf("su") !== -1 || command.indexOf("which") !== -1) {
console.log("[+] Blocked command: " + command);
throw new Error("Command not found");
}
return this.exec(cmd);
};
});
3. Extracting Encryption Keys
Capturing AES keys:
Java.perform(function() {
var SecretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function(key, algorithm) {
console.log("[+] SecretKeySpec created");
console.log(" Algorithm: " + algorithm);
console.log(" Key: " + bytesToHex(key));
return this.$init(key, algorithm);
};
function bytesToHex(bytes) {
var hex = [];
for (var i = 0; i < bytes.length; i++) {
hex.push(('0' + (bytes[i] & 0xFF).toString(16)).slice(-2));
}
return hex.join('');
}
});
4. Monitoring API Calls
Logging HTTP requests:
Java.perform(function() {
var URL = Java.use("java.net.URL");
URL.openConnection.overload().implementation = function() {
var connection = this.openConnection();
console.log("[+] HTTP Connection");
console.log(" URL: " + this.toString());
return connection;
};
// Hook OkHttp
var Request = Java.use("okhttp3.Request");
var RequestBuilder = Java.use("okhttp3.Request$Builder");
RequestBuilder.build.implementation = function() {
var request = this.build();
console.log("[+] OkHttp Request");
console.log(" URL: " + request.url().toString());
console.log(" Method: " + request.method());
var headers = request.headers();
for (var i = 0; i < headers.size(); i++) {
console.log(" " + headers.name(i) + ": " + headers.value(i));
}
return request;
};
});
Advanced Frida Techniques
Memory Scanning and Modification
Search for strings in memory:
var pattern = "password123";
Process.enumerateRanges('rw-').forEach(function(range) {
Memory.scan(range.base, range.size, pattern, {
onMatch: function(address, size) {
console.log("[+] Found '" + pattern + "' at: " + address);
},
onComplete: function() {
console.log("[*] Memory scan complete");
}
});
});
Modify memory values:
var targetAddress = ptr("0x12345000");
Memory.writeUtf8String(targetAddress, "new_password");
console.log("[+] Memory modified at: " + targetAddress);
RPC Communication
Create a bidirectional channel between Frida script and Python:
Frida script (script.js):
rpc.exports = {
decrypt: function(encrypted) {
var result = performDecryption(encrypted);
return result;
},
getKeys: function() {
return {
aes: capturedAESKey,
rsa: capturedRSAKey
};
}
};
Python client:
import frida
import sys
def on_message(message, data):
print(message)
device = frida.get_usb_device()
pid = device.spawn(["com.example.app"])
session = device.attach(pid)
with open("script.js") as f:
script = session.create_script(f.read())
script.on('message', on_message)
script.load()
# Call RPC functions
decrypted = script.exports.decrypt("encrypted_data")
print("Decrypted:", decrypted)
keys = script.exports.get_keys()
print("Keys:", keys)
device.resume(pid)
sys.stdin.read()
Stalking and Tracing
Function call tracing:
var targetFunction = Module.findExportByName("libc.so", "open");
Stalker.follow(Process.getCurrentThreadId(), {
events: {
call: true
},
onReceive: function(events) {
console.log("[+] Call trace:");
console.log(Stalker.parse(events));
}
});
// Follow specific function calls
Interceptor.attach(targetFunction, {
onEnter: function() {
Stalker.follow();
},
onLeave: function() {
Stalker.unfollow();
}
});
Frida Tools Ecosystem
Objection
A runtime mobile security toolkit built on Frida:
# Install
pip install objection
# Launch app with objection
objection -g com.example.app explore
# Common commands
android hooking list activities
android hooking list services
android sslpinning disable
android root disable
memory list modules
memory search "password" --string
Frida-Trace
Automatically trace function calls:
# Trace all ObjC methods
frida-trace -U -f com.example.app -m "*[* *]"
# Trace specific Java methods
frida-trace -U -f com.example.app -j '*!check*/i'
# Trace native functions
frida-trace -U -f com.example.app -i open -i read -i write
Frida-PS
List processes and applications:
# List running processes on device
frida-ps -U
# List installed applications
frida-ps -Uai
# Filter by name
frida-ps -U | grep example
Best Practices
1. Error Handling
Always wrap your hooks in try-catch:
Java.perform(function() {
try {
var TargetClass = Java.use("com.example.Target");
TargetClass.sensitiveMethod.implementation = function() {
console.log("[+] Hooked successfully");
return this.sensitiveMethod();
};
} catch(e) {
console.log("[-] Error: " + e);
}
});
2. Performance Considerations
Avoid excessive logging in hot paths:
var callCount = 0;
var logInterval = 100;
TargetClass.frequentMethod.implementation = function() {
callCount++;
if (callCount % logInterval === 0) {
console.log("[+] Called " + callCount + " times");
}
return this.frequentMethod();
};
3. Clean Hooking
Preserve original functionality:
var original = TargetClass.method;
TargetClass.method.implementation = function() {
// Pre-processing
console.log("[+] Before call");
// Call original
var result = original.apply(this, arguments);
// Post-processing
console.log("[+] After call, result: " + result);
return result;
};
Real-World Use Cases
Security Testing
- Identify vulnerabilities in mobile applications
- Test authentication mechanisms
- Verify encryption implementation
- Audit API security
Malware Analysis
- Understand malicious behavior at runtime
- Extract C2 server addresses
- Decrypt communications
- Bypass anti-analysis techniques
Research and Development
- Reverse engineer proprietary protocols
- Understand closed-source libraries
- Prototype security features
- Create proof-of-concept exploits
Resources and Learning
Official Documentation
- Frida Documentation
- Frida CodeShare - Community scripts
- Frida GitHub
Community Resources
- Blog Posts: Follow security researchers sharing Frida scripts
- YouTube Channels: Many security channels feature Frida tutorials
- Discord/Telegram: Join Frida community channels
Practice Platforms
- OWASP MSTG Crackmes: Practice mobile security with Frida
- HackTheBox Mobile Challenges: Apply Frida to CTF challenges
- Your Own Apps: Best way to learn is by practicing on your own applications
Conclusion
Frida has revolutionized dynamic analysis by making runtime instrumentation accessible and powerful. Whether you’re performing security assessments, analyzing malware, or conducting research, Frida provides the tools needed to understand and manipulate application behavior in real-time.
Key takeaways:
- ✅ Frida enables runtime code manipulation without recompilation
- ✅ JavaScript API makes hooking accessible to a wide audience
- ✅ Works across Android, iOS, Windows, macOS, and Linux
- ✅ Extensive ecosystem of tools and community scripts
- ✅ Essential skill for modern mobile security professionals
Start with simple hooks, experiment with different techniques, and gradually build your Frida toolkit. The ability to see and modify application behavior at runtime opens up endless possibilities for security research and reverse engineering.
Next Steps: Explore our companion guides on Android Reverse Engineering and Binary Analysis Fundamentals to complement your Frida skills.
Happy hooking and stay curious!