使用 Frida 匯出介面¶
您可以透過此功能實現將 frida RPC 匯出的方法作為 FIRERPA 介面擴充呼叫。您可以透過自行編寫功能性 Hook 程式碼實現對 APP 控制的極致。此功能需要您熟悉 frida hook 腳本的編寫,如果您已有現存腳本,您需要對您的腳本做稍微的改寫,使用我們的特定方法來實現。
注意
編寫匯出腳本¶
您需要編寫特定格式的腳本,匯出函數名稱必須遵循命名規範,務必是首字母小寫的 camelCase 駝峰式命名。對於定義性的名稱如 HTTP,方法命名不可使用全大寫格式,如名稱 sendHTTPRequest,應在匯出腳本中寫為 sendHttpRequest 而非使用全大寫 HTTP,以下是一個腳本應遵循的大致結構。
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() {
// 在 JVM 執行緒上執行
})
程式碼區塊 performRpcJVMCall 代表在 JVM 中執行您的程式碼,您可以在區塊裡使用 JVM 相關功能,例如 Java.use 或其他涉及到應用 Java 層的操作等。
return performRpcJVMCallOnMain(function() {
// 在 UI 執行緒上執行
})
程式碼區塊 performRpcJVMCallOnMain,代表在 JVM 中並且在 UI 主執行緒中執行您的程式碼,對於涉及到 UI 或主執行緒的操作,您需要在這個程式碼區塊裡執行才能成功,同時需要注意您的程式碼不會阻塞主執行緒執行,否則可能會導致應用無回應甚至崩潰。
return performRpcCall(function() {
// 執行一般的 JS 程式碼
})
程式碼區塊 performRpcCall,您只能在此執行一些基礎的 js 程式碼,不可在此使用 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 進行呼叫,或者,如果您熟悉 JsonRPC 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 始終處於前景執行的狀態。