Sécurité des Applications iOS - Les Débogueurs
Avec environ un demi-milliard d’appareils iOS vendus à travers le monde, la sécurité des applications iOS devient un enjeu majeur. Les attaques sur une application peuvent être très gratifiantes pour le pirate, lui donnant accès à des clés secrètes, des fonctionnalités normalement désactivées (rappelez-vous quand Skype facturait un supplément pour les appels en 3G) ou lui permettant d’uploader des faux scores sur des jeux.
En tant que développeurs mobiles, nous avons besoin d’avoir une bonne compréhension de la façon dont nous pouvons protéger nos applications. C’est pourquoi nous avons décidé de commencer une nouvelle série d’articles sur la sécurité des applications iOS. Étant donné que sécuriser une application implique de penser comme un attaquant, nous allons présenter chaque sujet des deux points de vue.
Aujourd’hui, nous allons parler de débogueurs.
GDB pour décrypter une application
Quand un pirate tente de cracker une application, il va, d’une manière ou d’une autre, attacher un débogueur à l’application qui s’exécute pour interagir avec elle. GDB peut être utilisé dans plusieurs buts. L’un d’entre eux consiste à décrypter le fichier binaire d’une application iOS. Toutes les applications que vous téléchargez à partir de l’App Store sont cryptées par Apple et les pirates utilisent un débogueur pour les décrypter en récupérant la mémoire virtuelle du processus en cours d’exécution.
En pratique, voilà ce que nous allons faire si nous voulons décrypter une application. Dans un premier temps nous téléchargeons l’application légalement depuis l’App Store et copions l’exécutable sur notre ordinateur. Imaginons que le nom de l’application soit HackedApp.app
. L’exécutable associé est donc HackedApp.app/HackedApp
.
Cet exécutable est un fichier Mach-O (voir Mach-O File Format Reference) qui contient un certain nombre d’informations sur l’application : le type d’architecture, les adresses virtuelles des méthodes, les librairies dynamiques liées, etc. L’outil en ligne de commande otool
est très utile pour explorer les fichiers Mach-O et les afficher d’une manière lisible. Nous utilisons la commande suivante pour savoir où le code est placé en mémoire virtuelle au runtime.
> otool -l HackedApp
HackedApp (architecture armv7):
…
Load command 1
cmd LC_SEGMENT
cmdsize 736
segname __TEXT
vmaddr 0x00001000
vmsize 0x0002d000
…
La ligne intéressante ici est vmaddr 0x00001000
: cela signifie que l’application va être chargée en mémoire virtuelle à l’adresse 0x1000
.
Ensuite, la commande suivante va nous permettre de savoir si le fichier exécutable est encrypté ou non et où sont situées les données encryptées pour l’architecture armv7
:
> otool -arch armv7 -l HackedApp | grep crypt
cryptoff 4096 # offset where encrypted data begin
cryptsize 180224 # size of encrypted data
cryptid 1 # 1 if encrypted, else 0
Dans cet exemple les données sont encryptées (cyrptid = 1
), commencent à l’adresse 4096 = 0x1000
et ont une taille de 180224 = 0x2C000
octets. Pour calculer la plage d’adresses où sont situées les données encryptées nous avons besoin de faire un peu de maths. Nous allons récupérer la mémoire virtuelle entre les adresses 0x1000 (vmaddr) + 0x1000 (cryptoff) = 0x2000
et 0x1000 (vmaddr) + 0x1000 (cryptoff) + 0x2C000 (cryptsize) = 0x2E000
.
Pour cela, il nous faut lancer l’application sur un appareil jailbreaké, attacher un débogueur puis appeler la commande dump memory
.
> ps -ax | grep HackedApp
15714 3:14.31 /var/mobile/Applications/UUID/HackedApp.app/HackedApp
> gdb -p 15714
...
Attaching to process 15714.
Reading symbols for shared libraries . done
Reading symbols for shared libraries ..........................................
......................................................................... done
0x39f5cd82 in <redacted> ()
gdb> dump memory dump.bin 0x2000 0x2E000
Le fichier dump.bin
contient alors les données décryptées de l’application.
Il nous reste deux étapes pour finir de cracker l’application. Premièrement, nous devons remplacer les données encryptées par celles contenues dans le fichier dump.bin
. Pour cela nous utilisons la commande dd
comme suit :
> dd seek=0x2000 bs=1 conv=notrunc if=./dump.bin of=./HackedApp
Deuxièmement, nous devons changer la valeur de cryptid
pour la mettre à 0
(facile à faire avec un éditeur hexadécimal).
Une fois toutes ces étapes réalisées, l’application est totalement décryptée et nous avons atteint notre but ultime : l’accès à la table des symboles. Cela signifie que nous avons accès à tous les noms de méthodes et à toutes les chaînes de caractères constantes que le développeur a définies dans le code de l’application. Pour extraire le nom des classes de la table des symboles, nous utilisons l’outil class-dump-z
(disponible ici) :
> class-dump-z HackedApp
…
@interface XXUnknownSuperclass : MyCustomClass
+(id)customMethod1;
+(id)customMethod2;
@end
@interface XXUnknownSuperclass (MyCategory)
+(id)customMethod:(float)param;
@end
…
Il devient alors facile de placer des breakpoints dans GDB sur les adresses de certaines méthodes pour intercepter leurs appels et altérer le comportement de l’application. Si nous connaissons le nom des méthodes et des classes que le développeur a utilisées dans son code, il devient relativement simple de comprendre ce que fait l’application en arrière plan et comment elle le fait.
Combattre GDB
Il y a deux choses qu’il faut savoir lorsqu’on utilise GDB :
- Premièrement, quand une application est en train d’être déboguée, le noyau est au courant et change l’état d’un flag particulier : le flag
P_TRACED
(déclaré dansproc.h
). Ce flag est mis à1
si un débogueur est rattaché au processus et à0
si l’application s’exécute normalement. Vous pouvez donc récupérer l’état de ce flag, vérifier si GDB est rattaché à votre application et mettre en place des actions pour limiter les fonctionnalités de l’application si c’est le cas.
Voici l’exemple d’une fonction qui permet de récupérer la valeur du flag P_TRACED
:
#include <sys/sysctl.h>
int isDebuggerPerforming() {
struct kinfo_proc infos_process;
size_t size_info_proc = sizeof(infos_process);
pid_t pid_process = getpid(); // pid of the current process
//
int mib[] = {CTL_KERN, // Kernel infos
KERN_PROC, // Search in process table
KERN_PROC_PID, // the process with pid =
pid_process}; // pid_process
//
//Retrieve infos for current process in infos_process
int ret = sysctl(mib, 4, &infos_process, &size_info_proc, NULL, 0);
if (ret) return 0; // sysctl failed
//
struct extern_proc process = infos_process.kp_proc;
int flags_process = process.p_flag;
return flags & P_TRACED // value of the debug flag
}
- Deuxièmement, nous pouvons directement désactiver tout débogueur qui est attaché ou qui veut s’attacher à un processus grâce à la fonction
p_trace
appelée avec le paramètrePT_DENY_ATTACH
. Si un débogueur est attaché à une application, un appel àp_trace(PT_DENY_ATTACH, 0, 0, 0)
va envoyer un signalENOTSUP
et quitter le processus courant. Dans le cas où l’application s’exécute normalement et qu’un débogueur tente de s’y rattacher, une erreursegmentation fault
apparaîtra.
Voici un exemple qui utilise p_trace
sur iOS :
#include <dlfcn.h>
void disableDebugger() {
void * handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW); // load dynamic librairies
int (* ptrace_function)(int, pid_t, caddr_t, int); // ptrototype of ptrace function
ptrace_function = dlsym(handle, string); // pointer on ptrace function
ptrace_function(31, 0, 0, 0); // PT_DENY_ATTACH == 31, not defined in iOS
dlclose(handle);
}
Et maintenant ?
Dans cet article nous avons vu comment les pirates décryptent des applications avec GDB pour en prendre le contrôle et nous avons présenté deux solutions qui permettent d’empêcher le traçage d’une application. Cependant, un lecteur attentif aura remarqué que ces solutions ne sont pas complètement sans défauts : un pirate obstiné va certainement chercher des appels à p_trace
ou sysctl
dans votre application et placer des breakpoints dans GDB pour changer leurs valeurs de retour, désactivant vos protections. Cela montre bien que la sécurité reste un jeu du chat et de la souris, où nous pouvons simplement complexifier la tâche du pirate.
Nous espérons que vous avez aimé le premier épisode de notre nouvelle série d’articles. Nous avons couvert une petite partie du sujet, les débogueurs n’étant pas les seuls outils capables de changer le comportement d’une application au runtime. De plus il existe d’autres approches, comme la modification du binaire de l’application lui-même, qui sera sûrement traité dans un prochain épisode.
Dans tous les cas, si vous avez des questions ou voulez juste partager un commentaire, nous serons très contents de vous répondre sur Twitter!