Exporting Interfaces with Frida¶
This feature allows you to use methods exported via Frida RPC as extended FIRERPA interfaces. You can achieve ultimate control over the app by writing your own functional hook code. This feature requires familiarity with writing Frida hook scripts. If you have existing scripts, you will need to slightly modify them to use our specific methods.
Attention
Writing the Export Script¶
You need to write scripts in a specific format. Exported function names must follow the naming convention, which must be camelCase with a lowercase first letter. For definitional names like HTTP, the method name should not use all uppercase letters. For example, a name like sendHTTPRequest should be written as sendHttpRequest in the export script, not using the all-caps HTTP. The following is the general structure a 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 script above, you can see three types of method definitions. The return performRpc code block pattern is mandatory to ensure your values are returned correctly. The meanings of these three different performRpc call blocks are as follows:
return performRpcJVMCall(function() {
// Execute on JVM thread
})
The performRpcJVMCall code block means your code will be executed in the JVM. Inside this block, you can use JVM-related features, such as Java.use or other operations involving the app's Java layer.
return performRpcJVMCallOnMain(function() {
// Execute on UI thread
})
The performRpcJVMCallOnMain code block means your code will be executed in the JVM and on the main UI thread. For operations involving the UI or the main thread, you must execute them within this block to succeed. At the same time, you need to ensure your code does not block the main thread, as this could cause the app to become unresponsive or even crash.
return performRpcCall(function() {
// Execute Normal JS code
})
The performRpcCall code block allows you to execute only basic JS code. You cannot use Java layer logic here.
Attention
Now that you understand the basic logic for writing our code, you can adapt your own code by wrapping it in this format. If you just want to test it, you can also copy this code directly to get started. We will use this hook code for the explanations below.
Injecting the Export Script¶
The method for injecting the export script here is exactly the same as the one described in the Persisting Frida Scripts chapter.
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 injected. You can execute the following call to check if the script is running correctly.
app.is_script_alive()
Calling Exported Methods¶
Calling the exported methods within the script is very similar to using native FIRERPA interfaces. Please see the example code.
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
For multi-instance apps, you need to get the instance of the specific app.
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
The calling method above is equivalent to the one below. You can see that Func1 in the method name has been changed to _func1. Both forms are acceptable.
app.example_func1("FIRE", "RPA")
>>> 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 (HTTP Interface)¶
In addition to supporting calls from the client, we also support calling via HTTP. You can use it in the following format.
Tip
You can make calls directly using jsonrpclib as shown below. Alternatively, if you are familiar with the JSON-RPC 2.0 protocol, you can write your own code to make requests. This is the most standardized protocol implementation with the most comprehensive documentation available.
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
Of course, you can still continue to use the previous calling protocol shown below, which is relatively 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 is the UID of the app instance. It defaults to 0 and does not need to be specified. For multi-instance apps, you must specify the UID here.
The code above calls the script's exported interface via HTTP. com.android.settings is the application package name, and exampleFunc1 is the name of the exported function. The request parameter args must be serialized using json.dumps. The parameter list can also contain multiple arguments, depending on the number of parameters your method requires. Providing an empty list [] indicates that the exported function has no parameters.
If your FIRERPA has interface certificates enabled, you need to use HTTPS for access 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¶
When calling via the HTTP interface, there are specific status codes that you may need to check. The statuses and their meanings are as follows:
| Status Code | Description |
|---|---|
| 200 | OK |
| 410 | Script not injected or not installed |
| 500 | Script or parameter exception |
| 400 | Parameter error |
Troubleshooting¶
If you encounter freezing or timeout issues when calling the interface via the API or HTTP, it is most likely because your app is in the background and has been forced to sleep by the system. Therefore, you may need to keep the app running in the foreground at all times.