Exportar Interfaces con Frida

Puede utilizar esta función para llamar a los métodos exportados por Frida RPC como extensiones de la interfaz de FIRERPA. Puede escribir su propio código de hook funcional para lograr el máximo control sobre la APP. Esta función requiere que esté familiarizado con la escritura de scripts de hook de Frida. Si ya tiene un script existente, necesitará modificarlo ligeramente para utilizar nuestros métodos específicos.

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 usted mismo; de lo contrario, se producirán errores relacionados con `Java not defined`. Este es un cambio oficial de Frida. Según las notas de cambio oficiales, 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 nuestra herramienta `tools/frida_script_generate.py` para reempaquetar su script JS original.

Escribir el Script de Exportación

Necesita escribir un script en un formato específico. Los nombres de las funciones exportadas deben seguir la convención de nomenclatura camelCase, comenzando con una letra minúscula. Para nombres definitorios como HTTP, el nombre del método no debe usar mayúsculas completas. Por ejemplo, el nombre sendHTTPRequest debe escribirse como sendHttpRequest en el script de exportación, en lugar de usar HTTP todo en mayúsculas. A continuación se muestra la estructura general que debe seguir un script.

Java.perform(function() {
const String = Java.use("java.lang.String")
rpc.exports.exampleFunc1 =  function (a, b) {
        return performRpcJVMCall(function() {
                // Ejecutar en el hilo de la JVM
                return String.$new("Execute on JVM thread:" + a + b).toString()
        })
}
rpc.exports.exampleFunc2 = function (a, b) {
        return performRpcJVMCallOnMain(function() {
                // Ejecutar en el hilo principal de la UI
                return String.$new("Execute on Main UI thread:" + a + b).toString()
        })
}
rpc.exports.exampleFunc3 = function (a, b) {
        return performRpcCall(function() {
                // Ejecutar código JS normal
                return a + b
        })
}})

En el script de ejemplo anterior, puede ver tres tipos de definiciones de métodos. El bloque de código con el patrón return performRpc es obligatorio, ya que garantiza que su valor se devuelva correctamente. Los significados de estos tres diferentes bloques de llamada performRpc son los siguientes:

return performRpcJVMCall(function() {
        // Ejecutar en el hilo de la JVM
})

El bloque de código performRpcJVMCall significa que su código se ejecuta en la JVM. Dentro de este bloque, puede usar funcionalidades relacionadas con la JVM, como Java.use u otras operaciones que involucren la capa Java de la aplicación.

return performRpcJVMCallOnMain(function() {
        // Ejecutar en el hilo de la UI
})

El bloque de código performRpcJVMCallOnMain significa que su código se ejecuta en la JVM y en el hilo principal de la UI. Para operaciones que involucran la UI o el hilo principal, debe ejecutarlas dentro de este bloque para que tengan éxito. Al mismo tiempo, debe tener cuidado de que su código no bloquee la ejecución del hilo principal, ya que podría causar que la aplicación no responda o incluso se cierre.

return performRpcCall(function() {
        // Ejecutar código JS normal
})

El bloque de código performRpcCall solo le permite ejecutar código JS básico. No puede usar lógica de la capa Java aquí.

Atención

Es posible que aún necesite usar `Java.perform` para envolver todo el bloque de código y asegurar que la lógica de Java se ejecute correctamente.

Ahora que comprende la lógica básica de escritura de nuestro código, puede adaptar su propio código a este formato. Si solo quiere probarlo, puede copiar directamente este fragmento de código para comenzar. En las siguientes secciones, también usaremos este código de hook para las explicaciones.

Inyectar el Script de Exportación

El método para inyectar el script de exportación aquí es exactamente el mismo que el descrito en el capítulo Script de Frida Persistente.

app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)

Después de llamar a la interfaz de inyección, su script debería estar inyectado. Puede ejecutar la siguiente llamada para verificar si el script funciona correctamente.

app.is_script_alive()

Llamar a los Métodos Exportados

La forma de llamar a los métodos exportados dentro del script es muy similar a usar las interfaces nativas de FIRERPA. Vea el código de ejemplo.

app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")

Para aplicaciones clonadas, necesita obtener la instancia de la aplicación clonada.

app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")

El método de llamada anterior es equivalente al siguiente. Puede notar que Func1 en el método se convierte en _func1. Ambas formas son válidas.

app.example_func1("FIRE", "RPA")
>>> app.example_func1("FIRE", "RPA")
'Hello World:FIRERPA'
>>> app.example_func2("FRI", "DA")
'Hello World:FRIDA'
>>> app.example_func3("FIRE", "RPA")
'FIRERPA'

Llamar a los Métodos Exportados (Interfaz HTTP)

Además de admitir llamadas desde el cliente, también admitimos llamadas a través de HTTP. Puede usarlo de la siguiente manera.

Consejo

A partir de la versión 8.15, los métodos exportados también admiten llamadas utilizando el protocolo JSON-RPC 2.0 (el modo MultiCall aún no es compatible).

Puede usar jsonrpclib directamente para realizar la llamada como se muestra a continuación. O, si está familiarizado con el protocolo JSON-RPC 2.0, también puede escribir su propio código para realizar la solicitud. Esta es la implementación de protocolo más estandarizada y con la documentación más completa.

import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")

Por supuesto, todavía puede continuar usando el siguiente protocolo de llamada anterior, que es relativamente más simple pero menos estandarizado.

import json
import requests
url = "http://192.168.0.2:65000/script/com.android.settings/exampleFunc1?user=0"
res = requests.post(url, data={"args": json.dumps(["FIRE", "RPA"])})
print (res.status_code, res.json()["result"])

El parámetro de consulta user en el enlace es el UID de la aplicación. El valor predeterminado es 0 y no necesita ser especificado. Si es una aplicación clonada, debe especificar el UID aquí.

El código anterior llama a la interfaz de exportación del script a través de HTTP. com.android.settings es el nombre del paquete de la aplicación, y exampleFunc1 es el nombre de la función exportada. El parámetro de la solicitud args debe ser serializado usando json.dumps. La lista de parámetros también puede contener múltiples argumentos, dependiendo del número de parámetros de su método. Proporcionar una lista vacía [] indica que la función exportada no tiene parámetros.

Si su FIRERPA tiene habilitado un certificado de interfaz, necesita acceder usando HTTPS y proporcionar la contraseña del certificado.

headers = {"X-Token": "contraseña_del_certificado"}
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])}, headers=headers, verify=False)
print (res.status_code, res.json()["result"])

Códigos de Estado HTTP

Las llamadas a través de la interfaz HTTP tienen códigos de estado específicos que puede necesitar para tomar decisiones. Los estados y sus significados son los siguientes:

Código de estadoDescripción del estado
200Todo normal
410Script no inyectado o no instalado
500Excepción en el script o en los parámetros
400Error en los parámetros

Solución de Problemas

Si encuentra problemas de bloqueo o tiempo de espera al llamar a la interfaz a través de la API o la interfaz HTTP, es muy probable que su aplicación esté en segundo plano y haya sido suspendida por el sistema. Por lo tanto, es posible que necesite mantener la APP ejecutándose en primer plano en todo momento.