Exporting Interfaces Using Frida¶
You can use this feature to expose methods from Frida RPC as FIRERPA interface extensions. By writing custom functional hook code, you can achieve full control over an app. This functionality requires familiarity with writing Frida hook scripts. If you already have existing scripts, you will need to slightly modify them using our specific methods to make them compatible.
Attention
Starting from version 9.0, the built-in Frida 17.x requires you to manually bundle frida-java-bridge into your script; otherwise, you may encounter errors such as “Java is not defined.” This change originates from official updates in Frida. According to the official documentation, you should create a Node.js project and include the Java bridge. For details, refer to https://github.com/oleavr/frida-agent-example, or use our provided tool tools/frida_script_generate.py to repackage your existing JavaScript scripts.
Writing Export Scripts¶
You must write scripts in a specific format. Exported function names must follow naming conventions: they should use camelCase with a lowercase first letter. For acronyms like HTTP, do not use all uppercase letters in method names. For example, instead of sendHTTPRequest, name it sendHttpRequest. Below is a general structure that your script should follow:
Java.perform(function() {
const String = Java.use("java.lang.String")
rpc.exports.exampleFunc1 = function (a, b) {
return performRpcJVMCall(function() {
return String.$new("Execute on JVM thread:" + a + b).toString()
})
}
rpc.exports.exampleFunc2 = function (a, b) {
return performRpcJVMCallOnMain(function() {
return String.$new("Execute on Main UI thread:" + a + b).toString()
})
}
rpc.exports.exampleFunc3 = function (a, b) {
return performRpcCall(function() {
return a + b
})
}})
In the example above, you can see three types of method definitions. The return performRpc... pattern is mandatory—it ensures correct return value handling. The meanings of these three different performRpc calls are as follows:
return performRpcJVMCall(function() {
// Execute on JVM thread
})
performRpcJVMCall executes your code within the JVM thread. You can use JVM-related features inside this block, such as Java.use or other operations involving the Java layer.
return performRpcJVMCallOnMain(function() {
// Execute on UI thread
})
performRpcJVMCallOnMain runs your code on the main UI thread within the JVM. Use this block for any operations related to the UI or main thread. Be cautious not to block the main thread, which could lead to application unresponsiveness or crashes.
return performRpcCall(function() {
// Execute Normal JS code
})
performRpcCall allows only basic JavaScript execution. Do not use any Java-layer logic here.
Attention
You may still need to wrap your entire code block with Java.perform to ensure proper execution of Java logic.
Now that you understand the basic coding logic, you can adapt your own code into this format. For testing purposes, feel free to copy and experiment with the sample code above—we’ll continue explaining using this hook script.
Injecting Export Scripts¶
The method for injecting export scripts is identical to the one described in the “Persistent Frida Script” section.
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
After calling the injection interface, your script should be successfully injected. You can verify its status with:
app.is_script_alive()
Calling Exported Methods¶
Calling exported methods in the script is very similar to using native FIRERPA interfaces. See the example below:
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
For multi-instance apps, you need to get the corresponding instance:
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
The above call is equivalent to the following. Note how Func1 becomes _func1—both formats are supported:
app.example_func1("FIRE", "RPA")
Example outputs:
>>> app.example_func1("FIRE", "RPA")
'Hello World:FIRERPA'
>>> app.example_func2("FRI", "DA")
'Hello World:FRIDA'
>>> app.example_func3("FIRE", "RPA")
'FIRERPA'
Calling Exported Methods via HTTP Interface¶
In addition to client-side calls, we also support invoking exported methods through HTTP. See usage examples below.
Tip
Starting from version 8.15, exported methods support calling via the JSON-RPC 2.0 protocol (multi-call mode is currently unsupported).
You can directly invoke using jsonrpclib as shown below. Alternatively, if you’re familiar with the JSON-RPC 2.0 protocol, you can implement your own requests—this is the most standardized approach with comprehensive documentation available.
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
Alternatively, you can continue using the legacy calling protocol below, which is simpler but less standardized:
import json
import requests
url = "http://192.168.0.2:65000/script/com.android.settings/exampleFunc1?user=0"
res = requests.post(url, data={"args": json.dumps(["FIRE", "RPA"])})
print(res.status_code, res.json()["result"])
The query parameter user in the URL specifies the UID of the app instance. It defaults to 0 and can be omitted unless dealing with multi-instance apps, in which case you must specify the appropriate UID.
The above code invokes exported script interfaces via HTTP. com.android.settings is the app package name, and exampleFunc1 is the exported function name. The request parameter args must be serialized using json.dumps. Multiple parameters are allowed depending on your function signature. An empty list [] indicates the exported function takes no arguments.
If your FIRERPA has interface certificate authentication enabled, you must access via HTTPS and provide the certificate password:
headers = {"X-Token": "certificate_password"}
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])}, headers=headers, verify=False)
print(res.status_code, res.json()["result"])
HTTP Status Codes¶
HTTP interface calls return specific status codes that you may need to handle. Their meanings are listed below:
| Status Code | Description |
|---|---|
| 200 | Everything is normal |
| 410 | Script not injected or not installed |
| 500 | Script error or invalid parameters |
| 400 | Invalid request parameters |
Troubleshooting¶
If API or HTTP interface calls result in freezing or timeouts, it’s likely because the app is in the background and has been forcibly suspended by the system. To resolve this, keep the app running in the foreground at all times.