使用 Frida 导出接口

您可以通过此功能实现将 frida RPC 导出的方法作为 LAMDA 接口扩展调用。您可以通过自行编写功能性 Hook 代码实现对 APP 控制的极致。此功能需要您熟悉 frida hook 脚本的编写,如果您已有现存脚本,您需要对您的脚本做稍微的改写,使用我们的特定方法来实现。

Frida Hook 接口导出及数据上报

编写导出脚本

您需要编写特定格式的脚本,这仍然不是官方 frida 的方法,导出函数名必须遵守下面的命名规范,务必是首字母小写的 camelCase。对于定义性的名称如 HTTP,您的方法命名不可使用全大写格式,比如常规名称 sendHTTPRequest,您应在此写为 sendHttpRequest,而不是使用全大写的 HTTP

Java.perform(function() {
rpc.exports = {
exampleFunc1: function (a, b) {
        return performRpcJVMCall(function() {
                var String = Java.use("java.lang.String")
                var msg = String.$new("Hello World").toString()
                return msg + ":" + a + b
        })
},
exampleFunc2: function (a, b) {
        return performRpcJVMCallOnMain(function() {
                var String = Java.use("java.lang.String")
                var msg = String.$new("Hello World").toString()
                return msg + ":" + a + b
        })
},
exampleFunc3: function (a, b) {
        return performRpcCall(function() {
                return a + b
        })
},
}
})

您可以看到三种定义方式,其中 return performRpc.. 代码块是必须有的,他用来确保您的值可以正确的返回。您的代码务必包含此代码块。对于 performRpcJVMCall,他代表在 JVM 中执行您的代码,您可以在块里使用 Frida Java 相关功能,Java.use 等。performRpcJVMCallOnMain,代表在 JVM 中执行您的代码,您可以在块里使用 Frida Java 相关功能,Java.use 等,同时,在应用的主进程执行,对于涉及到 UI 的操作,您需要在这个代码块里执行。最后一个 performRpcCall,您只能在此执行一些基础的 js 代码,不可在此编写 Java 层的逻辑。注意:虽然 performRpc 代码块可以让您执行 Java,您仍然可能需要使用 Java.perform 来封装整体代码。

现在您已经了解了我们代码的基本编写逻辑,您可以将您的代码封装成这种格式的代码,如果您只是想测试,也可以直接复制这段代码来开始体验,我们下面也是利用此 Hook 代码进行讲解。

注入导出脚本

这里的注入导出脚本的方法和 持久化 Frida 脚本 章节中的其实是完全一样的使用方法。

app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)

调用了注入接口后,您的脚本也应该就注入了,您可以执行如下调用来检查脚本是否正常。

app.is_script_alive()

调用导出方法

调用脚本内的导出方法的方式和您原生使用 LAMDA 接口是很相似的,请看示例代码。

app = d.application("com.android.settings")
app.exampleFunc1("LAM", "DA")

以上的调用方法等价于下面的,您可以发现,方法中的 大写 变成了 _小写,这两种都是可以的。

app.example_func1("LAM", "DA")
>>> app.example_func1("LAM", "DA")
'Hello World:LAMDA'
>>> app.example_func2("FRI", "DA")
'Hello World:FRIDA'
>>> app.example_func3("LAM", "DA")
'LAMDA'

调用导出方法(HTTP接口)

除了支持您使用 LAMDA 客户端调用外,我们还支持您通过 HTTP 的方式调用,HTTP 方式调用与客户端调用有一些区别。

import json
import requests
url = "http://192.168.0.2:65000/script/com.android.settings/exampleFunc1"
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])})
print (res.status_code, res.json()["result"])

使用以上代码来通过 HTTP 的方式调用导出接口。其中 com.android.settings 代表应用的包名,exampleFunc1 代表导出函数的名称。请求的参数 args 必须使用 json.dumps 进行序列化。参数列表也可以同时使用多个参数,依据您的方法参数个数决定,提供空列表 [] 代表该导出函数无参数。

如果您的 LAMDA 启用了接口证书,那么您需要使用 https 方式访问,并且需提供证书密码。

headers = {"X-Token": "证书密码"}
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])}, headers=headers, verify=False)
print (res.status_code, res.json()["result"])

HTTP 状态码

通过 HTTP 接口调用有特定的状态码,您可能需要根据他做判断,状态及其意义如下

状态码 200 一切正常
状态码 410 脚本未注入或未安装
状态码 500 脚本或参数异常
状态码 400 参数错误

问题排查

如果您通过 API 或者 HTTP 接口方式调用接口出现了卡死或者超时的问题,那大概率是因为您的应用处于后台被系统强制休眠,所以您可能需要使 APP 始终处于前台运行的状态。