frida-instrumentation Agent Skill
When to Use This Skill
Use this skill when:
- Bypassing SSL certificate pinning in Android or iOS apps during DAST
- Hooking Java/Kotlin/Objective-C/Swift methods at runtime without modifying APK/IPA
- Tracing native function calls in a running process
- Reversing mobile app encryption, license checks, or anti-tampering mechanisms
- Bypassing root detection, emulator detection, or jailbreak detection
- Automating mobile app pentesting with Objection
- Building dynamic analysis scripts for malware or binary research
What Frida Does
Frida is a cross-platform dynamic instrumentation framework that injects a JavaScript engine (V8/Duktape) into a target process at runtime. It enables reading/writing memory, hooking arbitrary functions, intercepting calls across Java, Objective-C, and native code layers, and exporting RPC interfaces for external control — all without recompilation or source access. It runs on Windows, macOS, Linux, Android, and iOS, making it the primary tool for mobile application security testing and runtime analysis.
Installation
frida-tools (host machine)
# Python package (pip) — installs frida, frida-trace, frida-ps, frida-ls-devices
pip install frida-tools
# Verify
frida --version
frida-ps --version
# Upgrade
pip install -U frida-tools
# Specific version (pin to match frida-server version)
pip install frida==16.2.1 frida-tools==12.4.3
frida-server (Android device)
# 1. Find device architecture
adb shell getprop ro.product.cpu.abi
# → arm64-v8a, armeabi-v7a, x86, x86_64
# 2. Download matching frida-server from:
# https://github.com/frida/frida/releases
# e.g.: frida-server-16.2.1-android-arm64.xz
# 3. Push and start
xz -d frida-server-16.2.1-android-arm64.xz
adb push frida-server-16.2.1-android-arm64 /data/local/tmp/frida-server
adb shell chmod +x /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &
# 4. Verify connection
frida-ps -U # -U = USB device
frida-ps -U | grep -i target_app
frida-server (iOS device — jailbroken)
# Via Cydia/Sileo — add Frida repo: https://build.frida.re
# Install: Frida (package)
# Verify
frida-ps -U # USB, or -R for remote TCP
# TCP connection (if USB unavailable)
frida-ps -H 192.168.1.50:27042
frida-gadget (non-rooted / non-jailbroken)
# Inject frida-gadget.so into APK (requires APK repackaging)
# Tool: objection patchapk (easiest method)
objection patchapk -s target.apk
# Or manual:
# 1. Download frida-gadget-XX-android-arm64.so.xz
# 2. Embed in APK lib directory
# 3. Load via modified smali or patched native library entry point
Core Concepts
Attach vs Spawn
# Attach to running process (by name or PID)
frida -U -n "com.target.app" # attach by package name
frida -U -p 1234 # attach by PID
# Spawn (start app, pause before main, inject)
frida -U -f com.target.app --no-pause # spawn and run
frida -U -f com.target.app # spawn, pause at entry (for early hooks)
# Load script at attach/spawn
frida -U -f com.target.app -l hook.js --no-pause
Script execution modes
# Interactive REPL
frida -U -n com.target.app
# → JavaScript REPL prompt
# One-shot script
frida -U -n com.target.app -l script.js
# Script with output (eval mode)
frida -U -n com.target.app -e "Java.perform(function(){ console.log('hi'); })"
JavaScript API Reference
Java API (Android)
// All Java hooking must be inside Java.perform()
Java.perform(function() {
// Hook a class method
var MainActivity = Java.use('com.target.app.MainActivity');
// Hook instance method
MainActivity.checkPin.implementation = function(pin) {
console.log('[*] checkPin called with: ' + pin);
var result = this.checkPin(pin); // call original
console.log('[*] checkPin result: ' + result);
return result;
};
// Override return value
MainActivity.isRooted.implementation = function() {
console.log('[*] isRooted hooked — returning false');
return false;
};
// Hook overloaded method (specify signature)
var String = Java.use('java.lang.String');
String.equals.overload('java.lang.String').implementation = function(other) {
var result = this.equals(other);
if (this.toString().indexOf('password') !== -1) {
console.log('[*] String.equals: ' + this + ' == ' + other + ' → ' + result);
}
return result;
};
// Enumerate loaded classes
Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.indexOf('target') !== -1) {
console.log('[*] Found class: ' + className);
}
},
onComplete: function() {}
});
// Instantiate a Java object
var SecretClass = Java.use('com.target.app.SecretClass');
var instance = SecretClass.$new('arg1');
console.log(instance.getSecret());
// Access static field
console.log(MainActivity.SECRET_KEY.value);
// Modify instance field
MainActivity.checkPin.implementation = function(pin) {
this.mMaxAttempts.value = 9999; // modify field
return this.checkPin(pin);
};
});
Interceptor API (Native / C functions)
// Hook by exported symbol name
Interceptor.attach(Module.findExportByName('libc.so', 'strcmp'), {
onEnter: function(args) {
// args[0], args[1] are NativePointer objects
var s1 = args[0].readUtf8String();
var s2 = args[1].readUtf8String();
if (s1 !== null && s2 !== null) {
console.log('[strcmp] "' + s1 + '" vs "' + s2 + '"');
}
this.s2 = s2; // save for onLeave
},
onLeave: function(retval) {
console.log('[strcmp] returned: ' + retval);
// Force return 0 (strings equal)
retval.replace(0);
}
});
// Hook by absolute address
var targetAddr = Module.findBaseAddress('libapp.so').add(0x1234);
Interceptor.attach(targetAddr, {
onEnter: function(args) {
console.log('[*] hit target function, arg0 = ' + args[0]);
},
onLeave: function(retval) {
retval.replace(ptr(1)); // return 1 (true)
}
});
// Replace entire function
Interceptor.replace(targetAddr, new NativeCallback(function(a, b) {
console.log('[*] replaced function called');
return 1; // always return 1
}, 'int', ['int', 'int']));
Module API
// List loaded modules
Process.enumerateModules().forEach(function(m) {
console.log(m.name, m.base, m.size);
});
// Find module by name
var lib = Process.findModuleByName('libssl.so');
console.log('Base:', lib.base);
// Enumerate exports of a module
Module.enumerateExports('libc.so').forEach(function(exp) {
if (exp.name.indexOf('SSL') !== -1) {
console.log(exp.name, exp.address);
}
});
// Find base address
var base = Module.findBaseAddress('libapp.so');
console.log('libapp.so base:', base);
// Get all symbols (including non-exported)
Module.enumerateSymbols('libapp.so').forEach(function(sym) {
if (sym.name.indexOf('check') !== -1) {
console.log(sym.name, sym.address);
}
});
Memory API
// Read memory
var addr = ptr('0x7f001234');
console.log(addr.readU8()); // 1 byte unsigned
console.log(addr.readU32()); // 4 bytes
console.log(addr.readUtf8String()); // null-terminated C string
console.log(addr.readByteArray(16)); // raw 16 bytes
// Write memory
addr.writeU8(0x90); // write single byte (NOP)
addr.writeByteArray([0x90, 0x90]); // write bytes
addr.writeUtf8String('patched'); // write string
// Allocate new memory
var buf = Memory.alloc(64);
buf.writeUtf8String('injected_string');
// Search memory for pattern
Memory.scan(base, 0x1000, '41 42 43 ?? 45', {
onMatch: function(address, size) {
console.log('[*] Pattern at: ' + address);
},
onComplete: function() {}
});
// Protect / change permissions
Memory.protect(ptr('0x401000'), 0x1000, 'rwx');
NativeFunction and NativeCallback
// Call an existing native function
var strlen = new NativeFunction(
Module.findExportByName('libc.so', 'strlen'),
'size_t', // return type
['pointer'] // argument types
);
var len = strlen(Memory.allocUtf8String('hello'));
console.log('length:', len);
// Create a native function to pass as callback
var myCallback = new NativeCallback(function(data, len) {
console.log('[*] callback triggered, len =', len);
return 0;
}, 'int', ['pointer', 'int']);
// Register as callback with a target function
var setCallback = new NativeFunction(
Module.findExportByName('libapp.so', 'register_callback'),
'void',
['pointer']
);
setCallback(myCallback);
ObjC API (iOS)
// Hook Objective-C method
var className = 'AppDelegate';
var methodName = '- validateLicense:';
if (ObjC.available) {
var klass = ObjC.classes[className];
var method = klass[methodName];
Interceptor.attach(method.implementation, {
onEnter: function(args) {
// args[0] = self, args[1] = selector, args[2+] = method args
var licenseKey = ObjC.Object(args[2]).toString();
console.log('[*] validateLicense called: ' + licenseKey);
},
onLeave: function(retval) {
// ObjC BOOL is int (0/1)
retval.replace(ptr(1)); // always return YES
}
});
// Enumerate all methods of a class
klass.$ownMethods.forEach(function(method) {
console.log(method);
});
}
frida-trace — Automatic Hooking
# Trace all calls to functions matching pattern
frida-trace -U -n com.target.app -i "Java_*" # all JNI functions
frida-trace -U -n com.target.app -i "SSL_*" # all SSL functions
frida-trace -U -n com.target.app -i "strcmp" # single function
# Trace Objective-C methods (iOS)
frida-trace -U -n TargetApp -m "-[AppDelegate *]" # all AppDelegate methods
frida-trace -U -n TargetApp -m "*validate*" # methods containing 'validate'
# Trace ObjC + native
frida-trace -U -f com.target.app -i "open*" -m "-[NSURLSession *]" --no-pause
# Custom handler output directory
frida-trace -U -n com.target.app -i "SSL_read" -o ./handlers
# Creates handlers/SSL_read.js — auto-generated, edit for custom logic
RPC Exports (Calling Frida from Python)
// script.js — export functions for Python caller
rpc.exports = {
dumpStrings: function() {
var results = [];
Java.perform(function() {
Java.enumerateLoadedClasses({
onMatch: function(c) { results.push(c); },
onComplete: function() {}
});
});
return results;
},
callDecrypt: function(ciphertext) {
var result = null;
Java.perform(function() {
var Crypto = Java.use('com.target.app.CryptoUtils');
result = Crypto.decrypt(Java.use('java.lang.String').$new(ciphertext));
});
return result ? result.toString() : null;
}
};
# caller.py
import frida, sys
def on_message(message, data):
print('[msg]', message)
device = frida.get_usb_device()
session = device.attach('com.target.app')
with open('script.js', 'r') as f:
script = session.create_script(f.read())
script.on('message', on_message)
script.load()
# Call exported RPC functions
api = script.exports
classes = api.dump_strings()
print(f'Found {len(classes)} classes')
plaintext = api.call_decrypt('U2FsdGVkX1+...')
print('Decrypted:', plaintext)
Common Workflows
SSL Pinning Bypass (Android)
// Universal SSL bypass — covers OkHttp3, Trustmanager, Network Security Config
Java.perform(function() {
// Method 1: TrustManager override
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
console.log('[*] TrustManagerImpl.verifyChain bypassed for: ' + host);
return untrustedChain;
};
// Method 2: OkHttp3 CertificatePinner
try {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(hostname, certs) {
console.log('[*] OkHttp3 CertificatePinner.check bypassed for: ' + hostname);
};
} catch(e) { console.log('[!] OkHttp3 not found: ' + e); }
// Method 3: SSLContext
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
var TrustAllManager = Java.registerClass({
name: 'com.frida.TrustAll',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() { return []; }
}
});
var SSLContext = Java.use('javax.net.ssl.SSLContext');
var ctx = SSLContext.getInstance('TLS');
ctx.init(null, [TrustAllManager.$new()], null);
SSLContext.getDefault.implementation = function() { return ctx; };
});
# Or use Objection for one-liner SSL bypass
objection --gadget com.target.app explore
objection> android sslpinning disable
Root Detection Bypass
Java.perform(function() {
// RootBeer / common root check classes
var classes = [
'com.scottyab.rootbeer.RootBeer',
'com.topjohnwu.superuser.Shell',
];
classes.forEach(function(cls) {
try {
var c = Java.use(cls);
if (c.isRooted) {
c.isRooted.implementation = function() { return false; };
console.log('[*] Hooked ' + cls + '.isRooted');
}
} catch(e) {}
});
// Hook common file existence checks
var File = Java.use('java.io.File');
File.exists.implementation = function() {
var path = this.getAbsolutePath();
var rootPaths = ['/su', '/system/bin/su', '/sbin/su', '/system/xbin/su'];
if (rootPaths.indexOf(path) !== -1) {
console.log('[*] File.exists blocked for: ' + path);
return false;
}
return this.exists();
};
// Block Runtime.exec calls for 'su'
var Runtime = Java.use('java.lang.Runtime');
Runtime.exec.overload('[Ljava.lang.String;').implementation = function(cmd) {
var command = cmd.join(' ');
if (command.indexOf('su') !== -1 || command.indexOf('which') !== -1) {
console.log('[*] Blocked exec: ' + command);
throw Java.use('java.io.IOException').$new('File not found');
}
return this.exec(cmd);
};
});
Crypto Key Extraction
// Hook javax.crypto.SecretKeySpec to grab AES keys
Java.perform(function() {
var SecretKeySpec = Java.use('javax.crypto.SecretKeySpec');
SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function(keyBytes, algorithm) {
console.log('[*] SecretKeySpec created:');
console.log(' Algorithm: ' + algorithm);
console.log(' Key (hex): ' + bytesToHex(keyBytes));
return this.$init(keyBytes, algorithm);
};
// Hook Cipher for encrypt/decrypt
var Cipher = Java.use('javax.crypto.Cipher');
Cipher.doFinal.overload('[B').implementation = function(input) {
var result = this.doFinal(input);
console.log('[Cipher.doFinal]');
console.log(' Input: ' + bytesToHex(input));
console.log(' Output: ' + bytesToHex(result));
return result;
};
});
function bytesToHex(bytes) {
var hex = '';
for (var i = 0; i < bytes.length; i++) {
hex += ('0' + (bytes[i] & 0xFF).toString(16)).slice(-2);
}
return hex;
}
iOS Jailbreak Detection Bypass
if (ObjC.available) {
// Hook file existence checks (jailbreak file paths)
Interceptor.attach(Module.findExportByName('libc.dylib', 'access'), {
onEnter: function(args) {
var path = args[0].readUtf8String();
var jbPaths = ['/Applications/Cydia.app', '/bin/bash', '/usr/sbin/sshd',
'/etc/apt', '/private/var/lib/apt'];
if (jbPaths.some(p => path && path.includes(p))) {
console.log('[*] access() blocked for: ' + path);
args[0] = Memory.allocUtf8String('/nonexistent');
}
}
});
// Hook stat
Interceptor.attach(Module.findExportByName('libSystem.B.dylib', 'stat'), {
onEnter: function(args) {
var path = args[0].readUtf8String();
if (path && path.includes('Cydia')) {
args[0] = Memory.allocUtf8String('/nonexistent');
}
}
});
// Hook Objective-C string comparisons used in JB checks
var NSFileManager = ObjC.classes.NSFileManager;
Interceptor.attach(NSFileManager['- fileExistsAtPath:'].implementation, {
onEnter: function(args) {
var path = ObjC.Object(args[2]).toString();
if (path.includes('Cydia') || path.includes('substrate')) {
console.log('[*] fileExistsAtPath blocked: ' + path);
this.block = true;
}
},
onLeave: function(retval) {
if (this.block) retval.replace(ptr(0));
}
});
}
Objection Framework Integration
Objection is a runtime exploration toolkit built on Frida that provides common pentest operations as simple commands without writing scripts.
pip install objection
# Connect to running app
objection --gadget com.target.app explore
# Connect to spawned app
objection --gadget com.target.app --startup-command 'android sslpinning disable' explore
# Common Objection commands (inside explore session):
# Android
android sslpinning disable # disable SSL pinning
android root disable # bypass root detection
android keystore list # list keystore entries
android keystore dump # dump keystore keys
android intent launch_activity <class> # launch activity
android hooking list classes # list all classes
android hooking list class_methods <cls> # list methods
android hooking watch class <cls> # hook all methods of class
android hooking watch method <cls>.<method> --dump-args --dump-return
android hooking search classes <term> # search class names
android heap instances of <class> # get live instances from heap
android heap evaluate <cls> this.<method>() # call method on live instance
# iOS
ios sslpinning disable # disable SSL pinning
ios jailbreak disable # bypass jailbreak detection
ios keychain dump # dump Keychain entries
ios plist cat <path> # read plist file
ios hooking list classes
ios hooking watch method "+[ClassName method:]" --dump-args
# General
jobs list # list active hooks
jobs kill <id> # remove hook
memory dump all <outfile> # dump process memory
memory search "<hex pattern>" # search memory
Troubleshooting
frida-server not found / connection refused
# Verify frida-server is running
adb shell ps | grep frida-server
# Restart
adb shell pkill frida-server
adb shell /data/local/tmp/frida-server &
# Check version mismatch (must match frida Python package)
frida --version
adb shell /data/local/tmp/frida-server --version
Unable to find application with identifier 'com.target.app'
# App not running — use -f to spawn, or start app first then attach
frida-ps -Ua # list running apps on USB device
frida -U -f com.target.app --no-pause -l hook.js
Script not responding / Java.perform callback never fires
// Java.perform is asynchronous on attach
// Ensure hook is reached by user interaction, or use setImmediate
setImmediate(function() {
Java.perform(function() {
// hook code here
});
});
ClassNotFoundException for target class
// Class may not be loaded yet — use ClassLoader enumeration
Java.enumerateClassLoaders({
onMatch: function(loader) {
try {
var cls = loader.loadClass('com.target.app.HiddenClass');
Java.use('com.target.app.HiddenClass');
// use class here
} catch(e) {}
},
onComplete: function() {}
});
Frida detected / anti-Frida bypasses
# Apps may detect frida-server port (27042) or /proc/self/maps entries
# Use frida-gadget with custom config instead of frida-server
# Or use Magisk module to hide frida-server process name
# Rename frida-server binary to a system process name:
adb shell cp /data/local/tmp/frida-server /data/local/tmp/svchost
adb shell /data/local/tmp/svchost &
EPERM when attaching to system process
# Frida-server must run as root for system process attachment
adb shell su -c '/data/local/tmp/frida-server &'
Built by Red Hound InfoSec — On-demand offensive security expertise for SMBs. 20+ years of Fortune 500 experience. Penetration testing, attack surface analysis, and security consulting.