Exporter des interfaces avec Frida¶
Cette fonctionnalité vous permet d'appeler les méthodes exportées par Frida RPC comme des extensions d'interface FIRERPA. Vous pouvez atteindre un contrôle ultime de l'application en écrivant votre propre code de hook fonctionnel. Cette fonctionnalité requiert une familiarité avec l'écriture de scripts de hook Frida. Si vous avez déjà des scripts existants, vous devrez les modifier légèrement pour utiliser notre méthode spécifique.
Attention
Écrire le script d'exportation¶
Vous devez écrire un script dans un format spécifique. Les noms des fonctions exportées doivent suivre les conventions de nommage, en utilisant impérativement le camelCase avec une première lettre en minuscule. Pour les noms définitionnels comme HTTP, le nom de la méthode ne doit pas utiliser un format tout en majuscules. Par exemple, un nom comme sendHTTPRequest doit être écrit sendHttpRequest dans le script d'exportation, et non avec HTTP en majuscules. Voici la structure générale qu'un script doit suivre.
Java.perform(function() {
const String = Java.use("java.lang.String")
rpc.exports.exampleFunc1 = function (a, b) {
return performRpcJVMCall(function() {
return String.$new("Exécuter sur le thread JVM :" + a + b).toString()
})
}
rpc.exports.exampleFunc2 = function (a, b) {
return performRpcJVMCallOnMain(function() {
return String.$new("Exécuter sur le thread UI principal :" + a + b).toString()
})
}
rpc.exports.exampleFunc3 = function (a, b) {
return performRpcCall(function() {
return a + b
})
}})
Dans le script d'exemple ci-dessus, vous pouvez voir trois types de définitions de méthodes. Le bloc de code utilisant le modèle return performRpc est obligatoire, car il garantit que votre valeur est retournée correctement. La signification de ces trois différents blocs d'appel performRpc est la suivante :
return performRpcJVMCall(function() {
// Exécuter sur le thread JVM
})
Le bloc de code performRpcJVMCall signifie que votre code est exécuté dans la JVM. Vous pouvez utiliser des fonctionnalités liées à la JVM dans ce bloc, comme Java.use ou d'autres opérations impliquant la couche Java de l'application.
return performRpcJVMCallOnMain(function() {
// Exécuter sur le thread UI
})
Le bloc de code performRpcJVMCallOnMain signifie que votre code est exécuté dans la JVM et sur le thread UI principal. Pour les opérations impliquant l'interface utilisateur (UI) ou le thread principal, vous devez les exécuter dans ce bloc pour qu'elles réussissent. Faites également attention à ce que votre code ne bloque pas l'exécution du thread principal, car cela pourrait rendre l'application non réactive ou même la faire planter.
return performRpcCall(function() {
// Exécuter du code JS normal
})
Le bloc de code performRpcCall vous permet uniquement d'exécuter du code JavaScript de base. Vous ne pouvez pas utiliser de logique de la couche Java ici.
Attention
Maintenant que vous comprenez la logique de base de l'écriture de notre code, vous pouvez encapsuler votre propre code dans ce format pour l'adapter. Si vous souhaitez simplement tester, vous pouvez copier directement ce morceau de code pour commencer. Nous utiliserons également ce code de hook pour les explications suivantes.
Injecter le script d'exportation¶
La méthode d'injection du script d'exportation ici est en fait exactement la même que celle décrite dans le chapitre Script Frida persistant.
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
Après avoir appelé l'interface d'injection, votre script devrait être injecté. Vous pouvez exécuter l'appel suivant pour vérifier si le script fonctionne correctement.
app.is_script_alive()
Appeler les méthodes exportées¶
La manière d'appeler les méthodes exportées dans le script est très similaire à l'utilisation native des interfaces FIRERPA. Veuillez consulter l'exemple de code.
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
Pour les applications multi-instances, vous devez obtenir l'instance de l'application concernée.
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
Les appels de méthode ci-dessus sont équivalents aux suivants. Vous remarquerez que Func1 dans le nom de la méthode est devenu _func1. Les deux syntaxes sont valides.
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'
Appeler les méthodes exportées (Interface HTTP)¶
En plus de prendre en charge les appels depuis le client, nous supportons également les appels via HTTP. Vous pouvez les utiliser de la manière suivante.
Conseil
Vous pouvez utiliser directement jsonrpclib pour effectuer des appels, comme indiqué ci-dessous. Ou, si vous êtes familier avec le protocole JsonRPC 2.0, vous pouvez également écrire votre propre code pour effectuer les requêtes. C'est l'implémentation de protocole la plus standardisée et la mieux documentée.
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
Bien sûr, vous pouvez toujours continuer à utiliser le protocole d'appel précédent ci-dessous, qui est relativement plus simple mais moins standardisé.
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"])
Le paramètre de requête user dans l'URL est l'UID de l'application. Par défaut, il est à 0 et n'a pas besoin d'être spécifié. S'il s'agit d'une application multi-instance, l'UID doit être spécifié ici.
Le code ci-dessus appelle l'interface exportée du script via HTTP. com.android.settings est le nom du package de l'application, et exampleFunc1 est le nom de la fonction exportée. Les paramètres de la requête, args, doivent être sérialisés à l'aide de json.dumps. La liste des arguments peut contenir plusieurs paramètres, en fonction du nombre de paramètres de votre méthode. Fournir une liste vide [] signifie que la fonction exportée n'a pas de paramètres.
Si vous avez activé les certificats d'interface pour FIRERPA, vous devez utiliser HTTPS pour l'accès et fournir le mot de passe du certificat.
headers = {"X-Token": "mot de passe du certificat"}
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])}, headers=headers, verify=False)
print (res.status_code, res.json()["result"])
Codes de statut HTTP¶
Les appels via l'interface HTTP ont des codes de statut spécifiques, que vous pourriez avoir besoin d'utiliser pour vos vérifications. Les statuts et leurs significations sont les suivants :
Dépannage¶
Si vous rencontrez des problèmes de blocage ou de timeout lors de l'appel de l'interface via l'API ou HTTP, il est très probable que votre application ait été mise en veille forcée par le système en arrière-plan. Vous devrez donc peut-être vous assurer que l'application reste toujours au premier plan.