Smartphone et mobilité
Le terme smartphone désigne un téléphone mobile disposant d’un nombre important de fonctionnalités que l’on retrouve habituellement sur un ordinateur. Ainsi, en plus d’être capable d’émettre et de recevoir des appels ou des messages textuels, il permet à son utilisateur d’exécuter un certain nombre de programmes sous la forme d’applications. Son interface utilisateur est composée d’un écran tactile accompagné d’un ensemble de boutons capacitifs ou physiques. Sa conception à mi-chemin entre le téléphone mobile et l’ordinateur lui permet en outre de posséder un format adapté à la mobilité, tout en étant autonome sur une ou plusieurs journée(s) grâce à sa batterie intégrée. De plus, un smartphone est équipé d’un grand nombre de capteurs et de périphériques de communication lui permettant d’échanger avec un environnement de plus en plus connecté. Enfin, son format réduit ainsi que les contraintes énergétiques nécessaires à sa mobilité lui imposent des restrictions concernant la taille et la consommation des composants matériels qu’il embarque.
Matériel embarqué
La très grande majorité des smartphones s’appuient sur une base matérielle ayant pour fondation un processeur d’architecture ARM. Plus précisément, le processeur prend place au sein d’un System on Chip (SoC) qui regroupe les autres unités principales du système ainsi que les contrôleurs de bus permettant d’accéder aux périphériques du smartphone. Le choix d’utiliser ce type de processeurs s’appuie sur leur meilleure efficacité énergétique en comparaison avec les autres architectures existantes. La dernière itération de l’architecture ARM en date pour les profils applicatifs , nommée ARMv8-A, possède les caractéristiques suivantes:
• Première architecture ARM 64 bits.
• Jeu d’instructions de taille fixe (32 bits).
• Mode de compatibilité avec l’architecture 32 bits précédente.
• 31 registres généraux de 64 bits.
• 32 registres de 128 bits utilisés pour les calculs SIMD.
• 4 niveaux de privilèges permettant la prise en charge des extensions d’aide à la virtualisation, ainsi que les extensions de sécurité.
Plus précisément, ces quatre niveaux de privilèges, appelés Exception Level (EL) dans la nomenclature proposée par ARM. Les niveaux EL0 et EL1 sont destinés, de manière similaire aux ring 3 et ring 0 de l’architecture Intel64, respectivement aux applications et aux systèmes d’exploitation. EL2 est quant à lui réservé à l’hébergement d’hyperviseurs dans le cadre des extensions de virtualisation de l’architecture. Enfin, le niveau EL3 appartient aux extensions de sécurité, aussi appelées TrustZone. Ces extensions ont pour but de définir deux modes de sécurité hermétiques, au sein du processeur ainsi que pour le reste du système, appelées normal world et secure world. De nombreux mécanismes d’isolation sont ensuite mis en place pour que les ressources appartenant au secure world soient rendues inaccessibles depuis le normal world. De plus, le niveau de privilège EL3 est le seul niveau de privilège capable d’effectuer la transition du normal world au secure world et inversement au sein du processeur.
Plus de détails sur l’organisation interne des systèmes à base de processeurs ARMv8-A, ainsi que des précisions supplémentaires sur cette architecture sont fournies dans l’article [Averlant 2017a]. Outre le processeur et les autres composants intégrés au SoC tels que le GPU, le reste du matériel est essentiellement composé de périphériques d’interface utilisateur, de périphériques de communication et de multiples capteurs. Parmi les périphériques de communication présents sur un smartphone, on retrouve généralement des périphériques permettant d’accéder aux réseaux de téléphonie mobile et Wi-Fi, ainsi qu’aux technologies Bluetooth et Near-Field Communication (NFC). Ces nombreux moyens de communication représentent des vecteurs d’attaque supplémentaires à considérer dans le cadre de notre modèle de sécurité. Concernant les capteurs embarqués, nous pouvons les répartir en quatre catégories :
• Les capteurs photographiques et audio.
• Les capteurs de mouvement (accéléromètre, gyroscope).
• Les capteurs de position (GPS, champ magnétique, proximité).
• Les capteurs environnementaux (température, lumière, pression, humidité).
L’ensemble des données produites par ces capteurs représente, là aussi, une menace pour la vie privée des utilisateurs de smartphone.
L’écosystème Android
Le marché des systèmes d’exploitation dédiés aux smartphones est globalement divisé entre deux acteurs : android développé par Google, et iOS par Apple. Nous avons choisi dans cette thèse de nous concentrer sur le système d’exploitation Android, dont le code a l’avantage d’être disponible sous licence open source contrairement à son rival. Il est en effet possible de le consulter depuis les dépôts Git de l’Android Open Source Project (AOSP) maintenus par Google depuis 2007. Toutefois, certaines parties mineures du système d’exploitation tel qu’il est distribué dans les smartphones restent sous licence propriétaire, comme les applications Google ou les modifications cosmétiques apportées par les différents constructeurs. Depuis son rachat par Google en 2005, le système d’exploitation Android n’a cessé d’évoluer, tant en terme de fonctionnalités que de mesures de sécurité. Dans cette section, nous allons effectuer un tour d’horizon de l’écosystème Android en sa dernière version en date, Android 9.0 « Pie ». Pour se faire, nous allons premièrement présenter l’architecture et l’organisation des différentes couches logicielles le composant. Ensuite nous étudierons le modèle de sécurité mis en place au sein de cet OS. Enfin, nous exposerons le modèle d’attaque considéré pour cette thèse, et introduirons notre problématique.
Panorama des couches logicielles
Le noyau Linux
Premièrement, le système d’exploitation Android se base sur un noyau Linux légèrement modifié qui provient exclusivement des branches Long Term Support (LTS) du noyau. Ces branches ont d’ailleurs obtenu un support étendu à 6 ans, depuis la sortie d’Android 8 « Oreo » et la mise en place du projet Treble, pour permettre aux constructeurs d’effectuer des mises à jour de sécurité pour toute la durée de vie du smartphone. Parmi les spécificités de la version Android du noyau, on retrouve notamment :
• Binder : un système de Communication Inter Processus (IPC) propre à Android dont le fonctionnement est décrit dans la section 1.2.2.
• ION : un driver d’allocation de mémoire physique.
• Ashmem : un gestionnaire de mémoire partagée.
• Low Memory Killer (LMK) : un driver utilisé pour stopper automatiquement les processus les moins « prioritaires » lors d’un manque de mémoire.
• Paranoid networking : un composant utilisé pour restreindre l’accès des processus aux sockets.
D’autres modifications mineures sont apportées au noyau telles que l’ajout ou la personnalisation de divers drivers , ainsi que l’ajout d’un régulateur de fréquence CPU et d’un planificateur de tâches optimisé. De plus, ces spécificités sont unifiées au fur et à mesure dans le noyau générique, ce qui facilite la maintenance et l’évolution de la version dédiée à Android.
Le framework Android
S’exécutant sous le contrôle du noyau Linux, le framework Android, aussi appelé Android Application Framework (AAF), représente la couche logicielle assurant le support d’exécution des applications Android. Celle-ci peut s’apparenter à un middleware fournissant aux développeurs la capacité de créer facilement des applications en s’appuyant sur un ensemble d’interfaces de programmation (API) et de services de haut niveau. Ainsi, la conception du framework n’est pas indifférente à l’adoption massive qu’a connue le système d’exploitation Android. Cette riche composition est notamment étudiée pour mettre à profit le plus possible la réutilisation de code afin d’économiser au maximum la mémoire, et ainsi prendre en compte une contrainte importante des smartphones. Premièrement, la couche fonctionnalités est consacrée à fournir les interfaces d’accès à l’ensemble des fonctionnalités offertes par le middleware. Ces interfaces sont définies par le biais d’APIs qui sont de deux types : celles appartenant au software development kit (SDK) utilisables depuis les parties d’applications écrites en Java ; et celles définies dans le native development kit (NDK) pouvant être exploitées depuis du code natif (C/C++). De plus, les applications peuvent aussi faire appel à de nombreux services système, pouvant être définis comme un ensemble de « serveurs » responsables de la gestion des ressources, physiques ou logicielles, du smartphone. Par exemple, le service Wi-Fi s’occupe de la gestion du périphérique du même nom, et offre la capacité d’effectuer des connexions à n’importe quel réseau sans fil à proximité. Autre exemple, le service PackageManager permet de se renseigner sur certains détails concernant les applications installées. La plupart des services système sont écrits en Java, mais il existe aussi certains services système natifs. En outre, ces services sont le plus souvent consultés à travers les méthodes des APIs Android, et non pas directement même si cela reste possible.
Ensuite vient la couche support qui constitue le ciment liant les différents composants du framework entre eux. En effet, c’est elle qui assure le lien entre les APIs et services de haut niveau mis en place dans la couche fonctionnalités, et les ressources de plus bas niveau d’abstraction, en fournissant les mécanismes d’adaptation nécessaires à leur accès. Le plus important de ces mécanismes est l’environnement d’exécution Java, nommé Android Runtime (ART), qui est spécifique à Android. Celui-ci est chargé d’effectuer la compilation du bytecode Dalvik EXecutable (Dex), issu de la pré-compilation du code Java des applications, en code machine. Ce processus est effectué grâce à une combinaison de compilation anticipée (AOT), de compilation juste-à-temps (JIT) et de compilation guidée par profil. Le produit de cette compilation est un fichier ELF contenant à la fois le code machine correspondant aux méthodes compilées, ainsi que le bytecode Dex à des fins de debogage. De plus, l’ART embarque un interpréteur Dex utilisé pour exécuter le code des applications fraîchement installées, ou bien les sections de code n’ayant pas été compilées car rarement utilisées. En outre, il assure le support d’exécution du bytecode ainsi compilé ou interprété de manière similaire à n’importe quelle machine virtuelle Java. On peut citer par exemple la fonctionnalité de ramasse miettes utilisé pour collecter les zones de mémoire n’étant plus utilisées par chaque application. En collaboration avec l’environnement d’exécution ART, la Java Native Interface (JNI) permet d’intégrer du code natif (C/C++) au code Java. Plus précisément, cette interface rend possible le chargement du code machine contenu dans des bibliothèques partagées ELF, et permet son interaction avec le bytecode exécuté dans l’environnement ART.
L’autre facette de cette couche support concerne les outils nécessaires au démarrage du système et à son maintien dans un état fonctionnel. Ces outils appartiennent à deux catégories. Tout d’abord la catégorie des binaires natifs qui regroupe le programme init, un shell dérivé du Korn Shell , certaines commandes Unix fournies par l’implémentation Toybox, et quelques autres binaires spécifiques à Android permettant d’interagir avec certains éléments du framework. Pour être plus précis concernant le binaire init, l’implémentation d’Android est quelque peu différente de son équivalent Unix. Celui-ci reste bien évidemment le premier binaire appelé par le noyau Linux au démarrage, et père de tous processus utilisateur, mais il est aussi chargé de la gestion des propriétés système. Ces dernières peuvent être décrites comme des registres stockant l’état et la configuration du système. Init est d’ailleurs capable de déclencher certaines actions lors de la modification de ces propriétés. Enfin, il est aussi responsable du démarrage de la deuxième catégorie d’outils : les daemons. Ceux-ci sont des programmes natifs, fonctionnant en arrière plan, et qui assurent certaines tâches de bas niveau. La plupart des daemons possèdent une ou plusieurs interfaces de communication (socket ou binder) utilisées le plus souvent depuis les services système ou les binaires natifs. Parmi ces daemons, on retrouve notamment ServiceManager et Zygote. ServiceManager est le daemon auprès duquel tous les services système doivent se déclarer lors de leur lancement. Ainsi, il fonctionne comme un annuaire de services auprès duquel les applications peuvent se renseigner pour découvrir l’ensemble des services leur étant disponibles. Zygote constitue quant à lui la base de tous les processus de haut niveau (i.e. des applications). Plus précisément, ce daemon a été étudié pour réduire le temps de lancement de ces processus et optimiser leur consommation mémoire. Ainsi, à son démarrage, il charge et initialise l’ensemble des composants nécessaires à l’exécution des processus de haut niveau (bibliothèques, descripteurs de fichiers, environnement ART…). Puis, lorsque l’un de ces processus doit être lancé, Zygote fork son processus et adapte les attributs et les privilèges du processus ainsi créé avant de donner la main au code du logiciel devant être lancé.
|
Table des matières
Introduction
1 Contexte général, mobilité et écosystème Android
1.1 Smartphone et mobilité
1.1.1 Matériel embarqué
1.2 L’écosystème Android
1.2.1 Panorama des couches logicielles
1.2.2 Modèle de sécurité existant
1.3 Motivations des attaquants et problématique
1.4 Conclusion
2 État de l’art
2.1 Terminologie de la sécurité informatique
2.1.1 Sûreté de fonctionnement
2.1.2 Sécurité-immunité
2.1.3 Politiques de sécurité
2.2 Mécanismes de contrôle d’accès pour Android
2.2.1 Mécanismes de niveau système
2.2.2 Mécanismes de niveau OS
2.2.3 Mécanismes de niveau framework
2.2.4 Mécanismes de niveau applicatif
2.3 Politiques d’autorisation pour Android
2.3.1 Considérations relatives à l’environnement Android
2.3.2 Politiques d’autorisation de la littérature
2.4 Conclusion et démarche scientifique
3 Politique de sécurité pour Android sensible au contexte
3.1 Présentation, objectifs et modèle d’attaque
3.1.1 Objectifs de la solution
3.1.2 Objets considérés par la politique
3.1.3 Types de règles de contrôle d’accès
3.1.4 Contexte nécessaire à l’application des règles
3.1.5 Modèle et scénarios d’attaque
3.2 Format, exemples et distribution
3.2.1 Format des règles de sécurité : le Secure Manifest
3.2.2 Exemples d’utilisation
3.2.3 Distribution des Secure Manifest
3.3 Les conflits et leur gestion
3.3.1 Définition et objectifs de la gestion des conflits
3.3.2 Aspects pratiques
3.3.3 Aspects ergonomiques
3.3.4 Améliorations possibles
3.4 Encourager l’utilisateur à paramétrer la politique
3.5 Conclusion
4 Architecture de sécurité multi-niveau
4.1 Besoins et contraintes techniques de notre politique
4.1.1 Contrôle d’accès et contexte
4.1.2 Protections contre le modèle d’attaque envisagé
4.1.3 Un moniteur de référence multi-niveau
4.2 Présentation de l’architecture
4.2.1 Aperçu des composants et caractéristiques
4.2.2 Détails des composants
4.3 Mise en œuvre de la politique
4.3.1 Récupération des Secure Manifest
4.3.2 Génération, activation et mise en application des règles
4.3.3 Gestion des conflits et expérience utilisateur
4.4 Contrôle de l’intégrité de notre solution
4.4.1 Périmètre de la protection
4.4.2 Protection contre les attaques logicielles
4.4.3 Protection contre les attaques matérielles
4.4.4 Protection au démarrage et chaîne de confiance
4.5 Conclusion
5 Prototype et performances
5.1 Plateformes d’expérimentation
5.1.1 La plateforme de développement 96Boards hikey
5.1.2 L’émulateur Android
5.2 Implémentation
5.2.1 AOSP, base logicielle d’Android
5.2.2 Implémentation du PDA
5.2.3 Implémentation du PHS
5.2.4 Implémentation du SCIH
5.2.5 Implémentation de l’IH
5.2.6 Limitations du prototype
5.3 Mesures de performances
5.3.1 Performances des actions atomiques de notre prototype
5.3.2 Microbenchmarks
5.3.3 Macrobenchmarks
5.4 Validation par scénario
5.4.1 Scénario n°1
5.4.2 Scénario n°2
5.4.3 Mise en relation avec les attaques récentes
5.5 Conclusion
Conclusion