Reportar datos con Frida

La función de reporte de datos con Frida se basa en la funcionalidad de persistencia. Puede usar sus scripts de Frida para interceptar automáticamente los datos de las llamadas a métodos y reportarlos a través de nuestro método específico. La función de reporte de datos le permite cargar fácilmente los datos interceptados por el script directamente a Redis o a una interfaz HTTP externa. Puede recibir el contenido de los datos reportados a través de Redis o de la interfaz HTTP. Al mismo tiempo, para maximizar la eficiencia de la red, se admite el reporte de datos después de la compresión (zlib).

Atención

A partir de la versión 9.0, la versión de Frida 17.x que utilizamos internamente requerirá que empaquete `frida-java-bridge` en su script; de lo contrario, aparecerán errores relacionados con `Java not defined`. Este cambio es una modificación oficial de Frida. Según las notas de la modificación oficial, necesitará crear un nuevo proyecto de Node.js e importar el java bridge. Para más detalles, consulte https://github.com/oleavr/frida-agent-example o utilice nuestro script `tools/frida_script_generate.py` para reempaquetar su script JS original.

Escribir el script de reporte

Primero, necesita modificar su script de Frida. Normalmente, los scripts de Frida tienen funciones como send y log que le permiten enviar datos al exterior. Para FIRERPA, necesita usar un método específico para enviar los datos. A continuación, se muestra un código de plantilla que hemos creado para interceptar el tráfico de okhttp. Es solo un script de demostración y es posible que no pueda usarlo directamente. Este script no es muy diferente de un script convencional; la única diferencia es el uso de un método emit, que es un método incorporado en FIRERPA. A través de él, puede enviar datos al exterior de manera fácil y sistemática.

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
        }
})

El método emit tiene dos parámetros: emit(name, content). name representa el tipo de datos. Si la dirección de reporte que ha configurado es Redis, este nombre representa el nombre de la cola de Redis. Debe describirlo en inglés con la mayor precisión posible, como product_info. content representa el contenido de los datos, y los tipos admitidos son solo cadena de texto (string) y arreglo de bytes (bytes). En el ejemplo anterior, lo convertimos en una cadena JSON antes de enviarlo.

Ahora que comprende el formato y los requisitos de llamada para escribir el script, es decir, cómo enviar los datos interceptados (hooked) al exterior, necesita continuar leyendo a continuación para aprender a configurar el destino del reporte de datos.

Destino del reporte de datos

El destino del reporte de datos representa a dónde deben enviarse los datos que emite en su script. Los tipos de destino admitidos son interfaces HTTP, colas de Redis y colas de RabbitMQ, con algunas pequeñas diferencias entre ellos.

Normalmente, si no necesita preocuparse por la información del origen de los datos, es decir, no necesita hacer coincidir los datos con la máquina de origen, le recomendamos que utilice una cola de Redis. Por el contrario, debería usar HTTP o MQTT (v5), ya que debido a las características de estos dos protocolos, podemos llevar más metadatos en el protocolo, lo que le permite realizar una coincidencia de dispositivos más precisa.

Metadatos del reporte

Para los datos reportados a una interfaz HTTP y a un destino RabbitMQ, además de los datos del reporte original, también puede obtener metadatos sobre el dispositivo y el script a través de la capa de protocolo. Los metadatos incluidos en el protocolo se muestran en la siguiente tabla.

CampoDescripción
applicationNombre del paquete de la aplicación (ej. com.android.settings)
deviceID del dispositivo (ej. 67b2a3d7-5004-ea2a-0d44-194de6ede8de)
encodeCodificación de datos (ej. none)
nameNombre de los datos (ej. report_data)
scriptID del script (ej. 7c52530d)
sequenceSecuencia del reporte (ej. 30)
timestampHora del reporte (ej. 1740023596914)
userID de la aplicación multi-instancia (ej. 0)

Atención

Debido a las características del protocolo Redis, los datos reportados a un destino Redis no contienen ninguno de los metadatos mencionados anteriormente.

device es el ID único del dispositivo, que puede encontrar en la barra de información del escritorio remoto. Normalmente, este ID es único y fijo. Puede usarlo para marcar dispositivos y establecer una correspondencia. encode es la codificación de los datos, que admite none y zlib. Si la codificación es zlib, necesitará usar zlib para descomprimir el cuerpo de los datos. name se utiliza para marcar el tipo de estos datos reportados; también es el primer parámetro que utiliza en el método emit. sequence representa el índice de los datos reportados, comenzando desde 0 y aumentando con cada reporte. Puede usar este campo para ordenar los datos o para verificar si se ha perdido algún reporte.

Parámetros adicionales en la URL de reporte

Al construir la URL de reporte, puede especificar algunos parámetros de ID dinámicos en la URL. Puede insertarlos en partes específicas de la URL utilizando el formato de placeholder de variable ${name}. Se puede usar en formas como http://192.168.1.2/report/${device_id}. Las variables admitidas son las siguientes.

NombreDescripción del nombre
device_idID único del dispositivo
device_id_shortID único del dispositivo (ID corto codificado en BASE62)
android_idAndroid ID
serialnoro.serialno

Reportar a HTTP

Necesitará escribir su propio servicio HTTP para recibir los datos reportados. La interfaz de reporte HTTP debe implementar el método POST. FIRERPA reportará los datos a la interfaz a través de POST y, al mismo tiempo, codificará cada campo de los metadatos como parámetros de consulta HTTP, de los cuales puede extraerlos y procesarlos. El cuerpo de los datos se transporta en el body de la solicitud POST. FIRERPA reintentará automáticamente 3 veces al recibir los códigos de estado 502, 503, 504. Si su backend recibe y procesa correctamente los datos del reporte, debe devolver el texto plano OK o SUCCESS y establecer el código de estado en 200 para indicar un procesamiento exitoso.

Atención

Las solicitudes HTTP son multihilo, por lo que los mensajes recibidos por el backend pueden no seguir el orden del reporte (sequence).
Ejemplo de URL de reporte: http://192.168.1.2/report/${device_id}?serialno=${serialno}
Si se requiere autenticación con contraseña: http://user:password@192.168.1.2/report/${device_id}?serialno=${serialno}

Reportar a MQTT

Al reportar a MQTT, debe extraer los metadatos del reporte de UserProperty. Es compatible con TLS, nombre de usuario y contraseña, y validación de certificado unidireccional (verificación del certificado del servidor), pero no admite la verificación bidireccional.

Ejemplo de URL de reporte: mqtt://test.mosquitto.org:1883/script/${device_id}/report
Si se requiere autenticación con contraseña: mqtt://rw:readwrite@test.mosquitto.org:1884/script/${device_id}/report

Si se requiere validación unidireccional: mqtts://test.mosquitto.org:8883/script/${device_id}/report?verify=true&ca=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVBekNDQXV1Z0F3SUJBZ0lVQlkxaGxDR3ZkajROaEJYa1ovdUxVWk5JTEF3d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2daQXhDekFKQmdOVkJBWVRBa2RDTVJjd0ZRWURWUVFJREE1VmJtbDBaV1FnUzJsdVoyUnZiVEVPTUF3RwpBMVVFQnd3RlJHVnlZbmt4RWpBUUJnTlZCQW9NQ1UxdmMzRjFhWFIwYnpFTE1Ba0dBMVVFQ3d3Q1EwRXhGakFVCkJnTlZCQU1NRFcxdmMzRjFhWFIwYnk1dmNtY3hIekFkQmdrcWhraUc5dzBCQ1FFV0VISnZaMlZ5UUdGMFkyaHYKYnk1dmNtY3dIaGNOTWpBd05qQTVNVEV3TmpNNVdoY05NekF3TmpBM01URXdOak01V2pDQmtERUxNQWtHQTFVRQpCaE1DUjBJeEZ6QVZCZ05WQkFnTURsVnVhWFJsWkNCTGFXNW5aRzl0TVE0d0RBWURWUVFIREFWRVpYSmllVEVTCk1CQUdBMVVFQ2d3SlRXOXpjWFZwZEhSdk1Rc3dDUVlEVlFRTERBSkRRVEVXTUJRR0ExVUVBd3dOYlc5emNYVnAKZEhSdkxtOXlaekVmTUIwR0NTcUdTSWIzRFFFSkFSWVFjbTluWlhKQVlYUmphRzl2TG05eVp6Q0NBU0l3RFFZSgpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNRTBIS21JemZUT3drS0xUM1RISGUrT2JkaXphbVBnClVabUQ2NFRmM3pKZE5lWUdZbjRDRVhieVA2ZnkzdFdjOFMyYm9XNmR6ckg4U2RGZjl1bzMyMEdKQTlCN1UxRlcKVGUzeGRhL0xtM0pGZmFIamtXdzdqQndjYXVRWmpwR0lOSGFwSFJscGlDWnNxdUF0aE9neFc5U2dEZ1lsR3pFQQpzMDZwa0VGaU13K3FEZkxvL3N4RktCNnZRbEZla01lQ3ltakxDYk53UEp5cXloRm1QV3dpby9QRE1ydUJUelBICjNjaW9CbnJKV0tYYzNPalhkTEdGSk9majdwUDBqL2RyMkxINzJlU3Z2M1BRUUZsOTBDWlBGaHJDVWNSSFNTeG8KRTZ5akdPZG56N2Y2UHZlTElCNTc0a1FPUnd0OGVQbjB5aWRyVEMxaWN0aWtFRDNuSFloTVVPVUNBd0VBQWFOVApNRkV3SFFZRFZSME9CQllFRlBWVjZ4QlVGUGlHS0R5bzVWMytIYmg0TjlZU01COEdBMVVkSXdRWU1CYUFGUFZWCjZ4QlVGUGlHS0R5bzVWMytIYmg0TjlZU01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUwKQlFBRGdnRUJBR2E5a1MyMU43MFRoTTYvSGo5RDdtYlZ4S0xCalZXZTJUUHNHZmJsM3JFRGZaK09LUloyajZBQwo2cjdqYjRUWk8zZHpGMnA2ZGdicmxVNzFZLzRLMFRkeklqUmozY1EzS1NtNDFKdlVRMGhaL2MwNGlHRGcveFdmCitwcDU4bmZQQVl3dWVycnVQTldtbFN0V0FYZjBVVHFSdGc0aFFEV0J1VUZESlR1V3V1QnZFWHVkejc0ZWgvd0sKc013ZnUxSEZ2ank1WjBpTURVOFBVRGVwalZvbE9DdWU5YXNobFM0RUI1SUVDZFNSMlRJdG5BSWlJd2lteDgzOQpMZFVkUnVkYWZNdTVUNVhtYTE4Mk9DMC91L3hSbEVtK3R2S0dHbWZGY04wcGlxVmw4T3JTUEJnSWxiKzFJS0pFCm0vWHJpV3IvQ3E0aC9KZkI3TlRzZXpWc2xna0Jhb1U9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K

En la URL anterior, los datos relevantes se enviarán a script/${device_id}/report. Puede suscribirse a estos mensajes con un comando como mosquitto_sub -L mqtt://test.mosquitto.org:1883/script/+/report.

Reportar a Redis

El reporte a Redis es relativamente simple. Como no lleva ningún metadato, no se puede distinguir directamente el origen de los datos. Es posible que necesite lograr este comportamiento modificando dinámicamente el script inyectado. Para el reporte a Redis, FIRERPA insertará directamente el cuerpo de los datos en la cola mediante LPUSH. Por ejemplo, en el script de ejemplo anterior, los datos del reporte se insertarán en la cola report_data.

Atención

Solo se admiten servicios de Redis en modo de instancia única (standalone), no clústeres de Redis.

Atención

Excepto por el campo de la contraseña, no agregue placeholders de variables en la URL de Redis. La URL es la estándar admitida por la biblioteca de Redis; modificar otras partes puede causar errores de análisis.
Ejemplo de URL de reporte: redis://1.2.3.4/0
Si se requiere autenticación con contraseña: redis://:password@1.2.3.4/0

TLS + contraseña

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

Inyectar el script de reporte

Por supuesto, obtener la instancia de la aplicación es el primer paso. Puede usar la siguiente llamada para obtener una variable app, que representa la instancia de la aplicación en la que necesita inyectar. Luego, la usará para continuar con las siguientes operaciones de inyección o desvinculación.

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

Use la siguiente interfaz para inyectar el script de reporte mencionado anteriormente en la aplicación. Cuando se intercepten datos, se enviarán a la cola report_data de Redis. En el ejemplo, su dispositivo necesita poder acceder directamente al servicio correspondiente en 192.168.1.10; de lo contrario, no se podrán reportar los datos.

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

También puede configurarlo de esta manera, para que sus datos se envíen a una interfaz HTTP en lugar de a Redis (compatible con https).

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

Cuando los datos que reporta son muy grandes, habilitar la compresión puede mejorar significativamente el rendimiento de la transmisión de red. Puede usar encode para habilitar la función de compresión de datos de reporte. Por supuesto, luego también necesitará descomprimir los datos reportados.

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

Descompresión de los datos reportados

Por defecto, los datos reportados no tienen ninguna compresión. Si ha configurado la compresión de reporte, también necesitará usar la codificación zlib en el extremo receptor para descomprimir los datos reportados. Puede usar el método decompress de la biblioteca estándar de Python zlib para descomprimir fácilmente los datos reportados.

zlib.decompress(data)

Eliminar el script de reporte

El proceso para eliminar el script de reporte es muy simple, es el mismo uso que en los scripts persistentes.

app.detach_script()