Module CCFI : Fonctions de signature et de mise à jour

Télécharger le fichier pdf d’un mémoire de fin d’études

Propriétés de sécurité

Dans le domaine de la sécurité des systèmes d’informations on considère quatre grandes propriétés de sécurité :
Confidentialité : l’information est uniquement accessible aux individus autorisés, par exemple pour protéger la vie privée ou une propriété intellectuelle ;
Intégrité : l’information n’a pas été modifiée (exactitude et complétude de l’information) par une action involontaire ou illégitime, par exemple lors de la diffusion de données sur un canal non sécurisé ;
Authenticité : l’information a été émise par une entité autorisée, et n’a pas été modifiée par une action involontaire ou illégitime, par exemple lors de la communication entre entités sur un canal non sécurisé ;
Disponibilité : l’information ou le service est et reste accessible aux utilisateurs dans le temps, par exemple pour l’accès à un service sur internet.
Pour un attaquant, l’objectif sera de compromettre ces propriétés de sécurité tandis qu’une protection cherchera à assurer ces propriétés. Suivant le besoin applicatif, il peut être nécessaire d’assurer une ou plusieurs de ces propriétés. Il est important de noter que, de par sa définition, l’authenticité implique l’intégrité.
Les attaques par injection de fautes sont souvent plus compliquées à mettre en place que les attaques informatiques classiques. Par exemple, il est plus simple d’attaquer la propriété de disponibilité d’un service en coupant l’alimentation du système qu’en réalisant une injection de fautes lorsque le système est accessible physiquement. C’est pourquoi un attaquant utilise l’in-jection de fautes en dernier recourt, lorsque les attaques informatiques classiques ne permettent pas de compromettre les propriétés de sécurité souhaitées.

Sécurité des données

L’objectif des systèmes numériques est de traiter automatiquement des données à la place des humains. Un attaquant cherchera toujours soit à récupérer une donnée secrète, soit à modifier le traitement d’une donnée pour accéder à un service dont l’accès est restreint. Il est donc important d’assurer la protection de ces données pour se protéger contre un attaquant.
Pour empêcher un attaquant d’accéder à des données secrètes, il est possible d’utiliser la propriété de confidentialité des données. Par exemple, la technologie Intel SGX [Costan et al. 2016] assure la confidentialité des données dans des systèmes multi-utilisateurs en chiffrant le contenu de la mémoire.
La protection contre la modification des données lors du stockage et du traitement est un problème complexe lié à l’intégrité des données. Dans les systèmes multi-utilisateurs, la virtua-lisation de la mémoire et l’utilisation de memory management unit (MMU) permet d’isoler les sections mémoires de chaque processus. Ainsi, un processus malveillant ne peut pas modifier le contenu de la mémoire d’un processus cible qui s’exécute sur le même système.

Sécurité du code

Pour traiter des données, une catégorie des systèmes numériques utilisent des processeurs. Ces circuits exécutent une suite d’instructions qui représentent le programme en charge du traitement des données.
Pour protéger le traitement des données, il est nécessaire de protéger le code via la propriété d’intégrité du code. Cela signifie qu’une protection s’assure que le code n’a pas été modifié avan son exécution. Par exemple, de nombreux systèmes informatiques utilisent la fonctionnalité W X pour empêcher les sections mémoires exécutables d’être modifiées par une écriture dans ces sections.
Il est également possible de vérifier que le code a bien été émis par une entité autorisée. Cette propriété est l’authenticité du code. On retrouve typiquement ce type de protection dans les mécanismes de démarrage sécurisé, pour lesquelles l’authenticité du code est vérifiée avant son chargement en mémoire pour exécution [Wang et al. 2022].
Enfin, le code qui représente un programme contient parfois des informations sensibles. Un attaquant peut utiliser de la rétro ingénierie pour voler un algorithme privé présent dans un programme ou construire une attaque. Il est donc parfois utile de limiter l’accès ou la com-préhensibilité du code aux entités autorisées uniquement. Pour cela, il existe des techniques de dissimulation telles que l’obfuscation [Collberg et al. 2009] ou le code polymorphe [Sharma et al. 2014]. Ces techniques utilisent des procédés de réécriture de code pour complexifier sa com-préhension par un humain. Il existe aussi des techniques de dissimulation à l’aide d’une fonction mathématique non cryptographique. Cela permet de stocker le code sous une forme dissimulée et de retrouver la valeur des instructions avant l’exécution. Ces techniques de dissimulation ne sont pas toujours robustes face à un attaquant. Les techniques de confidentialité du code permettent d’atteindre un meilleur niveau de sécurité basé sur des fonctions cryptographiques. Le code est conservé chiffré en mémoire et n’est déchiffré que lors de son exécution. Certains systèmes de démarrage sécurisé assurent ainsi la propriété de confidentialité du code [Wang et al. 2022].

Sécurité du flot de contrôle

Pour exécuter un programme, un processeur charge puis exécute les instructions de ce programme séquentiellement. Pour construire des structures de contrôle telles que les branche-ments conditionnels ou les boucles, certaines instructions permettent d’exécuter ensuite une autre instruction que celle qui suit séquentiellement. Le flot de contrôle d’un programme est l’enchainement des instructions qui composent ce programme. Le flot de contrôle est générale-ment représenté par un graphe orienté où les nœuds sont des séquences maximales d’instructions avec une seule instruction d’entrée et une seule instruction de sortie, appelées blocs de base. Les arcs sont les transitions entre les blocs de base. Le graphe de flot de contrôle (CFG) représente l’ensemble des chemins d’exécution possibles, pour un programme.
Plusieurs attaques exploitent une déviation du flot de contrôle pour exécuter arbitrairement du code. On peut par exemple citer le Return Oriented Programming (ROP) [Shacham 2007] et le Jump Oriented Programming(JOP) [Bletsch et al. 2011]. Pour se protéger contre ce type d’attaque, il est nécessaire de s’assurer que les chemins d’exécution pris par le programme sont bien définis dans le CFG. Abadi et al. proposent la propriété d’intégrité du flot de contrôle pour se protéger contre ce type d’attaque [Abadi et al. 2009]. On décompose généralement l’intégrité du flot de contrôle en plusieurs catégories [Burow et al. 2017] :
— intégrité des séquences d’instructions dans les blocs de base ;
— intégrité des branchements directs (destinations connues avant l’exécution) ;
— intégrité des branchements indirects (destinations inconnues avant l’exécution) ;
— intégrité des appels et retours de fonction.
Dans les systèmes d’information non embarqués, ce sont les retours de fonction et les bran-chements indirects qui sont à l’origine des vulnérabilités. Le caractère dynamique de ces trans-ferts de flot de contrôle rend l’identification des adresses cibles complexes. Une technique cou-rante pour identifier les cibles des branchements indirects est de regrouper les blocs de base par propriétés communes pour former des classes d’équivalence [Burow et al. 2017]. Chaque branchement indirect est ensuite associé à une unique classe d’équivalence et donc à un nombre fini de successeurs. Généralement, pour les appels de fonction indirects, les classes d’équivalence sont construites à partir des propriétés de fonctions (ensemble de blocs de base). Par exemple, il est possible d’utiliser une unique classe d’équivalence qui regroupe tous les premiers blocs de base des fonctions d’un programme. Tous les appels de fonction indirects sont associés à cette classe d’équivalence, ce qui signifie que toutes les fonctions peuvent être appelées depuis un branchement indirect. Un autre exemple est d’utiliser les prototypes de fonction pour re-grouper les premiers blocs de base des fonctions d’un programme. Cette fois, il existe plusieurs classes d’équivalence dont la taille, c’est-à-dire le nombre de blocs de base qui les constituent, varie. Il est important de noter que dans une perspective de sécurité, il est recommandé d’avoir des classes d’équivalence dont la taille est la plus petite possible [Burow et al. 2017]. En effet, cela permet de limiter le nombre de blocs de base atteignables depuis un branchement indirect.
Une fois les cibles des branchements indirects identifiées, un mécanisme est mis en place pour contrôler la validité des cibles lors de l’exécution. On distingue les techniques qui vérifient la légitimité du couple (branchement indirect, adresse de destination) lors de l’exécution [Abadi et al. 2009] et les techniques qui remplacent les branchements indirects par de nouvelles séquences d’instructions équivalentes composées de suites de test et de branchements directs [Arthur et al. 2015].

Attaques par injection de fautes

La figure 2.1 présente le fonctionnement d’une attaque par injection de fautes. En bas de la figure, l’attaquant injecte une perturbation dans le circuit cible. Cette perturbation se traduit par une faute dans la logique du circuit et dans le fonctionnement de la microarchitecture. Ainsi, il est possible de fauter le programme exécuté par ce circuit. Un attaquant pourra ex-ploiter l’effet de cette faute sur le programme pour compromettre une des propriétés de sécurité présentées précédemment. Bien que dans cette thèse, nous nous concentrons sur les circuits de type processeur, ces attaques fonctionnent également sur d’autres types de circuit, par exemple les accélérateurs cryptographiques.

Mécanismes d’injection de fautes

Les mécanismes d’injection de fautes permettent à un attaquant de soumettre le circuit cible à une perturbation physique. Les mécanismes d’injection de fautes se différencient par :
— leur précision spatiale, c’est-à-dire la capacité à sélectionner précisément un ou plusieurs bits ainsi que l’effet de la faute sur ces bits (inversion, mise à 1, mise à 0, transformation aléatoire) ;
— leur précision temporelle, c’est-à-dire la capacité à contrôler le moment d’injection, par exemple lors de l’exécution d’une instruction ;
— leur permanence, c’est-à-dire la durée de la perturbation ou de son effet dans le temps (un cycle, plusieurs cycles ou permanente).
Lors de la construction d’une attaque, on choisit le mécanisme d’injection de fautes en fonction de ces critères et de l’attaque visée. Certaines attaques peuvent être réalisées avec un faible niveau de précision tandis que d’autres requièrent une précision maximale pour éviter de déclencher une contre-mesure.
Perturbation de l’horloge
L’horloge d’un circuit intégré permet de cadencer les éléments synchrones de ce circuit. Lors des fronts d’horloge (montant et/ou descendant), les éléments synchrones changent d’état en fonction de la valeur de leurs entrées. La valeur minimale de la période d’horloge d’un circuit est définie par le temps maximum requis pour qu’un signal se propage dans la logique combinatoire entre deux éléments synchrones. En dessous de cette valeur, il est possible que les signaux en entrée des éléments synchrones ne soient pas stables et donc qu’une faute en sortie des éléments synchrones apparaisse.
Agoyan et al. utilisent des glitches sur le signal d’horloge pour perturber le calcul de l’algorithme AES sur un FGPA et ensuite faire une attaque par cryptanalyse du résultat er-roné [Agoyan et al. 2010]. Ils montrent notamment le rapport entre la durée de la perturbation du signal d’horloge et le nombre de bits fautés dans le résultat. Korak et al. combinent les glitches sur le signal d’horloge avec des glitches sur le bus d’alimentation pour améliorer le taux de succès de l’injection de fautes [Korak et al. 2014]. Ils rapportent que la durée du glitch permet de cibler différents étages dans la microarchitecture des microcontrôleurs cibles. Claudepierre et al. proposent un mécanisme d’injection de fautes multiples à bas coût en utilisant des perturbations du signal d’horloge [Claudepierre et al. 2021]. Enfin, il existe aussi des systèmes d’injection de fautes à faible coût, commercialisés pour le grand public, tels que la carte ChipWhisperer 1.
Les mécanismes de perturbation du signal d’horloge permettent d’atteindre une précision spatiale modérée. En effet, il n’est pas possible de contrôler la valeur de la faute, mais il est possible de fauter un unique bit [Agoyan et al. 2010]. La précision temporelle est limitée à la période d’horloge dans laquelle la perturbation est injectée. Ce type de perturbation est non-permanent et le circuit reprend son fonctionnement nominal au cycle suivant. Cependant, l’effet de la perturbation peut se propager sur plusieurs cycles, par exemple lorsque la perturbation modifie la valeur contenue dans un registre.
Perturbation de l’alimentation
La vitesse de propagation des signaux électriques dans la logique combinatoire d’un circuit dépend de la tension d’alimentation de ce circuit. Ainsi, diminuer la tension d’alimentation aug-mente ce temps de propagation. Il peut alors apparaitre des erreurs d’échantillonnage similaires à celles observées en présence d’une perturbation sur l’horloge. Zussa et al. montrent dans leurs travaux l’équivalence entre les attaques sur l’horloge et celles sur l’alimentation [Zussa et al. 2013].
Les techniques classiques de perturbation de l’alimentation sous-alimentent le circuit pen-dant une courte période [Zussa et al. 2013 ; Korak et al. 2014]. D’autres travaux proposent de créer un court-circuit pour perturber le réseau d’alimentation du circuit cible [O’Flynn 2016].
Les mécanismes de perturbation sur l’alimentation sont similaires aux mécanismes de pertur-bation de l’horloge. Enfin, il est important de noter que ces mécanismes permettent d’injecter de multiples fautes, par exemple pour attaquer un processus de démarrage sécurisé embarqué [Van den Herrewegen et al. 2020].
Perturbation de la température
Bien que peu courant, il est possible de perturber un circuit en modifiant sa température. Hutter et al. chauffent un microcontrôleur au-delà de la température de fonctionnement et parviennent à attaquer une implémentation logicielle de RSA [Hutter et al. 2013]. Il faut toutefois noter que les précisions temporelle et spatiale sont faibles. En effet, il est difficile de chauffer un circuit localement. Il est aussi difficile de contrôler des variations de température sur des intervalles de temps du même ordre de grandeur que la période d’horloge. Pour les mêmes raisons, il est également difficile de limiter le comportement fauté à un cycle d’horloge. La perturbation de la température n’est donc pas un mécanisme d’injection très pertinent pour un attaquant.
Perturbation électromagnétique
Émettre un champ électromagnétique proche d’un circuit permet d’induire des courants dans ce circuit ce qui génère des fautes. Dumont et al. proposent un modèle de ce phéno-mène [Dumont et al. 2021]. Lors d’une impulsion électromagnétique, le réseau d’alimentation du circuit est perturbé pendant la durée de l’impulsion. Cette perturbation affecte particu-lièrement les éléments synchrones du circuit. De plus, ils rapportent que la polarisation de l’impulsion électromagnétique permet de contrôler la nature de la faute (mise à 0 ou à 1) et que la propagation de la perturbation reste localisée autour de la sonde d’injection.
Les perturbations par impulsion électromagnétique sont populaires et font l’objet de nom-breuses études [Gandolfi et al. 2001 ; Moro et al. 2013 ; Ordas et al. 2017 ; Trouchkine et al. 2021 ; Proy et al. 2019 ; Dumont et al. 2021]. Cette technique permet d’atteindre une très bonne précision spatiale et temporelle avec un effet localisé. La complexité de mise en œuvre et le coût du matériel sont toutefois légèrement supérieurs à ceux des mécanismes de perturbations de l’alimentation et de l’horloge.
Perturbation lumineuse
Un rayonnement lumineux focalisé et plus particulièrement un rayonnement laser permet de créer une faute dans un circuit intégré [Skorobogatov et al. 2003]. Il est cependant nécessaire de mettre à nu les couches de silicium du circuit en le décapsulant par abrasion mécanique ou chimique, ce qui impacte fortement la compléxité et le coût d’une attaque.
Roscian et al. proposent un modèle des perturbations lumineuses utilisant l’effet photo-électrique [Roscian et al. 2013]. Lors du passage d’un photon à travers les couches de silicium d’un circuit, un courant électrique transitoire apparait et peut perturber le fonctionnement des transistors. Ils rapportent que l’effet d’une faute injectée par un rayonnement laser est soit la mise à 0 soit la mise à 1, mais rarement une inversion de la valeur du bit ciblé. De plus, ce type de mécanisme permet d’obtenir des fautes ciblant un unique bit très précisément [Colombier et al. 2019]. Aujourd’hui, les capacités des mécanismes d’injection laser permettent de faire des attaques avec de multiples fautes. Colombier et al. utilisent un banc laser avec quatre spots pour fauter jusqu’à 4 bits non contigus dans un même cycle ou plusieurs bits non contigus sur plusieurs cycles [Colombier et al. 2022]. Ce mécanisme d’injection de fautes permet donc de construire des attaques bien plus complexes, potentiellement capables de déjouer de nombreuses contre-mesures.
Les perturbations lumineuses permettent d’atteindre une précision spatiale et temporelle encore plus grande que les perturbations électromagnétiques. Ceux sont également des méca-nismes populaires [Skorobogatov et al. 2003 ; Roscian et al. 2013 ; Dutertre et al. 2014 ; Vasselle et al. 2017 ; Guillen et al. 2017 ; Colombier et al. 2019 ; Colombier et al. 2022] bien que le coût de l’équipement et la complexité de préparation soient élevés.
Perturbation par rayon X
En 2017, Anceau et al. ont proposé une approche permettant de modifier le comportement d’un transistor dans la mémoire d’un circuit à l’aide de faisceaux focalisés de rayon X [Anceau et al. 2017]. La perturbation est cette fois semi-permanente et il est nécessaire de chauffer le circuit pour la faire disparaitre. Cela s’oppose aux mécanismes d’injection présentés précédem-ment pour lesquels la perturbation disparait par elle-même après quelques cycles d’horloge. Ce type d’attaque ouvre de nouvelles perspectives qui sont tout de même limitées par le coût d’accès au matériel nécessaire (ici un synchrotron).
Exploitation des fonctionnalités du circuit cible
Jusqu’ici nous avons uniquement présenté des mécanismes d’injection de fautes externes pour lesquels un accès physique au circuit cible est nécessaire. Il existe aussi des mécanismes d’injection de fautes qui utilisent des fonctionnalités internes du circuit cible et qui ne nécessitent aucun accès physique. Le prérequis commun à tous ces mécanismes est un accès logique à la cible, par exemple une connexion réseau. La perturbation physique est alors injectée par le circuit cible lui-même en détournant des propriétés physiques ou des fonctionnalités déjà présentes et contrôlables par l’attaquant.
Les attaques de type RowHammer permettent de modifier la valeur de bits stockés dans une cellule mémoire de type DRAM en réalisant des accès répétés aux cellules adjacentes [Y. Kim et al. 2014]. Un attaquant exécutant un programme malicieux sur la même plateforme physique qu’un programme cible peut alors modifier le contenu de la mémoire du programme cible sans y accéder. L’attaquant n’a pas besoin d’avoir un niveau de privilège particulier autre que celui de pouvoir exécuter le programme malicieux.

De l’effet des fautes à leur modélisation

Une fois la perturbation injectée dans le circuit cible, elle produit une faute dans la mi-croarchitecture. On parle alors de l’effet de la faute, et pour le caractériser on utilise souvent les mêmes critères que pour les mécanismes d’injection de fautes : précision spatiale, précision temporelle et permanence. Toutefois, l’effet de la faute peut différer de celui de la perturbation. Par exemple, une perturbation transitoire dans la mémoire peut être capturée par le cache. Pour les cycles suivants, la valeur en mémoire sera correcte, mais la valeur enregistrée dans le cache restera erronée jusqu’à ce que le cache recharge cette valeur depuis la mémoire. Dans ce cas une perturbation transitoire provoque une faute qui dure plusieurs cycles d’horloge.
Pour discuter de l’effet des fautes, il est commun d’utiliser des modèles de faute. Un modèle de faute peut être utilisé aussi bien pour caractériser un mécanisme d’injection de fautes ou pour construire une attaque que pour concevoir et évaluer une contre-mesure. On trouve dans la littérature trois grands niveaux d’abstractions pour les modèles de fautes comme présentés sur la figure 2.1 :
— Microarchitecture, par exemple une corruption de lecture de la mémoire de données ou d’instructions, ou une corruption d’un signal dans un étage du pipeline ;
— Jeu d’instructions, par exemple une corruption d’instruction ou d’un registre ;
— Code source, par exemple la corruption d’une variable de test.
Une abstraction au niveau de la microarchitecture permet de représenter l’effet et la pro-pagation des fautes dans la cible. Cependant, la complexité de ce modèle dépend également de la complexité de la microarchitecture du circuit cible. Plusieurs travaux de caractérisation cherchent à construire des modèles de fautes au niveau de la microarchitecture [Korak et al. 2014 ; Trouchkine et al. 2021]. On trouve aussi des travaux qui utilisent des modèles de la microarchitecture du processeur pour évaluer des contre-mesures [Grycel et al. 2021].
Une abstraction au niveau du jeu d’instructions permet une plus grande expressivité de l’ef-fet de la faute au niveau logiciel. Il permet de représenter un grand nombre de fautes proches du matériel, notamment sur l’encodage des instructions sans se préoccuper des détails d’implé-mentation. On retrouve au niveau du jeu d’instructions les modèles de type saut d’instructions, rejeu d’instructions, corruption de registre ou encore inversion de test. Les modèles au niveau du jeu d’instructions sont aussi bien utilisés pour de la caractérisation [Moro et al. 2013 ; Proy et al. 2019 ; Menu et al. 2020], que pour la construction de contre-mesures [Moro 2014 ; Barry 2017 ; Proy et al. 2017].
Enfin, il est possible de modéliser l’effet des fautes au niveau du code source. Cela permet de mieux identifier les effets sur le programme. Un tel niveau est donc souvent utilisé pour construire des attaques [Boneh et al. 1997 ; Biham et al. 1997 ; Piret et al. 2003]. On trouve aussi quelques travaux qui utilisent un modèle de fautes au niveau du code source, avec par exemple des inversions de condition ou des sauts de lignes de code, pour construire des contre-mesures [Lalande et al. 2014].
Les travaux de Yuce et al. utilisent une méthodologie prenant en compte des aspects de la microarchitecture, comme le pipeline et le chemin critique de chaque étage, pour construire des attaques par injection de fautes [Yuce et al. 2017]. En particulier, ils montrent que certaines contre-mesures logicielles utilisant de la duplication d’instructions sont vulnérables en exploitant la microarchitecture.
Laurent et al. étudient le comportement de la microarchitecture face aux injections de fautes [Laurent et al. 2019]. Ils montrent qu’une analyse au niveau du jeu d’instructions n’est pas suffisante pour comprendre comment un processeur répond à une attaque. Par exemple, certaines fautes dépendent du contexte logiciel notamment pour les mécanismes de forwarding et d’exécution spéculative. En effet, une faute sur le mécanisme de forwarding responsable de la gestion des dépendances de données entre instructions peut apparaitre comme un remplacement d’instruction au niveau du jeu d’instructions. Cependant, cette faute peut avoir différents effets sur le programme en fonction des instructions précédemment exécutées et des suivantes.
Ces travaux montrent l’importance de la prise en compte de la microarchitecture dans l’étude des attaques par injection de fautes. En effet, même si les propriétés d’intégrité du code, du flot de contrôle et des données sont assurées, une faute dans la microarchitecture peut tout aussi bien modifier le comportement du programme. Il est donc important de prendre en compte la microarchitecture lors de la conception de contre-mesure pour assurer l’exécution correcte des instructions.

Exploitations

La finalité d’une attaque informatique est de compromettre une des propriétés de confi-dentialité, d’intégrité ou d’authenticité présentées dans la section 2.2. Pour les attaques par injection de fautes, on distingue les attaques sur la confidentialité qui visent le plus souvent les systèmes cryptographiques et les attaques génériques qui visent l’intégrité ou l’authenticité.
Attaques cryptographiques
En 1997, Boneh et al. présentent l’attaque connue sous le nom de Bellcore, considérée aujourd’hui comme la première attaque par injection de fautes [Boneh et al. 1997]. Cette attaque vise l’implémentation de l’algorithme RSA utilisant le théorème des restes chinois. L’objectif est de fauter le résultat de l’algorithme pour le factoriser avec un résultat non fauté. Il est alors possible de retrouver la clé privée utilisée sans besoin de résoudre le problème de factorisation des grands nombres. Ce type d’attaque est appelée Differential Fault Analysis (DFA) par Biham et al. qui s’intéressent à l’algorithme DES [Biham et al. 1997]. Piret et al. proposent une attaque sur l’algorithme AES [Piret et al. 2003]. Les attaques DFA exploitent la différence entre un calcul fauté et un calcul correct. D’autres variantes exploitent des collisions ou l’absence d’erreur dans le résultat pour gagner en connaissance sur le secret. Clavier présentent plusieurs techniques de cryptanalyse basées sur les injections de fautes pour les algorithmes de chiffrement par bloc [Clavier 2012] alors que Berzati et al. se concentrent sur l’algorithme RSA [Berzati et al. 2012]. On peut aussi citer les attaques de type Statistical Inefective Fault Analysis qui consistent à injecter une faute qui ne modifie pas le résultat du calcul final [Dobraunig et al. 2018].
Il est important de noter que les protections contre les attaques par canaux auxiliaires telles que le masquage ne permettent pas de se protéger contre les attaques par injection de fautes [Amiel et al. 2006 ; Pan et al. 2019].
Enfin, les attaques par injection de fautes peuvent aussi être un outil pour la rétro ingénie-rie [Biham et al. 1997 ; San Pedro et al. 2011].
Attaques génériques
Bien qu’une grande partie de la sécurité informatique repose sur la cryptographie, il est aussi nécessaire de construire des protocoles pour mettre en place les différents dispositifs de sécurité. Il est parfois plus simple pour un attaquant de cibler la logique du protocole ou de l’implémentation de la fonction cryptographique que la fonction cryptographique elle-même. Souvent les attaques informatiques essaient de corrompre l’intégrité du code ou du flot de contrôle.
Une cible courante des attaques par injection de fautes sont les systèmes de démarrage sécu-risé. Timmers et al. présentent une attaque permettant d’exécuter du code arbitrairement lors du démarrage sécurisé d’un SoC ARM en utilisant une perturbation sur l’alimentation [Tim-mers et al. 2016]. Cette attaque transforme une instruction de chargement depuis la mémoire en une instruction de branchement qui saute dans une section de la mémoire contrôlée par l’at-taquant. Vasselle et al. attaquent le démarrage sécurisé d’un smartphone Android avec des perturbations laser [Vasselle et al. 2017]. Plutôt que d’attaquer directement le programme, ils modifient le contenu du registre de status (CPSR) qui contient entre autres le résultat d’une comparaison et le registre de configuration du niveau de privilège (CSR). Van den Herrewe-gen et al. attaquent le démarrage sécurisé d’un microcontrôleur en combinant une double faute sur l’alimentation et la technique du Return Oriented Programming [Van den Herrewegen et al. 2020]. On retrouve même sur des forums l’utilisation d’injection de fautes pour contourner le démarrage sécurisé de la console de jeux vidéo Xbox 360 [GliGli et al. 2012]. Cela confirme que les attaques par injection de fautes sont une menace réelle et que le profil des attaquants n’est pas limité à des experts dans des laboratoires spécialisés.
Ces attaques peuvent être adaptées à un grand nombre de cas d’utilisation, par exemple l’élévation de privilège sur un système Linux en utilisant cette fois une corruption des don-nées [Seaborn et al. 2015]. On observe qu’il existe plusieurs attaques ciblant soit l’intégrité du code et du flot de contrôle, soit l’intégrité des données. Il est donc important d’assurer toutes ces propriétés à la fois pour couvrir tous les chemins d’attaques possibles.

Protections

Dans la section précédente, nous avons montré le besoin de se protéger contre les attaques par injection de fautes. Il est possible d’implémenter une protection au niveau analogique dans le circuit, au niveau logique ou au niveau du logiciel.
Plusieurs travaux proposent des capteurs pour détecter les perturbations physiques dans le circuit. Ainsi, il est possible d’utiliser des détecteurs de glitches basés sur la mesure de la période d’horloge [Endo et al. 2012], des détecteurs d’impulsions électromagnétiques [El-Baze et al. 2016] ou des détecteurs d’illumination laser [He et al. 2016]. Une autre solution est de placer le circuit dans un boîtier robuste aux attaques par injection de fautes [Sohier et al. 2022]. D’autres travaux proposent des modifications lors de la conception des circuits pour les rendre plus robustes face aux fautes [Karaklajić et al. 2013]. L’intégrité des données et du code en mémoire peut être assurée par des codes détecteurs d’erreur tels que les CRC. Les unités de calcul sont souvent protégées en utilisant le principe de redondance. Par exemple, il est possible de réaliser deux fois le même calcul en parallèle et de comparer les deux résultats. Néanmoins, cette solution double la surface du circuit nécessaire pour réaliser le calcul. Une autre solution est d’utiliser de la redondance temporelle en calculant plusieurs fois le même résultat avec la même unité logique. Dans cette section, nous nous intéressons aux protections placées aux niveaux logiciel et mixte, c’est-à-dire avec une modification conjointe du matériel et du logiciel.

Protections logicielles

Afin de protéger un programme contre les attaques par injection de fautes, il est possible de modifier uniquement le logiciel. Il existe plusieurs familles de contre-mesures purement logi-cielles.
Une partie des contre-mesures logicielles se concentre sur la détection de faute sur le flot de contrôle. Plusieurs contre-mesures utilisent des compteurs pour suivre l’évolution du flot de contrôle lors de l’exécution. Les compteurs sont initialisés avec une valeur unique avant le début de la séquence d’instructions à vérifier. Ils sont ensuite incrémentés en fonction du chemin de flot de contrôle pris durant l’exécution. En fin de séquence, les compteurs sont comparés à des valeurs de référence. Une divergence entre la valeur du compteur et la valeur de référence permet de détecter une faute sur le flot de contrôle. Lalande et al. proposent d’incrémenter le compteur entre chaque ligne du code source à protéger [Lalande et al. 2014]. Barry proposent plutôt d’incrémenter le compteur entre chaque instruction assembleur. Bien que très coûteuse, car doublant la taille de code, cette technique vérifie également que le bon nombre d’instructions a été exécuté dans la séquence protégée [Barry 2017].
Quelques contre-mesures proposent des solutions pour protéger les données liées au flot de contrôle. Schilling et al. proposent de protéger le calcul des branchements condition-nels [Schilling et al. 2018]. Les variables entières utilisées pour le calcul des conditions sont encodées avec un AN-code. Les AN-codes sont des codes détecteurs d’erreurs linéaires par rapport à l’addition qui est l’opération utilisée pour le calcul de condition. Le résultat de la comparaison est obtenu par le calcul du modulo de la soustraction entre les deux opérandes à comparer. Les auteurs discutent également de la combinaison du résultat de la comparaison avec une contre-mesure protégeant l’intégrité du flot de contrôle.
Proy et al. se concentrent sur la protection automatique des boucles en dupliquant les instructions et les données liées au flot de contrôle des boucles lors de la compilation [Proy et al. 2017]. Ils proposent aussi de sécuriser les appels de fonction en utilisant des variables de suivies lors des appels et des retours de fonction. Il est ainsi possible de vérifier le nombre de fois qu’une fonction a été appelée dans un programme.
SWIFT est une protection logicielle contre les Single Event Upset [Reis et al. 2005]. Cette protection fait l’hypothèse que la mémoire est déjà protégée par un mécanisme de code correc-teur d’erreurs. Elle combine un mécanisme de redondance basé sur la duplication d’instruction et un mécanisme de flot de contrôle qui associe une signature unique à chaque bloc de base. Ces travaux présentent également un ensemble d’optimisations permettant de réduire le coût de la contre-mesure. En comparaison avec les autres protections, SWIFT assure en même temps l’intégrité du code, du flot de contrôle et des données. De plus, les transformations de code pré-sentées dans ces travaux peuvent être implémentées dans un compilateur pour une application automatique de la contre-mesure.
Lorsque la disponibilité devient une propriété essentielle d’un système, il n’est plus suffi-sant de détecter les fautes. Plusieurs travaux proposent des solutions de tolérance aux fautes qui permettent d’assurer le bon comportement du programme même en présence de fautes. Ces contre-mesures sont souvent construites pour résister à un modèle de faute de type saut d’instruction. Une technique classique est d’utiliser de la redondance temporelle ou spatiale. Barenghi et al. proposent de tripler les instructions en stockant les résultats dans des registres différents [Barenghi et al. 2010]. Il est alors possible de détecter et de corriger une faute en conservant le résultat majoritaire. À cela, Barenghi et al. proposent aussi d’utiliser un bit de parité pour valider l’intégrité des données chargées depuis la mémoire.
Moro propose une contre-mesure de tolérance au saut d’une instruction [Moro 2014]. Pour cela, les instructions doivent être idempotentes ou transformées dans une forme idempo-tente équivalente, c’est-à-dire que les instructions produisent le même résultat qu’elles soient exécutées une ou plusieurs fois. Après cette transformation, les instructions sont dupliquées permettant ainsi de résister à une attaque qui sauterait une instruction. Barry montre qu’il est possible de déployer cette contre-mesure automatiquement lors de la compilation [Barry 2017]. De plus, il propose une généralisation de ce mécanisme avec de la n-plication d’instruction pour résister à un saut de n instructions.
Plusieurs travaux proposent des contre-mesures spécifiques à un algorithme. Patrick et al. proposent une méthode de détection des fautes pour les algorithmes de chiffrement par bloc à l’aide de redondance intra-instruction [Patrick et al. 2017]. L’idée repose sur une représentation par découpage binaire (bit-slicing) de l’état de l’algorithme. L’état pouvant être représenté comme une matrice de N blocs de taille M, chaque bloc étant traité individuellement. Le découpage binaire consiste alors à utiliser la matrice transposée. Une nouvelle méthode de calcul est nécessaire afin de manipuler les données ainsi éclatées. Afin de renforcer la sécurité, plusieurs états redondants peuvent être entrelacés dans la matrice avec un état de référence. Cela permet de vérifier la cohérence entre les états redondants ainsi que de valider le bon calcul de l’état de référence.
Lac et al. présentent une contre-mesure utilisant aussi du découpage binaire [Lac et al. 2018]. Cette fois, une implémentation sur 8-bit d’un algorithme est parallélisé sur une architec-ture 32 bits. Il est donc possible de traiter quatre variables en parallèle grâce à des instructions SIMD. La donnée à manipuler est dupliquée et intercalée avec une donnée de référence. Il est alors possible de vérifier la cohérence entre chaque donnée de 8 bits présente deux fois dans chaque mot de 32 bits. De plus, la présence d’une donnée de référence permet de valider que le calcul s’est bien déroulé dans son intégralité.
Enfin, une dernière catégorie de contre-mesures logicielles, dites infectives, protègent spécifi-quement les algorithmes cryptographiques. Elles ont vu le jour pour répondre à la problématique de single point of failure des techniques de détection souvent basées sur un test (représenté par un unique bit). Le concept est de propager un calcul fauté pour infecter le résultat final afin que celui-ci soit indistinguable d’une valeur aléatoire indépendante des secrets manipulés dans l’algorithme. Afin de détecter une erreur, l’algorithme à sécuriser est calculé deux fois. La diffé-rence entre les deux algorithmes est calculée à l’aide d’un OU exclusif puis elle est transformée par une fonction non linéaire avant d’être recombinée avec l’état de l’algorithme à protéger. Ces contre-mesures sont limitées à des applications cryptographiques et ne protègent que contre certains types d’attaquant. En effet, une double faute permet de déjouer ces contre-mesures en attaquant les deux calculs redondants. De plus les techniques de type SIFA exploitent une faute qui ne modifie pas le résultat et sont donc insensibles à ces contre-mesures [Baksi et al. 2019].
Bien que les contre-mesures logicielles permettent de répondre à certaines attaques par in-jection de fautes, les propriétés de sécurité qu’elles assurent sont limitées. Les contre-mesures logicielles pures ne permettent pas de garantir l’intégrité du code pendant l’exécution. Au mieux, il est possible de vérifier l’intégrité du code lors du chargement de celui-ci en mémoire par un système de démarrage sécurisé. De plus, le coût sur les performances du système à protéger est important en ce qui concerne la mémoire nécessaire et le temps d’exécution. Le principe de duplication, par exemple, double à la fois l’espace mémoire nécessaire et le temps d’exécution pour les sections protégées. Pour toutes ces raisons, de plus en plus de travaux étudient les contre-mesures mixtes matérielles et logicielles. Pour autant, les contre-mesures logicielles restent intéressantes lorsque le système à protéger est déjà déployé et qu’il n’est plus possible de modifier le matériel, car elles sont plus fléxibles.

Protections mixtes matérielles et logicielles

Combiner les approches matérielles et logicielles permet de concevoir des contre-mesures dont le coût en temps d’exécution et en taille mémoire est plus faible que pour les contre-mesures logicielles pures. De plus, l’intégration des contre-mesures directement dans le matériel peut augmenter le niveau de sécurité apporté notamment en utilisant des informations qui ne sont pas accessibles facilement au niveau logiciel, par exemple la valeur de l’instruction effecti-vement chargée dans le pipeline. On distingue les contre-mesures qui ajoutent un co-processeur au support matériel existant et les contre-mesures qui modifient directement le support ma-tériel en s’intégrant dans la microarchitecture du processeur. Cette section se concentre plus particulièrement sur les contre-mesures assurant les propriétés d’intégrité du code et du flot de contrôle, car cette thèse vise à protéger le code et son exécution.
Pour assurer l’intégrité du code, une technique commune aux contre-mesures mixtes maté-rielles logicielles est le calcul d’une signature représentant les séquences d’instructions valides selon le programme qui s’exécute. Le calcul de la signature lors de l’exécution est réalisé par le matériel, ce qui permet de limiter la dégradation des performances du programme en temps d’exécution au prix d’une augmentation de la surface du circuit. Il est toutefois nécessaire d’ajouter des métadonnées et du code au programme à protéger afin de comparer les signatures calculées lors de l’exécution à des signatures de référence préalablement calculées sur le code binaire du programme. Plusieurs contre-mesures reposent sur un tel calcul de signature sur des blocs d’instructions et vérifient ainsi l’intégrité du code par partie [Arora et al. 2006 ; Dan-ger et al. 2020 ; Clercq et al. 2016]. Ces mécanismes de signature peuvent être combinés avec des mécanismes d’intégrité du flot de contrôle. Par exemple, une machine à états permet de valider l’enchainement des séquences d’instructions à partir de métadonnées embarquées dans le programme. Arora et al. utilisent plusieurs machines à états placées dans un co-processeur pour valider le graphe d’appel et l’enchainement des instructions à l’intérieur d’une fonction [Arora et al. 2006]. Danger et al. utilisent un co-processeur qui lit une mémoire dédiée dans laquelle sont stockées entre autres la signature d’intégrité du code ainsi que la liste des adresses des blocs successeurs valides [Danger et al. 2020].
La General Path Signature Analysis (GPSA), une technique originellement proposée par Wilken et al. dans le cadre de la sûreté de fonctionnement en présence de Single Event Upset, permet d’assurer l’intégrité du code et l’intégrité du flot de contrôle avec le calcul d’une seule signature pour les deux propriétés de sécurité [Wilken et al. 1990]. Werner et al. proposent d’utiliser ce mécanisme dans le cadre des attaques par injection de fautes [Werner et al. 2015]. L’idée est de calculer une signature à partir des instructions d’un bloc de base comme pour les mécanismes d’intégrité du code. La signature est chaînée entre chaque bloc de base, ce qui permet également d’assurer l’intégrité du flot de contrôle. Pour cela, il est nécessaire d’instrumenter le code pour garantir des invariants dans le flot de contrôle. Cette technique est présentée en détail dans le chapitre 3.
Les contre-mesures [Arora et al. 2006 ; Werner et al. 2015 ; Danger et al. 2020] utilisent toutes les trois des co-processeurs pour le calcul de la signature. Cette technique a l’avantage d’être applicable sans modifier la microarchitecture du processeur à protéger.
Il existe aussi des contre-mesures qui s’intègrent dans la microarchitecture du processeur. SOFIA est une architecture de processeur sécurisé qui assure la confidentialité et l’authenticité du code ainsi que l’intégrité flot de contrôle. La confidentialité du code et l’intégrité du flot de contrôle sont assurées par un même mécanisme. Le code est chiffré en mémoire et SOFIA repose sur une dépendance entre le déchiffrement des instructions et le flot de contrôle. Ainsi, lors du chargement d’une instruction dans le processeur, le déchiffrement dépend de l’adresse de l’instruction courante ainsi que de l’adresse de l’instruction précédente. Une modification du code ou du flot de contrôle provoque donc un mauvais déchiffrement de l’instruction. Un second mécanisme fonctionnant sur le principe de signature, présenté précédemment, assure l’authenticité (et donc l’intégrité aussi) du code. Pour l’authenticité du code, SOFIA utilise une primitive cryptographique pour construire un Message Authentication Code (MAC). Cette solution permet de détecter un éventuel déchiffrement fauté et donc toutes les fautes ciblant le code ou le flot de contrôle. Bien que très intéressante pour les propriétés de sécurité assurées, cette contre-mesure possède un coût important. D’une part, il est nécessaire d’utiliser deux primitives cryptographiques, une pour le déchiffrement et une pour le calcul du MAC, ce qui entraine une augmentation de la surface du circuit de 28,2 %. D’autre part, les modifications nécessaires au niveau logiciel provoquent un surcoût de 140 % de la taille de code.
Werner et al. proposent une amélioration de SOFIA qui combine GPSA et la construction de fonctions de chiffrement authentifié à partir d’une fonction éponge [Werner et al. 2018]. Cette fois, une unique fonction de chiffrement authentifié assure l’authenticité du code et l’in-tégrité du flot de contrôle, ainsi que la confidentialité du code. Un mécanisme similaire à GPSA permet de créer des invariants dans le flot de contrôle sur lesquels repose le bon déchiffrement des instructions. De plus, il n’est pas nécessaire de vérifier une signature lors de l’exécution, puisque le mauvais déchiffrement d’une instruction provoque une réaction en chaîne détectée par l’apparition d’instructions invalides (n’appartenant pas au jeu d’instructions) dans le pro-cesseur. Toutefois, cela suppose que le jeu d’instructions n’est pas dense (il existe des encodages d’instruction invalides). Cette technique nécessite ainsi des métadonnées que pour les invariants de flot de contrôle, ce qui la rend particulièrement légère en comparaison des autres contre-mesures protégeant l’intégrité du code et du flot de contrôle. Cette contre-mesure entraine un surcoût moyen en taille de code et en temps d’exécution de 19.8% et 9.1% respectivement et une augmentation de la surface du circuit de 47 %.

Le rapport de stage ou le pfe est un document d’analyse, de synthèse et d’évaluation de votre apprentissage, c’est pour cela chatpfe.com propose le téléchargement des modèles complet de projet de fin d’étude, rapport de stage, mémoire, pfe, thèse, pour connaître la méthodologie à avoir et savoir comment construire les parties d’un projet de fin d’étude.

Table des matières

Résumé
Table des matières
Table des figures
Liste des tableaux
Listings
1 Introduction 
1.1 Introduction
1.2 Problématique
1.3 Contributions
1.4 Plan du manuscrit
2 Attaques par injection de fautes 
2.1 Introduction
2.2 Propriétés de sécurité
2.2.1 Sécurité des données
2.2.2 Sécurité du code
2.2.3 Sécurité du flot de contrôle
2.3 Attaques par injection de fautes
2.3.1 Mécanismes d’injection de fautes
2.3.2 De l’effet des fautes à leur modélisation
2.3.3 Exploitations
2.4 Protections
2.4.1 Protections logicielles
2.4.2 Protections mixtes matérielles et logicielles
2.5 Conclusion
3 Microarchitecture sécurisée 
3.1 Introduction
3.2 Motivations
3.2.1 Modèle de menace
3.2.2 Type de processeur visé
3.3 Signature pour l’intégrité du code et du flot de contrôle
3.4 SCI-FI : Intégrité d’exécution
3.4.1 Vue d’ensemble
3.4.2 Pipeline state
3.4.3 Module CCFI : intégrité du code et du flot de contrôle
3.4.4 Module CSI : intégrité des signaux de contrôle
3.4.5 Sécurisation des branchements indirects
3.4.6 Prédiction de branchement
3.4.7 Sécurisation du mécanisme d’interruptions
3.5 Support pour des microarchitectures plus complexes
3.6 Discussion et positionnement
3.7 Conclusion
4 Implémentations et évaluations 
4.1 Introduction
4.2 Processeur cible : CV32E40P
4.3 Extensions du CV32E40P avec SCI-FI
4.3.1 Pipeline state
4.3.2 Module CCFI : Fonctions de signature et de mise à jour
4.3.3 Module CSI
4.3.4 Mécanisme d’alerte
4.3.5 Extension du jeu d’instructions RISC-V pour SCI-FI
4.4 Support logiciel
4.4.1 Extension du back-end RISC-V de LLVM
4.4.2 Générations des dispatchers
4.4.3 Bibliothèque C Newlib
4.4.4 Générations des signatures
4.5 Analyse de sécurité
4.5.1 Vérification du pipeline state
4.5.2 Module d’intégrité du code et du flot de contrôle
4.5.3 Module d’intégrité des signaux de contrôle
4.5.4 Intégrité du flot de contrôle
4.6 Évaluation de l’impact sur les performances
4.6.1 Synthèse ASIC
4.6.2 Environnement d’évaluation logiciel
4.6.3 Évaluation logicielle
4.7 Conclusion
5 Conclusion 
5.1 Conclusion générale
5.2 Perspectives
Publications et communications personnelles
Table des matières
Bibliographie

Télécharger le rapport complet

Télécharger aussi :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *