🪝Outil Frida

Description

Frida est une boîte à outils d’instrumentation de code dynamique. Il vous permet d'injecter des extraits de JavaScript ou de votre propre bibliothèque dans des applications natives sur Windows, macOS, GNU/Linux, iOS, watchOS, tvOS, Android, FreeBSD et QNX. Frida vous fournit également des outils simples construits sur l'API Frida. Ceux-ci peuvent être utilisés tels quels, adaptés à vos besoins ou servir d'exemples d'utilisation de l'API.

Installation

$ pip install frida-tools

ressource: https://github.com/frida/frida

Installation sur Android

  • Télécharger le binaire frida-server approprié à l'appareil utilisé via la page de releases suivante:

Page de releases
  • Importer le binaire sur l'appareil via la commande $ adb push frida-server /data/local/tmp/

  • Donner les permissions au binaire via la commande $ adb shell "chmod 755 /data/local/tmp/frida-server"

  • Puis lancer le binaire en arrière-plan via la commande $ adb shell "/data/local/tmp/frida-server &"

Note: Pour fonctionner, frida-server a besoin de permissions élevées sur l'appareil. Il est donc nécessaire de rooter celui-ci pour l'utiliser.

Hooking (bases)

Dans un premier temps lorsqu'on utilise l'API Javascript Frida, on doit se connecter à JVM (Java Virtual Machine).

Pour cela on utilise Java.perform:

Java.perform(() => {
    console.log("Hello World!");
});

Ensuite, il nous faut importer la classe que l'on souhaite tester avec Java.use:

var class1 = Java.use("com.example.xyz.Activity");

Enfin, il reste à hook la méthode à tester comme ceci:

[Classe].[Method].implementation = function([args]){
//Code
};

[Classe] est la variable définie prédèdemment, [Method] est la fonction du code hookée et [args] représente le ou les arguments utilisés par la fonction.

Fonctions Android

Le hooking permet ainsi d'intéragir avec des fonction android dynamiquement.

Exemple:

Java.perform(function() {
    var TelephonyManager = Java.use('android.telephony.TelephonyManager');
    
    TelephonyManager.getDeviceId.implementation = function() {
        var deviceId = this.getDeviceId();
        console.log('[+] Device Id:", deviceId);
        return deviceId;
    };
});

Permet d'intercepter la méthode getDeviceId et log l'ID de l'appareil via la commande console.log()

Appeler la méthode d'une classe dynamique

Pour les méthodes non statiques, il est d'abord nécessaire de créer une instance de la classe avec la méthode "$new()".

Java.perform(function() {
    var class_reference = Java.use("<package_name>.<class>");
    var class_instance = class_reference.$new(); // Class Object
    class_instance.<method>(); // Calling the method
});

Invoquer une méthode d'une instance déjà existante

Dans le cas où l'instance existe déjà, on utilise les API suivantes:

  • Java.performNow : Execute le code dans le contexte du runtime Java.

  • Java.choose : Pour choisir l'instance que l'on souhaite utiliser.

Java.choose utilise les deux callbacks suivants:

  • onMatch : Cette fonction est exécutée pour chaque instance de la classe spécifiée trouvée lors de l'opération Java.choose. Il reçoit l'instance actuelle comme paramètre. On peut y définir des actions personnalisées à effectuer sur chaque instance. Le paramètre d'instance représente chaque instance correspondante de la classe cible.

  • onComplete : Est utiliser pour définir des actions à effectuer à la fin de la fonction onMatch (exemple: actions de nettoyage).

Java.performNow(function() {
  Java.choose('<Package>.<Class_Name>', {
    onMatch: function(instance) {
      // TODO
    },
    onComplete: function() {}
  });
});

Interaction avec l'UI

Sous Android, certaines opérations, notamment celles qui interagissent avec l’interface utilisateur, doivent être effectuées sur le thread principal (également appelé thread UI). En effet, le framework Android applique la sécurité des threads pour les composants de l'interface utilisateur et n'autorise pas les mises à jour des éléments de l'interface utilisateur à partir des threads d'arrière-plan.

Il est donc nécessaire d'utiliser la fonction Java.scheduleOnMainThread pour utiliser le thread principal pendant l'operation.

Hook les constructors

Pour les constructors, on utilise la méthode "$init()".

Java.perform(function() {
  var a =  Java.use("<package_name>.<class>");
  a.$init.implementation = function(param){
    this.$init(123, 321);
  }
});

API / Fonctions natives

Frida permet également de hook des fonctions natives avec Interceptor.attach().

Exemple avec malloc:

Interceptor.attach(Module.findByName(null, 'malloc'), {
    onEnter: function(args) {
        var size = args[0].toInt32();
        console.log('[+] Memory Allocation:', size, 'bytes');
    };
});

Exemple avec une librairies interne à l'application:

var strcmp_adr = Module.findExportByName("libc.so", "strcmp");
Interceptor.attach(strcmp_adr, {
    onEnter: function (args) {
        // Modify or log arguments if needed
    },
    onLeave: function (retval) {
        // Modify or log return value if needed
    }
});

Ce script permet de s'attacher à la fonction native malloc afin de log la taille de chaque allocation mémoire.

Frida-trace

Frida-trace est un utilitaire de Frida permettant de tracer les appels en temps réel.

Exemple d'utilisation:

$ frida-trace -U -f com.target.xyz -i "open*"

ressource: https://frida.re/docs/frida-trace/

Dernière mise à jour