Frida を使用したインターフェースのエクスポート

この機能を通じて、Frida RPC でエクスポートされたメソッドを FIRERPA インターフェースの拡張として呼び出すことができます。独自の機能的なフックコードを作成することで、アプリの制御を最大限に高めることができます。この機能を使用するには、Frida フックスクリプトの作成に精通している必要があります。既存のスクリプトがある場合は、当社の特定の方法を使用するために、スクリプトを少し修正する必要があります。

注目

9.0 以降、内蔵の Frida 17.x では、`frida-java-bridge` をスクリプトに手動でバンドルする必要があります。そうしないと、`Java not defined` 関連のエラーが発生します。この変更は Frida 公式の変更によるもので、公式の変更説明によれば、新しい Node.js プロジェクトを作成し、Java Bridge を導入する必要があります。詳細については、https://github.com/oleavr/frida-agent-example を参照するか、提供されている `tools/frida_script_generate.py` を使用して元の JS スクリプトを再パッケージ化してください。

エクスポートスクリプトの作成

特定の形式のスクリプトを作成する必要があります。エクスポートする関数名は、先頭が小文字の camelCase 命名規則に従う必要があります。HTTP のような定義的な名前の場合、メソッド名にすべて大文字の形式を使用することはできません。例えば、sendHTTPRequest という名前は、エクスポートスクリプトではすべて大文字の HTTP を使用せず、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
        })
}})

上記のサンプルスクリプトでは、3つのメソッド定義が確認できます。その中で return performRpc パターンのコードブロックは必須であり、値が正しく返されることを保証します。これら3つの異なる performRpc 呼び出しコードブロックが表す意味はそれぞれ次のとおりです:

return performRpcJVMCall(function() {
        // JVM スレッドで実行
})

performRpcJVMCall コードブロックは、JVM 内でコードを実行することを意味します。このブロック内では、Java.use やアプリケーションの Java レイヤーに関わるその他の操作など、JVM 関連の機能を使用できます。

return performRpcJVMCallOnMain(function() {
        // UI スレッドで実行
})

performRpcJVMCallOnMain コードブロックは、JVM 内かつ UI メインスレッドでコードを実行することを意味します。UI やメインスレッドに関わる操作は、このコードブロック内で実行しないと成功しません。同時に、コードがメインスレッドの実行をブロックしないように注意する必要があります。さもなければ、アプリケーションの無応答やクラッシュを引き起こす可能性があります。

return performRpcCall(function() {
        // 通常の JS コードを実行
})

performRpcCall コードブロックでは、基本的な JS コードのみを実行でき、Java レイヤーのロジックを使用することはできません。

注目

Java のロジックが正常に実行されることを保証するために、依然として `Java.perform` を使用してコードブロック全体をラップする必要がある場合があります。

これで、コードの基本的な作成ロジックを理解いただけたかと思います。ご自身のコードをこの形式にラップして適応させることができます。テスト目的であれば、このコードを直接コピーして体験を開始することもできます。以下では、このフックコードを使用して説明を進めます。

エクスポートスクリプトのインジェクト

ここでのエクスポートスクリプトのインジェクト方法は、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 経由での呼び出しもサポートしています。以下の形式で使用できます。

豆知識

バージョン 8.15 以降、エクスポートされたメソッドは JSON-RPC 2.0 プロトコルでの呼び出しもサポートしています(MultiCall モードはまだサポートされていません)。

以下のように、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 はエクスポートされた関数名です。リクエストのパラメータ argsjson.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 インターフェース経由での呼び出しには特定のステータスコードがあります。これに基づいて判断する必要があるかもしれません。ステータスとその意味は以下のとおりです。

ステータスコードステータスコードの説明
200正常
410スクリプトがインジェクトされていない、またはインストールされていない
500スクリプトまたはパラメータの異常
400パラメータエラー

トラブルシューティング

API または HTTP インターフェース経由でインターフェースを呼び出す際にフリーズやタイムアウトの問題が発生した場合、その原因はアプリケーションがバックグラウンドでシステムによって強制的にスリープ状態にされている可能性が高いです。そのため、アプリを常にフォアグラウンドで実行状態に保つ必要があるかもしれません。