Frida 上报数据¶
使用 Frida 上报数据的功能基于持久化功能,您可以通过您编写的 Frida 脚本自动截取方法调用的数据并通过我们的特定方法进行数据上报,数据上报功能可以使您轻松地将脚本截获到的数据直接上传到 Redis、MQTT或者外部 HTTP 接口,您可以通过 Redis、MQTT 或者 HTTP 接口接收到上报的数据内容。同时为了最大化网络效能,支持数据压缩后上报(zlib)。
注意
自 9.0 版本开始,内置的 Frida 17.x 需要您自行将 frida-java-bridge 打包进脚本,否则会出现 Java not defined 相关错误,这项改动属于 Frida 官方的变更。依据官方的改动说明,您需要新建 Node.js 项目并引入 frida-java-bridge,详细请参考:https://github.com/oleavr/frida-agent-example ,或者使用我们提供的 frida_script_generate.py 对 js 脚本进行打包。
编写上报脚本¶
通常 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(name, content)。name 代表数据的类型,如您设置的上报地址为 Redis,那么该名称代表的是 Redis 队列名。您需要尽量准确地用英文描述,比如 product_info 等。content 代表数据内容,类型仅支持字符串和字节数组。示例中我们将其转换成了字符串提交。
现在,您已经了解了脚本编写需要的格式和调用要求,也就是如何将 Hook 到的数据提交到外部,您还需要继续阅读下文来了解如何配置 数据上报的目的地。
数据上报目的地¶
数据上报目的地,代表您在脚本中 emit 的数据应该往何处发送,目的地支持的类型有 HTTP 接口、Redis 队列和 MQTT,它们之间有一些不同点。
通常情况下,如果您无需关心数据来源等信息,即无需将数据与来源机器进行匹配,我们建议使用 Redis 队列。反之,您则应使用 HTTP 或者 MQTT(v5),因为这两种协议的特性,我们可以在协议中携带更多的元数据,这样您可以进行更加精确的设备匹配。
上报的元数据¶
对于上报到 HTTP 接口以及 MQTT 目的地的数据,除了包含原本的上报数据之外,您还可以通过协议层获取关于设备及脚本的元数据,协议中附带的元数据如下表所示。
注意
由于 Redis 协议特性,上报到 Redis 目的地的数据不包含任何上述元数据。
device 为设备的唯一 ID,您可以在远程桌面的信息栏中找到它,通常这个 ID 是唯一且固定的。您可以通过它来标记设备从而建立对应关系。encode 为数据编码,编码支持 none 以及 zlib,如编码为 zlib,则您需要使用 zlib 来解压数据体。name 用来标记此上报数据类型,它也是您使用 emit 方法时的第一个参数,sequence 代表上报数据的索引,从 0 开始,每次上报都会递增,您可以通过此字段对数据进行排序或者检查是否存在上报丢失的情况。
上报链接的额外参数¶
支持您在上报链接指定一些动态的 id 参数在链接中,通过变量占位符 ${name} 的格式将其插入在链接的特定部位。例如 http://192.168.1.2/report/${device_id}。支持的变量如下表所示。
上报到 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 协议认证
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
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
redis://:password@1.2.3.4/0
TLS + 密码认证的 Redis
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()