Process and Device Manipulation
Lists connectable processes and devices, or terminates processes.
Command | Description | Example |
---|---|---|
frida-ps | Lists running processes. You can specify the target. |
|
frida-ls-devices | Lists devices recognized by Frida. |
|
frida-kill | Terminates the specified process. |
|
Attaching to and Spawning Targets
Use the frida
command to attach to an existing process or to spawn and inject into a new process.
Option | Description | Example |
---|---|---|
-U / --usb | Specifies a device connected via USB. | frida -U -n Twitter |
-R / --remote | Connects to a remote frida-server (default: localhost:27042). Specifying host:port is also possible. | frida -R -p 1234 frida -R 192.168.1.100:12345 -f com.example.app |
-H HOST / --host HOST | Specifies the host and port of a remote frida-server. Similar to -R but more explicit. | frida -H 10.0.0.5:27042 -f com.example.app |
-f FILE / --file FILE | Spawns the specified application and injects before the main thread runs. (e.g., Android package name, iOS bundle ID, executable path) | frida -U -f com.android.settings frida -U -f com.apple.Preferences frida -f /bin/ls |
-n NAME / --attach-name NAME | Attaches to the process with the specified name. | frida -U -n WeChat frida -n explorer.exe |
-p PID / --attach-pid PID | Attaches to the process with the specified Process ID (PID). | frida -p 1337 |
--no-pause | When spawning an app with -f , starts execution immediately without pausing at the beginning. | frida -U -f com.example.test --no-pause |
-l SCRIPT / --load SCRIPT | Loads and executes the specified JavaScript file. | frida -U -n Twitter -l myscript.js |
-e CODE / --eval CODE | Executes the specified JavaScript code directly. Exits after execution instead of entering the REPL. | frida -U -f com.app.id -e "console.log('Hello from Frida!');" |
--runtime=qjs|v8 | Specifies the JavaScript runtime to use (default is V8). QJS is lightweight. | frida -U -f com.example.app --runtime=qjs -l script.js |
--realm=native|emulated | Specifies whether to attach to a native or an emulated process. | frida -U --realm=emulated -n "emulator-process" |
-f
, -n
, or -p
. Combine with -U
, -R
, or -H
depending on the target device.Function Tracing (frida-trace)
Traces specific function calls. Can display arguments, return values, and caller information.
Option | Description | Example |
---|---|---|
-i "function" | Includes the specified function in the trace. Glob patterns can be used. |
|
-I "module" | Includes all functions within the specified module in the trace. |
|
-x "function" | Excludes the specified function from the trace. Used in combination with -i or -I . |
|
-X "module" | Excludes all functions within the specified module from the trace. |
|
-a "module!offset" | Traces a function specified by module name and offset. Useful when symbols are not available. |
|
-T / --include-imports | Also includes imported functions in the trace (by default, only exported functions are included). |
|
-m "objc_method" | Traces an Objective-C method. Equivalent to -i "-[Class method]" or -i "+[Class method]" . |
|
-M "objc_class" | Traces all methods of the specified Objective-C class. |
|
-j "java_method" | Traces a Java method. * can be used as a wildcard. Specify the class name and method name. |
|
-J "java_class" | Traces all methods and constructors of the specified Java class. |
|
-o output.txt | Outputs the trace results to the specified file. |
|
-h / --help | Shows the help message. |
|
Interactive Manipulation (Frida REPL)
After attaching with the frida
command, you can interactively execute JavaScript APIs to manipulate the target process.
Basic REPL Commands
Command | Description |
---|---|
%resume | Resumes a process that was paused on startup with -f . |
%reload | Reloads the script loaded with -l . |
%load script.js | Loads a new script file. |
%unload | Unloads the currently loaded script. |
%exit | Exits the Frida REPL (the process will be detached). |
(JavaScript code) | Executes arbitrary JavaScript code. |
Basic JavaScript API Examples
Examples of APIs commonly used in the REPL.
// List modules
Process.enumerateModules().forEach(function(m){ console.log(JSON.stringify(m)); });
// List export functions of a specific module
Module.enumerateExports('libc.so').forEach(function(exp){ console.log(exp.name + ': ' + exp.address); });
// Get the base address of a module
var baseAddr = Module.findBaseAddress('libnative-lib.so');
console.log('Base address: ' + baseAddr);
// Create a function pointer from an address
var myFunc = new NativeFunction(baseAddr.add(0x1234), 'int', ['int', 'pointer']);
// Call the function
var result = myFunc(10, Memory.allocUtf8String('hello'));
console.log('Result: ' + result);
// Use Java classes (Android)
if (Java.available) { Java.perform(function() { var StringClass = Java.use('java.lang.String'); var myString = StringClass.$new('Hello from Java!'); console.log(myString.toUpperCase()); var MainActivity = Java.use('com.example.app.MainActivity'); // Call a static method var result = MainActivity.staticMethod(5); console.log('Static method result: ' + result); // Get an instance (e.g., find an existing instance) Java.choose('com.example.app.MainActivity', { onMatch: function(instance) { console.log('Found instance: ' + instance); // Call an instance method console.log(instance.getSomeValue()); instance.setSomeValue(100); }, onComplete: function() { console.log('Instance search complete.'); } }); });
}
// Use Objective-C classes/methods (iOS/macOS)
if (ObjC.available) { var NSAutoreleasePool = ObjC.classes.NSAutoreleasePool; var pool = NSAutoreleasePool.alloc().init(); // For memory management try { var NSString = ObjC.classes.NSString; var nsString = NSString.stringWithString_('Hello from ObjC!'); console.log(nsString.UTF8String()); // Convert to C string and display var UIAlertController = ObjC.classes.UIAlertController; if (UIAlertController) { // Call a class method var alert = UIAlertController.alertControllerWithTitle_message_preferredStyle_( NSString.stringWithString_('Frida Alert'), NSString.stringWithString_('Hello from Frida!'), 1 // UIAlertControllerStyleAlert ); console.log('Alert Controller created: ' + alert.$ivars.message); // Access instance variable (Note: private API) } } finally { pool.release(); }
}
Function Hooking and Modification (JavaScript API)
Use Interceptor.attach
, Java.use
, and ObjC.classes
to monitor function calls or modify their arguments and return values.
Native Functions (Interceptor)
// Hook the open function in libc
var openPtr = Module.findExportByName('libc.so', 'open');
Interceptor.attach(openPtr, { onEnter: function(args) { // args[0] is the first argument (pathname) this.filePath = args[0].readUtf8String(); console.log('open() called with path: ' + this.filePath); // Example of rewriting an argument: redirect a specific file to another // if (this.filePath.includes('target.txt')) { // args[0] = Memory.allocUtf8String('/data/local/tmp/redirected.txt'); // console.log('Redirecting open() to /data/local/tmp/redirected.txt'); // } }, onLeave: function(retval) { // retval is the return value (file descriptor) console.log('open("' + this.filePath + '") returned: ' + retval.toInt32()); // Example of rewriting a return value: return an error // if (this.filePath.includes('secret.txt')) { // console.log('Forcing open() to fail for secret.txt'); // retval.replace(-1); // More accurately, return an errno corresponding to EACCES, etc. // } }
});
// Hook a function at a specific address (offset)
var moduleBase = Module.findBaseAddress('libnative-lib.so');
var targetAddr = moduleBase.add(0x1A2B); // Address of the target function
Interceptor.attach(targetAddr, { onEnter: function(args) { console.log('Function at ' + targetAddr + ' called.'); console.log('Arg0: ' + args[0].toInt32() + ', Arg1: ' + args[1].readPointer().readUtf8String()); }, onLeave: function(retval) { console.log('Function at ' + targetAddr + ' returned: ' + retval); }
});
// Replace a function's implementation (Interceptor.replace)
var mallocPtr = Module.findExportByName(null, 'malloc'); // null searches all modules
var myMalloc = new NativeCallback(function(size) { console.log('malloc() called with size: ' + size); // No need to call the original malloc. You can implement your own allocator here, // or return a fixed pointer. Here, we just log and return 0. return ptr(0); // Example of returning NULL
}, 'pointer', ['size_t']);
// If you want to call the original function, use Interceptor.attach instead of replace
Interceptor.replace(mallocPtr, myMalloc);
// Note: replace is risky. It completely replaces the original implementation,
// which can have a significant impact on the program's behavior.
Java Methods (Android)
Java.perform(function() { // Hook a method of a specific class var TargetClass = Java.use('com.example.app.CryptoUtils'); TargetClass.encrypt.implementation = function(data) { console.log('CryptoUtils.encrypt() called with data: ' + data); // Call the original method var result = this.encrypt(data); console.log('CryptoUtils.encrypt() returned: ' + result); // Example of modifying the return value // var modifiedResult = "MODIFIED_" + result; // console.log('Returning modified result: ' + modifiedResult); // return modifiedResult; return result; }; // Hook an overloaded method (specify signature) TargetClass.processData.overload('int', 'java.lang.String').implementation = function(num, str) { console.log('processData(int, String) called with num=' + num + ', str=' + str); // Example of modifying an argument // var modifiedStr = str + "_hooked"; // console.log('Calling original with modified string: ' + modifiedStr); // return this.processData(num, modifiedStr); return this.processData(num, str); }; // Hook a constructor var FileClass = Java.use('java.io.File'); FileClass.$init.overload('java.lang.String').implementation = function(path) { console.log('new File("' + path + '")'); // Modifying a constructor call can be complex // Generally, you should call the original constructor return this.$init(path); }; // Completely take over a method's implementation (do not call the original) TargetClass.getSecretKey.implementation = function() { console.log('getSecretKey() called, returning fake key!'); var StringClass = Java.use('java.lang.String'); return StringClass.$new("FAKE_SECRET_KEY_FROM_FRIDA"); };
});
Objective-C Methods (iOS/macOS)
if (ObjC.available) { try { // Hook a class method var NSURL = ObjC.classes.NSURL; Interceptor.attach(NSURL['+ URLWithString:'].implementation, { onEnter: function(args) { // args[0] is self (the class object), args[1] is the selector // args[2] is the first argument (NSString *) this.urlString = ObjC.Object(args[2]).toString(); console.log('[NSURL URLWithString:@"' + this.urlString + '"]'); // Modifying the argument // if (this.urlString.startsWith('http://example.com')) { // var newUrlString = ObjC.classes.NSString.stringWithString_('https://safe.example.com'); // args[2] = newUrlString; // console.log('Rewriting URL to https://safe.example.com'); // } }, onLeave: function(retval) { // retval is the return value (NSURL *) console.log('[NSURL URLWithString:] returned: ' + ObjC.Object(retval)); } }); // Hook an instance method var LAContext = ObjC.classes.LAContext; // Local Authentication (Face ID/Touch ID) if (LAContext) { Interceptor.attach(LAContext['- evaluatePolicy:localizedReason:reply:'].implementation, { onEnter: function(args) { console.log('[LAContext evaluatePolicy...]'); // args[2] is policy (LAPolicy) // args[3] is localizedReason (NSString *) // args[4] is reply (block) this.replyBlock = new ObjC.Block(args[4]); // Get the block var originalReply = this.replyBlock.implementation; // Original block implementation // Example of forcing the hooked authentication to always succeed console.log('Forcing authentication success!'); // Create and call a block with a new implementation var newReply = new ObjC.Block(function(success, error) { console.log('Original reply block would have received: success=' + success + ', error=' + error); // Always call the original callback with success = YES, error = nil originalReply(true, null); }, 'void', ['bool', 'pointer']); // Replace the original reply block (here we call it directly) newReply(true, null); // Call with dummy values (they will be overwritten) // Important: If you replace the reply block to skip the original method's execution, // you need to avoid calling the original method itself. // Use Interceptor.replace or return early in onEnter. // For simplicity, this example manipulates the reply block assuming the original method is called. // In practice, Interceptor.replace is often safer. // It's also possible to overwrite the original reply block's pointer, but it's complex. // args[4] = newReply.handle; }, onLeave: function(retval) { // This method is asynchronous, so onLeave is not very meaningful console.log('[LAContext evaluatePolicy...] returned (async)'); } }); // Example of bypassing authentication using Interceptor.replace (more direct) // Interceptor.replace(LAContext['- evaluatePolicy:localizedReason:reply:'].implementation, // new NativeCallback(function(self, sel, policy, reason, reply) { // console.log('[REPLACED LAContext evaluatePolicy...] - Forcing success!'); // var replyBlock = new ObjC.Block(reply); // // Immediately call the callback as successful // replyBlock.implementation(true, null); // }, 'void', ['pointer', 'pointer', 'long', 'pointer', 'pointer']) // ); } else { console.log('LAContext not available.'); } } catch (e) { console.error('Error setting up ObjC hooks: ' + e); }
} else { console.log('Objective-C runtime not available.');
}
Memory Manipulation
Read or write the target process’s memory, or search for specific patterns.
// Read memory at a specific address
var addr = ptr('0x12345678'); // Target address
// Read as a 4-byte integer
var valueInt = Memory.readInt(addr);
console.log('Int at ' + addr + ': ' + valueInt);
// Read as a pointer
var valuePtr = Memory.readPointer(addr);
console.log('Pointer at ' + addr + ': ' + valuePtr);
// Read as a UTF-8 string (specify length)
var valueStr = Memory.readUtf8String(addr, 64); // Read up to 64 bytes
console.log('String at ' + addr + ': ' + valueStr);
// Read as a byte array (ArrayBuffer)
var valueBytes = Memory.readByteArray(addr, 16); // Read 16 bytes
console.log('Bytes at ' + addr + ': ' + hexdump(valueBytes, { ansi: true }));
// Write to memory at a specific address
var targetAddr = Module.findExportByName('libtarget.so', 'global_flag');
if (targetAddr) { // Write a 32-bit integer value Memory.writeInt(targetAddr, 1); console.log('Wrote 1 to ' + targetAddr); // Write a string var strAddr = Memory.allocUtf8String('Modified by Frida'); // Assuming the global variable is a pointer to a string, overwrite it // Memory.writePointer(targetAddr, strAddr); // console.log('Wrote string pointer to ' + targetAddr); // Write a byte array Memory.writeByteArray(targetAddr.add(8), [0xDE, 0xAD, 0xBE, 0xEF]); console.log('Wrote bytes to ' + targetAddr.add(8));
} else { console.log('Could not find global_flag export.');
}
// Allocate memory
var buffer = Memory.alloc(1024); // Allocate 1024 bytes
console.log('Allocated buffer at: ' + buffer);
// Write to the allocated memory
Memory.writeUtf8String(buffer, 'Hello from allocated memory!');
console.log(Memory.readUtf8String(buffer));
// Scan memory (Memory.scan)
var moduleName = 'libnative-lib.so';
var searchPattern = '48 8d 3d ?? ?? ?? ?? 48 8d 15'; // Part of an x86_64 LEA instruction (?? is a wildcard)
var module = Process.findModuleByName(moduleName);
if (module) { console.log('Scanning module ' + moduleName + ' (' + module.size + ' bytes) for pattern: ' + searchPattern); Memory.scan(module.base, module.size, searchPattern, { onMatch: function(address, size) { console.log('Pattern found at address: ' + address); // Dump memory around the match // console.log(hexdump(address, { length: 64, ansi: true })); // You can use Interceptor.attach on the found address here // return 'stop'; // To stop the scan after the first match }, onError: function(reason) { console.error('Memory scan error: ' + reason); }, onComplete: function() { console.log('Memory scan complete.'); } });
} else { console.log('Module ' + moduleName + ' not found.');
}
// Search the Java heap (Android)
if (Java.available) { Java.perform(function() { console.log('Searching for instances of java.lang.String on the heap...'); Java.choose('java.lang.String', { onMatch: function(instance) { // Be careful, as many instances can be found if (instance.length() > 50) { // Example: only show strings longer than 50 characters console.log('Found String instance: "' + instance + '"'); } }, onComplete: function() { console.log('Heap search complete.'); } }); console.log('\nSearching for instances of com.example.SecretData...'); Java.choose('com.example.SecretData', { onMatch: function(instance) { console.log('Found SecretData instance: ' + instance); // Access the instance's fields console.log(' - key: ' + instance.key.value); console.log(' - value: ' + instance.data.value); }, onComplete: function() { console.log('SecretData search complete.'); } }); });
}
Advanced Features
Features for more complex analysis and manipulation.
Stalker (Code Tracing)
Traces a thread’s execution and collects instruction-level information. It has a significant performance impact but allows for detailed analysis.
// Trace the execution of a specific thread
var threadId = Process.getCurrentThreadId(); // Current REPL thread ID (usually you would specify a target thread)
// Example: Start tracing a thread when a specific function is called
var targetFuncPtr = Module.findExportByName(null, 'sensitive_function');
Interceptor.attach(targetFuncPtr, { onEnter: function(args) { var currentTid = this.threadId; console.log('sensitive_function called in thread ' + currentTid + '. Starting Stalker.'); // Start following this thread Stalker.follow(currentTid, { events: { call: true, // Trace function calls ret: false, // Don't trace returns (can be noisy) exec: false, // Don't trace individual instructions (very noisy) block: false, // Don't trace basic block execution compile: false // Don't trace compile events }, // Callback invoked when a specific call event occurs onCallSummary: function(summary) { // The summary object contains caller and callee information // console.log(JSON.stringify(summary)); }, // For more detailed information (custom callback) // transform: function (iterator) { // var instruction = iterator.next(); // do { // // Get instruction details from the instruction object // // console.log(instruction.address + ': ' + instruction.mnemonic + ' ' + instruction.opStr); // // Do something when a specific instruction or call is detected // // if (instruction.mnemonic === 'call') { // // // ... // // } // iterator.keep(); // Necessary to let the instruction execute // } while ((instruction = iterator.next()) !== null); // }, // onReceive: function (events) { // Receive event data collected by Stalker.follow (mutually exclusive with transform) // var decodedEvents = Stalker.parse(events); // console.log(JSON.stringify(decodedEvents)); // } }); // Example of stopping the stalker after a certain time setTimeout(function() { console.log('Stopping Stalker for thread ' + currentTid); Stalker.unfollow(currentTid); // Process collected data if needed // Stalker.flush(); // Force sending of any remaining data in the queue }, 5000); // Trace for 5 seconds }, onLeave: function(retval) { // Stopping the Stalker is often done asynchronously }
});
// Note: Stalker.follow pauses the target thread and rewrites its code,
// which can cause significant performance degradation and, in some cases, crashes.
// Filtering events and limiting the scope is crucial.
RPC (Remote Procedure Call)
You can define functions in a Frida script and call them from an external Python script. Useful for data exchange and complex control.
Frida Script Side (agent.js)
// Define and export a function that can be called from Python
function addNumbers(a, b) { console.log('[Agent] addNumbers called with:', a, b); return a + b;
}
function getDeviceInfo() { console.log('[Agent] getDeviceInfo called'); var info = { platform: Process.platform, arch: Process.arch, pid: Process.id, mainModule: Process.enumerateModules()[0].name }; // Java/ObjC APIs can also be used if (Java.available) { try { Java.performNow(function() { var Build = Java.use('android.os.Build'); var VERSION = Java.use('android.os.Build$VERSION'); info.androidSdk = VERSION.SDK_INT.value; info.deviceModel = Build.MODEL.value; }); } catch (e) { info.javaError = e.message; } } return info;
}
// Example of a function with asynchronous operations (returns a Promise)
function readFileContent(path) { console.log('[Agent] readFileContent called for path:', path); return new Promise(function(resolve, reject) { try { var file = new File(path, 'r'); var content = file.readAllText(); file.close(); console.log('[Agent] Read ' + content.length + ' bytes.'); resolve(content); } catch (e) { console.error('[Agent] Error reading file:', e.message); reject(e.message); // Return the error message } });
}
// Export the functions
rpc.exports = { add: addNumbers, deviceInfo: getDeviceInfo, readFile: readFileContent // Functions returning a Promise can also be exported
};
console.log('[Agent] RPC exports ready.');
// Wait for calls from Python
// (The script itself doesn't need a loop or anything)
Python Side (control.py)
import frida
import sys
import time
def on_message(message, data): """ Callback for when a message is received from the Frida script """ if message['type'] == 'send': print(f"[Frida Agent] {message['payload']}") elif message['type'] == 'error': print(f"[Frida Error] {message['stack']}") else: print(f"[Frida Message] {message}")
# Attach to the target process (e.g., attach to a running 'cat')
# In practice, you would check the PID or name with frida-ps etc.
target_process = "cat" # or PID, Android/iOS app name, etc.
try: # To attach to a process on a USB device # device = frida.get_usb_device(timeout=1) # session = device.attach(target_process) # To attach to a local process session = frida.attach(target_process)
except frida.ProcessNotFoundError: print(f"Error: Process '{target_process}' not found.") sys.exit(1)
except frida.TimedOutError: print("Error: Timed out waiting for USB device.") sys.exit(1)
except Exception as e: print(f"Error attaching to process: {e}") sys.exit(1)
# Load the agent script
script_path = "agent.js"
try: with open(script_path, "r", encoding="utf-8") as f: script_code = f.read()
except FileNotFoundError: print(f"Error: Script file '{script_path}' not found.") session.detach() sys.exit(1)
script = session.create_script(script_code)
# Set the message handler
script.on('message', on_message)
# Load the script
print("[Python] Loading script...")
script.load()
print("[Python] Script loaded. Accessing RPC exports...")
# Wait a moment for RPC functions to become available (depends on the situation)
time.sleep(1)
# Call the exported functions
try: # Simple synchronous call result_add = script.exports.add(10, 25) print(f"[Python] RPC call add(10, 25) returned: {result_add}") # Synchronous call to get information dev_info = script.exports.device_info() print("[Python] RPC call deviceInfo() returned:") for key, value in dev_info.items(): print(f" - {key}: {value}") # Call an asynchronous function that returns a Promise (Python waits synchronously) # Example of causing an error by trying to read a non-existent file try: print("[Python] Calling RPC readFile('/path/to/nonexistent/file.txt')...") content = script.exports.read_file('/path/to/nonexistent/file.txt') print(f"[Python] RPC readFile() returned: {len(content)} bytes") except frida.InvalidOperationError as e: # If the script side calls reject(errorMessage), # a frida.InvalidOperationError is raised on the Python side, # and the error message can be retrieved. print(f"[Python] RPC readFile() failed as expected: {e}") # Example of reading an existing file (depends on the target environment) # Example: for Android # target_file = "/data/local/tmp/mydata.txt" # try: # print(f"[Python] Calling RPC readFile('{target_file}')...") # content = script.exports.read_file(target_file) # print(f"[Python] RPC readFile('{target_file}') returned:\n{content[:200]}...") # Print first 200 chars # except frida.InvalidOperationError as e: # print(f"[Python] RPC readFile('{target_file}') failed: {e}")
except frida.RPCException as e: print(f"[Python] RPC Error: {e}")
except AttributeError: print("[Python] Error: RPC exports not available yet or script error.")
except Exception as e: print(f"[Python] An unexpected error occurred: {e}")
finally: # Unload the script and detach from the session print("[Python] Unloading script and detaching...") try: script.unload() session.detach() except frida.TransportError: print("[Python] Target process may have already terminated.") except Exception as e: print(f"[Python] Error during cleanup: {e}")
print("[Python] Finished.")