# 使用 Frida 上報資料

使用 Frida 上報資料的功能基於持久化功能，您可以透過您編寫的 Frida 腳本自動攔截方法呼叫的資料，並透過我們的特定方法進行資料上報。資料上報功能可以讓您輕鬆地將腳本攔截到的資料直接上傳到 Redis 或外部 HTTP 介面，您可以透過 Redis 或 HTTP 介面接收到上報的資料內容。同時為了最大化網路效能，支援資料壓縮後上報 (zlib)。

```{attention}
自 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 腳本進行二次封裝。
```

## 編寫上報腳本

第一步，您需要改寫您的 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)`。其中 `name` 代表資料的類型，如果您設定的上報位址為 Redis，那麼此名稱代表的是 Redis 佇列名稱。您需要盡量使用準確的英文來描述，例如 `product_info` 等。`content` 則代表該資料的內容，類型僅支援字串 (string) 和位元組陣列 (bytes)。在上述範例中，我們將其轉換為 JSON 字串提交。

現在，您已經了解了編寫腳本所需的格式和呼叫要求，也就是如何將 Hook 到的資料提交到外部。您還需要繼續閱讀下文，以了解如何設定**資料上報的目的地**。

## 資料上報目的地

資料上報目的地，代表您在腳本中 `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 開始，每次上報都會遞增。您可以透過此欄位對資料進行排序，或檢查是否存在上報遺失的情況。


### 上報連結的額外參數

在您組合上報連結時，支援您在連結中指定一些動態的 ID 參數。您可以透過變數預留位置 `${name}` 的格式將其安插在連結的特定位置。透過諸如 `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)。
```

```
範例上報連結 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、支援使用者名稱密碼、支援單向憑證驗證 (伺服器憑證校驗)，不支援雙向校驗。

```
範例上報連結 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
```

在上述連結中，相關資料將會被傳送到 `script/${device_id}/report`，您可以透過如 `mosquitto_sub -L mqtt://test.mosquitto.org:1883/script/+/report` 來訂閱這些訊息。

### 上報到 Redis

Redis 上報相對簡單，因其並未攜帶任何元資料，所以無法直接區分資料來源。您可能需要透過動態改寫注入腳本的方式來實現此行為。對於 Redis 上報，FIRERPA 會直接將資料主體透過 `LPUSH` 的方式推入佇列。例如，在上文的範例腳本中，上報資料將會被推入 `report_data` 佇列中。

```{attention}
僅支援單例模式 (standalone) 的 Redis 服務，不支援 Redis 叢集。
```

```{attention}
除了密碼欄位，請勿在 Redis 連結中加入變數預留位置。此連結為標準 Redis 函式庫支援的連結，修改其他部分可能導致無法正確解析。
```

```
範例上報連結 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")
```

您也可以這樣設定，如此一來您的資料將被提交到 HTTP 介面而不是 Redis (支援 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()
```