Frida Export Interface¶
You can use this feature to call methods exported via Frida RPC as extensions of the FIRERPA interface. By writing your own functional Hook scripts, you can achieve ultimate control over the app, signature exporting, and more. Familiarity with writing Frida scripts is required.
Attention
Starting from version 9.0, the built-in Frida 17.x requires you to bundle the frida-java-bridge into your script yourself. Otherwise, errors related to Java not defined will occur. This change is an official Frida modification. According to the official change notes, you need to create a Node.js project and introduce frida-java-bridge. For details, refer to: https://github.com/oleavr/frida-agent-example , or use our provided frida_script_generate.py to package your js script.
Writing Export Scripts¶
You need to write scripts in a specific format. Exported function names must follow naming conventions: camelCase must be used with the first letter lowercase. For acronyms such as HTTP, function names must not be in all uppercase. For example, sendHTTPRequest should be written as sendHttpRequest in the export script. Below is an approximate structure that 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 above sample script, you can see three ways of defining functions. Among them, the return performRpc pattern code blocks must be used to ensure your values are correctly returned. The meanings of these three different performRpc call blocks are as follows:
return performRpcJVMCall(function() {
// Execute on JVM thread
})
The performRpcJVMCall block represents executing your code within the JVM. You can use JVM-related functionality inside the block, such as Java.use or other operations involving the app's Java layer.
return performRpcJVMCallOnMain(function() {
// Execute on UI thread
})
The performRpcJVMCallOnMain block represents executing your code on the JVM’s main UI thread. For operations involving the UI or the main thread, you need to execute them within this block to succeed. Also, make sure your code does not block the main thread, otherwise it may cause the app to become unresponsive or even crash.
return performRpcCall(function() {
// Execute Normal JS code
})
The performRpcCall block can only be used to execute basic JavaScript code; you cannot use Java layer logic here.
Attention
You may still need to use Java.perform to wrap the overall code block to ensure Java logic executes properly.
Now, you understand the basic scripting logic. You can adapt your code into this format. If you just want to test, you can also directly copy this code to experience it. Below we will continue the explanation based on this Hook code.
Injecting Export Scripts¶
The method of injecting export scripts here is completely identical to that in the Persistent Frida Script chapter.
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
After calling the injection interface, the script is injected. You can check whether the script is alive using the following call.
app.is_script_alive()
Calling Exported Methods¶
The way to call exported methods in the script is very similar to how you natively use the FIRERPA interface.
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
For apps with multiple instances, you need to get the multi-instance application instance:
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
These two calling methods are equivalent. You will notice that Func1 in the method name becomes _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 client-side calls, we also support calling via HTTP, which can be used in the following form.
You can use jsonrpclib for calls as shown below; alternatively, if you are familiar with the JSON-RPC 2.0 protocol, you can write your own code to send requests—this is the most standard protocol implementation with comprehensive documentation.
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 use the previous calling protocol, which is relatively simple but not as 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 link indicates the app's UID, defaulting to 0. For multi-instance apps, you must specify the UID.
The above code calls the script export interface 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. The parameter list supports multiple arguments, determined by the number of function parameters; providing an empty list [] means the exported function takes no parameters.
If 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¶
Calling via the HTTP interface returns specific status codes, which can be used for status determination.
Troubleshooting¶
If the interface hangs or times out when calling via API or HTTP, it is highly likely that the app is in the background and forced into sleep by the system. Therefore, you need to ensure the app is always running in the foreground.