Une histoire de mémoire :
Cet article va présenter la méthodologie utilisée ainsi que le script développé lors d’un audit mobile afin de contourner les détections de Jailbreak implémentées au sein d’une application iOS. Nous allons montrer une façon différente de s’attacher à une fonction, utile dans le cas où le nom de cette dernière n’est pas connu.
Note : Habituellement, on utilise le nom de la fonction afin de s’attacher dessus. Des exemples pour Android (1) et iOS (2) peuvent être trouvé sur le site officiel de Frida.
Lors d’un audit d’une application iOS, nous avons constaté la présence d’un mécanisme de détection de Jailbreak au sein de cette dernière. En effet, lors des tentatives de lancement, l’application s’ouvrait et se fermait instantanément, et le message suivant pouvait être retrouvé au sein des journaux système :
Nous avons d’abord tenté de contourner cette protection avec nos scripts développés lors de précédents audits, sans succès. Même à la suite de la mise à jour de nos scripts, en se basant sur des scripts publiquement accessibles (3) , nous n’étions pas en mesure de contourner cette protection et le même message d’erreur nous était retourné.
C’est à ce moment que nous avons décidé de repartir de zéro et faire un script dédié. Nous avons donc repris le code décompilé que nous avons commencé à analyser et sommes partis du message d’erreur retourné par l’application lors des tentatives de lancement.
En cherchant à quel moment cette chaine de caractère est appelée, nous avons constaté que celle-ci n’était appelée qu’à un seul moment :
Nous nous sommes donc concentrés sur la fonction utilisant cette chaine de caractère. Une analyse plus approfondie de cette dernière a montré qu’elle faisait appel à une autre fonction, qui effectue toutes les vérifications liées au Jailbreak de l’appareil (par exemple vérification de la présence de Cydia sur le système).
Note : Cydia est une application dédié à la gestion des paquets sur un appareil iOS jailbreaké (installé souvent directement lors du jailbreak d’un appareil). D’autres applications similaires, comme par exemple Sileo, existent.
Dépendant du résultat retourné par les vérifications, l’application continue son lancement ou alors affiche le message d’erreur au niveau des journaux système et s’arrête.
Bon, maintenant nous avons identifié la fonction responsable du comportement constaté, ainsi que de la fonction effectuant les vérifications. Mais nous n’avons pas les noms de ces deux fonctions pour pouvoir nous attacher dessus avec Frida. Nous nous sommes donc inspirés de la manière dont Nviso contourne les mécanismes de Certificate Pinning au sein d’applications développés avec le langage Flutter (4).
Afin de s’attacher sur la fonction qui les intéresse, ils s’appuient sur la fonction Memory.scan de Frida (5), afin d’identifier au sein de la mémoire le motif correspondant au début de la fonction. Memory.scan de Frida permet de parcourir la mémoire accessible par l’application et de retourner les adresses mémoires auxquelles le motif a pu être trouvé.
Nous avons décidé de nous attacher à la fonction globale et non juste aux vérifications et avons donc récupéré le début de la fonction en hexadécimal, permettant de s’y attacher une fois l’application lancée.
Il ne reste plus qu’à modifier le comportement de l’application. Ne nécessitant pas de retourner de valeur particulière pour continuer le démarrage de l’application, nous avons remplacé la fonction par un simple return :
console.log(‘[+] La fonction pour les JB se trouve à : 0x‘ + (address – m.base).toString(16));
Interceptor.replace(address, new NativeCallback(() => {
console.log(‘[+] Bypassed !‘);
return 0;
}, ‘void‘, []));
En utilisant ce script, nous avons par la suite été en mesure de lancer l’application, contournant les mécanismes de protection contre les appareils Jailbreakés.
Le script final utilisé est le suivant :
var m = Process.findModuleByName(« AppIdentifier »);
var ranges = m.enumerateRanges(‘r-x‘);
ranges.forEach(range => {
//METTRE ICI LE MOTIF QUE VOUS CHERCHEZ
Memory.scan(range.base, range.size,« VOTRE MOTIF ICI », {
onMatch: function(address, size) {
console.log(‘[+] La fonction pour les JB se trouve à : 0x‘ + (address – m.base).toString(16));
Interceptor.replace(address, new NativeCallback(() => {
console.log(‘[+] Bypassed !‘);
return 0;
}, ‘void‘, []));
}
});
});
Références :
1) https://frida.re/docs/examples/android/
2) https://frida.re/docs/examples/ios/