使用 Frida 上报数据

使用 Frida 上报数据的功能基于持久化功能,您可以通过您编写的 Frida 脚本自动截取方法调用的数据并通过我们的特定方法进行数据上报,数据上报功能可以使您轻松的将脚本截获到的数据直接上传到 redis 或者外部 http 接口,您可以通过 redis 或者 http 接口接收到上报的数据内容。同时为了最大化网络效能,支持数据压缩后上报(zlib)。

编写上报脚本

第一步,您需要改写您的 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 或者 RabbitMQ 队列,因为这两种协议特性,我们可以在协议中携带更多的元数据,这样您可以进行更加精确的设备匹配。

上报的元数据

对于上报到 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 目的地的数据不包含任何元数据。

device 为设备的唯一 ID,您可以在远程桌面的信息栏中找到他,通常这个 ID 是唯一且固定的。您可以通过它来标记设备从而产生对应关系。encode 为数据编码,编码支持 none 以及 zlib,如编码为 zlib,则您需要使用 zlib 来解压数据体。name 用来标记此上报数据类型,它也是您使用 emit 方法时的第一个参数,sequence 代表上报数据的索引,从 0 开始,每次上报都会递增,您可以通过此字段对数据进行排序或者检查是否存在上报丢失的情况。

上报到 HTTP

您需要自行编写一个接收上报数据的 HTTP 服务,HTTP 上报接口需实现 POST 方法。FIRERPA 将通过 POST 向接口上报数据,同时将会把元数据中的各个字段编码为 HTTP 查询参数,您可以从中提取并处理。对于数据体,则是携带在 POST 请求的 body 中。FIRERPA 会在收到 502、503、504 状态码时自动进行 3 次重试,如您的后端正确接收并处理了数据上报的数据,应返回纯文本的 OK 或者 SUCCESS 并设置状态码为 200 以示成功处理。

注意:HTTP 请求是多线程的,后端接收到的消息可能并不遵循上报顺序(sequence)。

上报到 RabbitMQ

上报到 RabbitMQ 的队列,FIRERPA 不会为您自动创建队列,您需要自行创建好队列。在如上的示例脚本中,您需要提前创建好 report_data 队列。对于上报的元数据,您应从 Header 中提取。

上报到 Redis

Redis 上报相对简单,因其并没有携带任何元数据,所以无法直接区分数据来源,您可能需要通过动态改写注入脚本的方式来实现这一行为。对应 Redis 上报,FIRERPA 将会直接将数据体通过 LPUSH 的方式压入队列,例如在上文的示例脚本中,上报数据将会被压入 report_data 队列中。

注意:只支持单例模式(standalone)的 Redis 服务,不支持 Redis 集群。

注入上报脚本

当然,获取 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()