What are Frida APIs?
They are part of the Frida dynamic instrumentation toolkit, they allow one to interact with and modify the behaviour of applications and processes at runtime. Think of them as tools to ‘inject’ custom scripts into an application to monitor or manipulate its behaviour.
Core Concepts of Frida APIs
- Injection
Frida allows you to inject Javascript code into running processes (like apps in android), they injected script can interact with the application’s memory, functions, and data in real time.
- Runtime Interaction.
Once the script is injected, it communicates with the Frida client on your computer, letting you inspect or modify behaviour.
- Cross-platform.
Frida works with native (C/C++) and managed (Java, .Net) applications.
Key Frida APIs and their Uses
- Module
The
Module
API is used to interact with loaded libraries or binaries in the target process. You can enumerate their exports, imports, and base addresses.Main Methods
Module.findExportByName(name, exportName)
:Finds the address of an exported function or variable.
name
: Name of the Module (for instancelibnative-lib.so
, ornull
for the main executable).exportName
: Name of the exported symbol (function or variable).Module.enumerateExports(name)
: lists all imported symbols of a module.Module.enumerateImports(name)
: Lists all imported symbols of a module.Module.findBaseAddress(name)
: Finds the base address of a loaded module.Example:
Find and list all exports of a module:
let exportedModules = Module.enumerateExports("libc.so"); exportedModules.forEach(function (export) { console.log("Name: " + export.name + ", Address: " + export.address); });
Find and call a specific function:
let myFunction = Module.findExportByName("libnative-lib.so", "myFunction"); let nativeFunction = new NativeFunction(myFunction, "int", ["int", "int"]); console.log(nativeFunction(10, 20));
- Interceptor
The interceptor API is used to hook into functions in the target application. It allows you to monitor, modify, or replace the behaviour of those functions.
Main Methods
Interceptor.attach()
: Hooks into a specific function to observe or modify its behaviour.Arguments
address
: The memory address of the function to hook into (can be obtained usingModule.findExportByName()
or other methods).
callBacks
: an object defining what to do when a function is entered or exited.
onEnter(args)
: Called when the function is invoked.args
is an array-like object containing arguments when passed to the function.onLeave(retval)
: Called when the function returns.retval
represents the return value.Example:
Hook into a function and print its arguments:
let systemCall = Module.findExportByName("libc.so", "fopen"); Interceptor.attach(systemCall, { onEnter: function(args) { let argPassed = Memory.readCString(args[0]); console.log("Argument passed: " + argPassed); }, onLeave: function(retval) { console.log("Return value: " + retval); } });
- Java
The
Java
API is used to work with Java code in Android applications. You can enumerate classes, hook methods, or call methods at runtime.Main Methods
Java.perform()
: Executes a callback in the context of the Java runtime.Java.use(className)
: Returns a wrapper for a Java class that you can interact with (call methods, access fields).Java.enumerateLoadedClasses()
: Lists all loaded classes in the application.Java.enumerateClassLoaders()
: Lists all class loaders, useful for accessing hidden classes.- ‘Java.choose(className, callback)’: Finds all live instances of a given class.
Example:
Hook a method in a Java class:
Java.perform(function() { let myClass = Java.use("com.example.MyClass"); myClass.myMethod.implementation = function (arg1, arg2) { console.log("Original arg1: " + arg1 + ", arg2: " + arg2); return this.myMethod("newArg1, newArg2"); //call the method with new args } });
List all methods of a class:
Java.perform(function() { let myClass = Java.use("com.example.MyClass"); console.log(myClass.class.getMethods()); });
Access a static field of a class:
Java.perform(function() { let myClass = Java.use("com.example.MyClass"); console.log("Static field value: " + myClass.myStaticField.value); });
- Memory
The
Memory
API is used to read, write and scan memory regions of a target process. It’s useful for tasks like dumping or altering values directly in memory.Main Methods
Memory.read*()
: Reads data from a specific memory address.
Memory.readUtf8Strings(address)
: Reads a null terminated UTF-8 string.Memory.readByteArray()
: Reads a raw byte array.Memory.write*()
: Writes data to a specific memory address.
Memory.writeUtf8String(address, string)
: Writes a UTF-8 string.Memory.writeInt(address, value)
: Writes an integer.Example:
Read and write to Memory:
let baseAddress = Module.findBaseAddress("libnative-lib.so"); let offset = 0x1234 let address = baseAddress.add(offset); console.log("Original value: " + Memory.readInt(address)); Memory.writeInt(address, 42); // Change value in memory console.log("Modified value: " + Memory.readInt(address));
How do these API work together?
You can combine these APIs to perform complex tasks, for instance:
- You can use the
Module
api to find the base address of a library or locate a function. - You can then combine it with the
Interceptor
to hook the function and modify its behaviour. - You can use
Memory
to manipulate or inspect the memory regions associated with the hooked function. - For Java code, you can use
Java
to hook Java methods in Android apps and inspect their interactions with native libraries.
With Frida in your toolkit, no app is too sneaky, no function too hidden, and no memory too Untouchable. With great power comes great responsibility - and occasional crash.
Happy hooking!