Frida 匯出介面¶
您可以透過此功能將 frida RPC 匯出的方法作為 FIRERPA 介面擴充呼叫。透過自行編寫功能性的 Hook 指令稿,您可以實現對 APP 的極致控制、簽章匯出等。需要您熟悉 frida 指令稿的編寫。
注意
自 9.0 版本開始,內建的 Frida 17.x 需要您自行將 frida-java-bridge 打包進指令稿,否則會出現 Java not defined 相關錯誤,此項變更屬於 Frida 官方的變更。依據官方的變更說明,您需要新建 Node.js 專案並引入 frida-java-bridge,詳細請參考:https://github.com/oleavr/frida-agent-example ,或者使用我們提供的 frida_script_generate.py 對 js 指令稿進行打包。
編寫匯出指令稿¶
您需要編寫特定格式的指令稿,匯出函式名稱必須遵循命名規範:必須使用首字母小寫的駝峰 camelCase 命名法。對於縮寫詞如 HTTP,方法名稱不得使用全大寫格式。例如,sendHTTPRequest 應在匯出指令稿中寫為 sendHttpRequest。以下是一個指令稿應遵循的大致結構。
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
})
})
在上面的範例指令稿中,您可以看到三種方法定義。其中,return performRpc 模式的程式碼區塊必須使用,以確保您的值正確返回。這三種不同的 performRpc 呼叫程式碼區塊所代表的意義分別為:
return performRpcJVMCall(function() {
// Execute on JVM thread
})
程式碼區塊 performRpcJVMCall 代表在 JVM 中執行您的程式碼,您可以在區塊裡使用 JVM 相關功能,例如 Java.use 或其他涉及到應用 Java 層的操作。
return performRpcJVMCallOnMain(function() {
// Execute on UI thread
})
程式碼區塊 performRpcJVMCallOnMain 代表在 JVM 的 UI 主執行緒中執行您的程式碼。對於涉及 UI 或主執行緒的操作,您需要在該程式碼區塊中執行才能成功。同時,請注意確保您的程式碼不會阻塞主執行緒,否則可能導致應用無回應甚至崩潰。
return performRpcCall(function() {
// Execute Normal JS code
})
程式碼區塊 performRpcCall 只能用於執行基礎 JS 程式碼,不可在此使用 Java 層的邏輯。
注意
您可能仍然需要使用 Java.perform 來封裝整體程式碼區塊,以確保 Java 邏輯正常執行。
現在,您已了解程式碼的基本編寫邏輯。您可以將您的程式碼封裝成此格式進行適配。如果您只是想測試,也可以直接複製這段程式碼來體驗。我們下面也將基於此 Hook 程式碼進行講解。
注入匯出指令稿¶
這裡注入匯出指令稿的方法與 持久化 Frida 指令稿 章節中的方法完全相同。
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
呼叫注入介面後,指令稿即已注入,您可以透過以下呼叫檢查指令稿是否正常。
app.is_script_alive()
呼叫匯出方法¶
呼叫指令稿內匯出方法的方式與您原生使用 FIRERPA 介面非常相似
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
對於多開應用,您需要取得多開應用的實例:
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
這兩個呼叫方式等價,您會發現方法名稱中的 Func1 變成了 _func1,兩種方式均可。
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'
呼叫匯出方法(HTTP 介面)¶
除了支援客戶端呼叫外,我們還支援透過 HTTP 方式呼叫,可按以下形式使用。
您可以使用 jsonrpclib 進行呼叫,如下所示;或者,如果您熟悉 JSON-RPC 2.0 協定,也可以自行編寫程式碼發送請求——這是最規範且可參考文件最全的協定實現。
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
當然,您也可以繼續使用之前的呼叫協定,相對簡單,但不夠規範。
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"])
連結中的查詢參數 user 表示應用的 UID,預設為 0,多開應用須指定 UID。
以上程式碼透過 HTTP 方式呼叫指令稿匯出介面。com.android.settings 為應用套件名稱,exampleFunc1 為匯出的函式名稱。請求參數 args 必須使用 json.dumps 序列化。參數列表支援多個參數,由方法參數個數决定,提供空列表 [] 表示該匯出函式無參數。
如果 FIRERPA 啟用了介面憑證,則需使用 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 介面呼叫會返回特定的狀態碼,可根據狀態碼進行狀態判斷。
問題排查¶
如果您透過 API 或 HTTP 方式呼叫介面時出現卡死或逾時,大概率是因為應用處於背景被系統強制休眠。因此,您需要確保 APP 始終處於前景執行狀態。