使用 Frida 上报数据¶
使用 Frida 上报数据的功能基于持久化功能,您可以通过您编写的 Frida 脚本自动截取方法调用的数据并通过我们的特定方法进行数据上报,数据上报功能可以使您轻松的将脚本截获到的数据直接上传到 redis 或者外部 http 接口,您可以通过 redis 或者 http 接口接收到上报的数据内容。同时为了最大化网络效能,支持数据压缩后上报(zlib)。
注意
自 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 的自带方法,您可通过他方便且系统化的将数据提交到外部。
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) |
注意
由于 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 | 安卓 ID |
| serialno | ro.serialno |
上报到 HTTP¶
您需要自行编写一个接收上报数据的 HTTP 服务,HTTP 上报接口需实现 POST 方法。FIRERPA 将通过 POST 向接口上报数据,同时将会把元数据中的各个字段编码为 HTTP 查询参数,您可以从中提取并处理。对于数据体,则是携带在 POST 请求的 body 中。FIRERPA 会在收到 502、503、504 状态码时自动进行 3 次重试,如您的后端正确接收并处理了数据上报的数据,应返回纯文本的 OK 或者 SUCCESS 并设置状态码为 200 以示成功处理。
注意
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 队列中。
注意
只支持单例模式(standalone)的 Redis 服务,不支持 Redis 集群。
注意
除了密码字段,请勿在 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 变量,他代表了您需要注入的那个应用的实例,随后您需要通过他来继续下面的注入或脱离操作。
app = d.application("com.android.settings")
通过如下的接口将上述上报脚本注入到应用。当截获数据时,数据将会被提交到 Redis 的 report_data 队列,在示例里,您的设备需要能直接访问到 192.168.1.10 上的相关服务否则将无法上报数据。
app.attach_script(script, emit="redis://192.168.1.10/0")
您也可以这样设置,这样您的数据将被提交到 http 接口而不是 Redis(支持 https)。
app.attach_script(script, emit="http://192.168.1.10/dataReport")
当您上报的数据很大的时候,启用压缩可以大大提高网络传输的吞吐性能,您可以使用 encode 来启用上报数据压缩功能。当然,您随后也需要对上报数据进行解压操作。
app.attach_script(..., encode=DataEncode.DATA_ENCODE_ZLIB)
上报数据解压¶
默认情况下,上报数据无任何压缩,如您设置了上报压缩,您还需在接收端使用 zlib 编码来对上报数据进行解压缩,您可以 Python 官方库 zlib 的 decompress 方法来方便的对上报数据进行解压。
zlib.decompress(data)
移除上报脚本¶
移除上报脚本的过程很简单,和持久化脚本里的用法是一样的。
app.detach_script()