# Frida を使用したデータレポート

Frida を使用したデータレポート機能は永続化機能に基づいています。自身で作成した Frida スクリプトを介してメソッド呼び出しのデータを自動的にインターセプトし、我々が提供する特定のメソッドを通じてデータをレポートすることができます。データレポート機能を使用すると、スクリプトが取得したデータを Redis や外部の HTTP インターフェースに直接アップロードすることが容易になります。Redis や HTTP インターフェースを介してレポートされたデータコンテンツを受信できます。また、ネットワーク効率を最大化するため、データ圧縮後（zlib）のレポートもサポートしています。

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

## レポートスクリプトの作成

まず、Frida スクリプトを修正する必要があります。通常、Frida スクリプトには `send` や `log` といった機能があり、外部にデータを送信できます。FIRERPA の場合、特定のメソッドを使用してデータを送信する必要があります。以下は、okhttp のトラフィックをインターセプトするためのテンプレートコードを使用したデモです。これはあくまでデモスクリプトであり、このスクリプトを正常に使用できない場合があります。以下のスクリプトは通常のスクリプトと大きな違いはありません。唯一の違いは `emit` メソッドを使用している点です。これは FIRERPA 独自のメソッドであり、これを使用することで、簡単かつ体系的に外部にデータを送信できます。

```js
Java.perform(function() {
        Java.use("com.android.okhttp.internal.http.HttpEngine").getResponse.implementation = function() {
                var response = this.getResponse()
                var data = {}
                data["url"] = response._request.value._url.value._url.value
                data["body"] = response.body().string()
                emit("report_data", JSON.stringify(data))
                return response
        }
})
```

`emit` メソッドには `emit(name, content)` という2つの引数があります。`name` はデータのタイプを表します。レポート先のアドレスを redis に設定した場合、この名前は redis のキュー名を表します。できるだけ正確な英語で記述する必要があります（例：`product_info`）。`content` はデータの内容を表し、サポートされる型は文字列(string)とバイト配列(bytes)のみです。上記の例では、JSON 文字列に変換して送信しています。

これで、スクリプト作成に必要なフォーマットと呼び出し要件、つまりフックしたデータを外部に送信する方法について理解できました。次に、**データレポートの送信先**を設定する方法について、以下の内容を読み進めてください。

## データレポートの送信先

データレポートの送信先は、スクリプト内で `emit` したデータをどこに送信するかを定義します。送信先として、HTTP インターフェース、Redis キュー、RabbitMQ キューがサポートされており、これらにはいくつかの違いがあります。

通常、データの送信元などの情報、つまり**データ**と**送信元マシン**を一致させる必要がない場合は、Redis キューの使用をお勧めします。逆に、一致させる必要がある場合は、HTTP または MQTT (v5) を使用すべきです。これらのプロトコルの特性上、プロトコルにより多くのメタデータを付与できるため、より正確なデバイスマッチングが可能になります。

### レポートされるメタデータ

HTTP インターフェースおよび RabbitMQ を送信先とするデータには、元のレポートデータに加えて、プロトコル層からデバイスとスクリプトに関するメタデータを取得できます。プロトコルに含まれるメタデータは以下の表の通りです。

| フィールド | 説明 |
|------------|----------------------------------------|
| application| アプリケーションのパッケージ名（例：com.android.settings） |
| device | デバイス ID（例：67b2a3d7-5004-ea2a-0d44-194de6ede8de） |
| encode | データエンコーディング（例：none） |
| name | データ名（例：report_data） |
| script | スクリプト ID（例：7c52530d） |
| sequence | レポートシーケンス（例：30） |
| timestamp | レポートタイムスタンプ（例：1740023596914） |
| user | マルチインスタンスアプリ ID（例：0） |

```{attention}
Redis プロトコルの特性上、Redis を送信先とするデータには上記のメタデータは一切含まれません。
```

`device` はデバイスの一意の ID です。リモートデスクトップの情報欄で確認できます。通常、この ID は一意で固定です。これを使用してデバイスをマークし、対応関係を確立できます。`encode` はデータエンコーディングで、`none` と `zlib` をサポートしています。`zlib` でエンコードされている場合、データ本体を zlib で解凍する必要があります。`name` はこのレポートデータのタイプをマークするために使用され、`emit` メソッドの最初の引数でもあります。`sequence` はレポートデータのインデックスを表し、0 から始まり、レポートごとにインクリメントされます。このフィールドを使用してデータをソートしたり、レポートの欠落がないかを確認したりできます。


### レポート URL の追加パラメータ

レポート URL を組み立てる際に、URL 内に動的な ID パラメータを指定することがサポートされています。変数プレースホルダー `${name}` の形式で、URL の特定の部分に挿入できます。`http://192.168.1.2/report/${device_id}` のような形式で使用します。サポートされている変数は以下の通りです。


| 名前 | 説明 |
| ----------- | ----------- |
| device_id | デバイスの一意の ID |
| device_id_short | デバイスの一意の ID（BASE62 でエンコードされた短いデバイス ID） |
| android_id | Android ID |
| serialno | ro.serialno |


### HTTP へのレポート

レポートデータを受信するための HTTP サービスを自身で作成する必要があります。HTTP レポートインターフェースは POST メソッドを実装する必要があります。FIRERPA は POST を介してインターフェースにデータをレポートします。同時に、メタデータの各フィールドを HTTP クエリパラメータとしてエンコードします。そこから抽出して処理することができます。データ本体は、POST リクエストの body に含まれます。FIRERPA は 502、503、504 ステータスコードを受信した場合、自動的に 3 回リトライします。バックエンドがレポートデータを正しく受信して処理した場合、プレーンテキストの `OK` または `SUCCESS` を返し、ステータスコードを `200` に設定して処理の成功を示す必要があります。

```{attention}
HTTP リクエストはマルチスレッドであるため、バックエンドが受信するメッセージはレポートシーケンス (`sequence`) に従わない可能性があります。
```

```
レポート URL の例 http://192.168.1.2/report/${device_id}?serialno=${serialno}
パスワード認証が必要な場合 http://user:password@192.168.1.2/report/${device_id}?serialno=${serialno}
```

### MQTT へのレポート

MQTT へのレポートでは、レポートされるメタデータは `UserProperty` から抽出する必要があります。TLS、ユーザー名/パスワード認証、一方向証明書検証（サーバー証明書の検証）をサポートしており、双方向検証はサポートしていません。

```
レポート URL の例 mqtt://test.mosquitto.org:1883/script/${device_id}/report
パスワード認証が必要な場合 mqtt://rw:readwrite@test.mosquitto.org:1884/script/${device_id}/report

一方向検証が必要な場合 mqtts://test.mosquitto.org:8883/script/${device_id}/report?verify=true&ca=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVBekNDQXV1Z0F3SUJBZ0lVQlkxaGxDR3ZkajROaEJYa1ovdUxVWk5JTEF3d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2daQXhDekFKQmdOVkJBWVRBa2RDTVJjd0ZRWURWUVFJREE1VmJtbDBaV1FnUzJsdVoyUnZiVEVPTUF3RwpBMVVFQnd3RlJHVnlZbmt4RWpBUUJnTlZCQW9NQ1UxdmMzRjFhWFIwYnpFTE1Ba0dBMVVFQ3d3Q1EwRXhGakFVCkJnTlZCQU1NRFcxdmMzRjFhWFIwYnk1dmNtY3hIekFkQmdrcWhraUc5dzBCQ1FFV0VISnZaMlZ5UUdGMFkyaHYKYnk1dmNtY3dIaGNOTWpBd05qQTVNVEV3TmpNNVdoY05NekF3TmpBM01URXdOak01V2pDQmtERUxNQWtHQTFVRQpCaE1DUjBJeEZ6QVZCZ05WQkFnTURsVnVhWFJsWkNCTGFXNW5aRzl0TVE0d0RBWURWUVFIREFWRVpYSmllVEVTCk1CQUdBMVVFQ2d3SlRXOXpjWFZwZEhSdk1Rc3dDUVlEVlFRTERBSkRRVEVXTUJRR0ExVUVBd3dOYlc5emNYVnAKZEhSdkxtOXlaekVmTUIwR0NTcUdTSWIzRFFFSkFSWVFjbTluWlhKQVlYUmphRzl2TG05eVp6Q0NBU0l3RFFZSgpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNRTBIS21JemZUT3drS0xUM1RISGUrT2JkaXphbVBnClVabUQ2NFRmM3pKZE5lWUdZbjRDRVhieVA2ZnkzdFdjOFMyYm9XNmR6ckg4U2RGZjl1bzMyMEdKQTlCN1UxRlcKVGUzeGRhL0xtM0pGZmFIamtXdzdqQndjYXVRWmpwR0lOSGFwSFJscGlDWnNxdUF0aE9neFc5U2dEZ1lsR3pFQQpzMDZwa0VGaU13K3FEZkxvL3N4RktCNnZRbEZla01lQ3ltakxDYk53UEp5cXloRm1QV3dpby9QRE1ydUJUelBICjNjaW9CbnJKV0tYYzNPalhkTEdGSk9majdwUDBqL2RyMkxINzJlU3Z2M1BRUUZsOTBDWlBGaHJDVWNSSFNTeG8KRTZ5akdPZG56N2Y2UHZlTElCNTc0a1FPUnd0OGVQbjB5aWRyVEMxaWN0aWtFRDNuSFloTVVPVUNBd0VBQWFOVApNRkV3SFFZRFZSME9CQllFRlBWVjZ4QlVGUGlHS0R5bzVWMytIYmg0TjlZU01COEdBMVVkSXdRWU1CYUFGUFZWCjZ4QlVGUGlHS0R5bzVWMytIYmg0TjlZU01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUwKQlFBRGdnRUJBR2E5a1MyMU43MFRoTTYvSGo5RDdtYlZ4S0xCalZXZTJUUHNHZmJsM3JFRGZaK09LUloyajZBQwo2cjdqYjRUWk8zZHpGMnA2ZGdicmxVNzFZLzRLMFRkeklqUmozY1EzS1NtNDFKdlVRMGhaL2MwNGlHRGcveFdmCitwcDU4bmZQQVl3dWVycnVQTldtbFN0V0FYZjBVVHFSdGc0aFFEV0J1VUZESlR1V3V1QnZFWHVkejc0ZWgvd0sKc013ZnUxSEZ2ank1WjBpTURVOFBVRGVwalZvbE9DdWU5YXNobFM0RUI1SUVDZFNSMlRJdG5BSWlJd2lteDgzOQpMZFVkUnVkYWZNdTVUNVhtYTE4Mk9DMC91L3hSbEVtK3R2S0dHbWZGY04wcGlxVmw4T3JTUEJnSWxiKzFJS0pFCm0vWHJpV3IvQ3E0aC9KZkI3TlRzZXpWc2xna0Jhb1U9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
```

上記の URL では、関連データは `script/${device_id}/report` に送信されます。`mosquitto_sub -L mqtt://test.mosquitto.org:1883/script/+/report` のようなコマンドでこれらのメッセージをサブスクライブできます。

### Redis へのレポート

Redis へのレポートは比較的シンプルです。メタデータが含まれていないため、データソースを直接区別することはできません。この動作を実現するには、インジェクトするスクリプトを動的に書き換える必要があるかもしれません。Redis へのレポートでは、FIRERPA はデータ本体を `LPUSH` を介して直接キューにプッシュします。例えば、前述のサンプルスクリプトでは、レポートデータは `report_data` キューにプッシュされます。

```{attention}
スタンドアロンモードの Redis サービスのみをサポートしており、Redis クラスタはサポートしていません。
```

```{attention}
パスワードフィールドを除き、redis の URL に変数プレースホルダーを追加しないでください。URL は標準の redis ライブラリがサポートする形式であり、他の部分を変更すると正しく解析できなくなる可能性があります。
```

```
レポート URL の例 redis://1.2.3.4/0
パスワード認証が必要な場合 redis://:password@1.2.3.4/0

TLS + パスワード

rediss://:password@1.2.3.4/0?ssl_ca_data=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURUVENDQWpXZ0F3SUJBZ0lVUFIvcmcxK0x2aU5tYzNsc0...
```

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

もちろん、app インスタンスの取得が最初のステップです。上記の呼び出しで `app` 変数を取得できます。これは、インジェクト対象のアプリケーションのインスタンスを表します。その後、これを使用して後続のインジェクトまたはデタッチ操作を続行する必要があります。

```python
app = d.application("com.android.settings")
```

以下のインターフェースを介して、上記で作成したレポートスクリプトをアプリケーションにインジェクトします。データがインターセプトされると、データは Redis の `report_data` キューに送信されます。この例では、デバイスが 192.168.1.10 上の関連サービスに直接アクセスできる必要があります。そうでない場合、データレポートは失敗します。

```python
app.attach_script(script, emit="redis://192.168.1.10/0")
```

このように設定することもできます。この場合、データは Redis ではなく http インターフェースに送信されます（https をサポート）。

```python
app.attach_script(script, emit="http://192.168.1.10/dataReport")
```

レポートするデータが大きい場合、圧縮を有効にするとネットワーク転送のスループットを大幅に向上させることができます。`encode` を使用してデータレポートの圧縮機能を有効にできます。もちろん、その後、受信側でレポートデータを解凍する必要があります。

```python
app.attach_script(..., encode=DataEncode.DATA_ENCODE_ZLIB)
```

## レポートデータの解凍

デフォルトでは、レポートデータは圧縮されません。レポートの圧縮を設定した場合、受信側で zlib を使用してレポートデータを解凍する必要があります。Python の公式ライブラリである zlib の `decompress` メソッドを使用すると、レポートデータを簡単に解凍できます。

```python
zlib.decompress(data)
```

## レポートスクリプトの削除

レポートスクリプトの削除プロセスは簡単で、永続化スクリプトでの使用方法と同じです。

```python
app.detach_script()
```