Vulnérabilités aux injections de NOP sur cartes à puce Introduction

publicité
Vulnérabilités aux injections de NOP sur cartes à puce
Franck De Goër
Tuteur : Marie-Laure Potet
Ensimag - Verimag
[email protected]
16 mai 2013
Introduction
Les cartes à puces se sont généralisées depuis les années 80, et contiennent aujourd’hui des
données sensibles (cartes bancaires, e-passeports, badges d’accès, etc.). Il est donc primordial qu’elles
présentent une robustesse maximale à tout type d’attaque, que ce soit matériel ou logiciel. Par
exemple, il est préférable qu’une personne malveillante ne soit pas en mesure d’utiliser une carte
bleue sans connaître son code PIN. À ce titre, de même que l’on teste les serrures de coffre-fort
d’une part, et la résistance de la porte dudit coffre d’autre part, la sécurité du code ainsi que la
protection matérielle de la carte doivent être éprouvées.
Nous nous intéressons dans ce papier à l’évaluation de la robustesse d’un code s’exécutant sur
carte à puce, face aux attaques classiques du domaine (ces attaques sont introduites en section 1, et
largement expliquées dans le papier de Bar-El, Choukri, Naccache, Tunstall et Whelan [1]). Nous
nous intéresserons particulièrement à la possibilité de détecter au niveau source des vulnérabilités
pouvant être exploitées par un attaquant durant l’exécution du programme.
Les autorités de certification de cartes à puce (CESTI 1 ) ont besoin de vérifier la robustesse d’un
code avant de le valider. Il est donc nécessaire de développer des outils d’analyse qui vont dans
ce sens, c’est-à-dire permettant la détection automatisée de vulnérabilités. À l’heure actuelle, leur
protocole de validation, détaillé dans la partie 2.1, est basé sur des tests exhaustifs en brute force,
indépendamment de la sémantique du code testé (nous y reviendrons dans la section 2.1). L’approche
Lazart (introduite dans le papier de Potet, Mounier et Vivien [2], et sur laquelle nous revenons en
section 2.2), quant à elle, bien que non exhaustive, permet de détecter les vulnérabilités au niveau
du code source. L’avantage de cette approche est qu’elle reste praticable même lorsque le modèle de
faute est complexifié (e.g. l’attaquant peut introduire un nombre quelconque de fautes). Elle est
complémentaire à l’approche du CESTI, en ce sens qu’elle permet de mettre en évidence des points
sensibles du programme qu’il est particulièrement intéressant de tester. À l’heure actuelle, Lazart ne
détecte que les vulnérabilités liées aux inversions de test, comme détaillé section 2.2. L’objectif de ce
papier est d’étendre cette approche aux attaques NOP. Ces attaques, décrites plus précisément en section 3, consistent à empêcher l’exécution de certaines instructions d’un programme lors de l’exécution.
Dans la section 1, nous décrirons le principe d’une attaque laser sur carte à puce et ses conséquences. En 2, nous verrons les différentes approches existantes pour détecter des vulnérabilités
aux attaques laser. Les sections 3 et 4 présentent notre contribution au domaine : en 3, nous ferons
un descriptif détaillé du type d’attaque étudié et ses conséquences ; et en 4 nous présenterons une
implémentation s’insérant dans l’approche Lazart pour détecter ce type d’attaques.
1. Centre d’Évaluation de la Sécurité des Technologies de l’Information : organisme chargé de valider la robustesse
des codes exécutés sur carte avant commercialisation
1
Franck De Goër
Injections de NOP sur cartes à puce
Table des matières
1 Principe d’une attaque laser
3
2 Détections de vulnérabilité aux injections
2.1 Au niveau binaire : brute-tests . . . . . .
2.2 Au niveau source : Lazart . . . . . . . . .
2.2.1 Coloration du graphe de flot . . .
2.2.2 Génération de mutants . . . . . . .
2.2.3 Tests symboliques sur les mutants
2.3 Sensibilité à l’introduction de NOP . . . . .
2.4 Pertinence des NOP . . . . . . . . . . . . .
de
. .
. .
. .
. .
. .
. .
. .
faute
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
: approches existantes
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
.
.
.
.
.
.
.
4
4
4
4
6
6
7
7
3 Attaques de type JUMP ← NOP
3.1 Boucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Structure if|elsif|else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Retours de fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
8
9
10
4 Implémentation et résultats
4.1 Noppy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.1 Inclusion dans l’approche Lazart . . . . . . . . . . . . . . . . .
4.1.2 Algorithme de base d’ajout d’arcs . . . . . . . . . . . . . . . .
4.2 Sur un exemple : verify . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2.1 Mutation et coloration du CFG . . . . . . . . . . . . . . . . . .
4.2.2 Production de codes mutants et analyse dynamique sur le CFG
11
11
11
12
14
15
15
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
modifié
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
A Efficacité des détections au niveau source et au niveau binaire
21
B Deux conséquences differentes à une attaque de type NOP sur une boucle while
21
C Mutant de verify pour analyse dynamique
22
2
Franck De Goër
1
Injections de NOP sur cartes à puce
Principe d’une attaque laser
Nous présentons dans cette section le principe général d’une attaque classique sur carte à puce :
l’attaque au laser par injection de faute. L’idée est de modifier une valeur de la mémoire afin de modifier le comportement à l’exécution du programme à attaquer, et ce afin d’obtenir des informations ou
d’outrepasser des authentifications. C’est une attaque matérielle dans le sens où elle est insérée physiquement dans le système, mais sa répercution est directement liée au logiciel qui s’exécute sur la carte.
Dans les papiers de Pellegrini, Bertacco, Austin [3] et de Barenghi, Breveglieri, Koren, Pelosi,
Regazzoni [4] sont décrites des attaques respectivement sur RSA et AES s’exécutant sur carte à
puce. Grâce à des injections de faute dans les calculs, il est possible de récupérer la clé privée (ou la
clé symétrique dans le cas de l’AES) embarquée dans la carte. Dans ces deux cas, il s’agit d’injection
d’erreurs de calculs durant le chiffrement d’un même message afin de récupérer des informations
sur la clef. La thèse de Maria Christofi [5] traite dans le chapitre 9 des analyses de vulnérabilité
d’implémentations cryptographiques face aux injections de faute.
Dans ce papier, nous nous intéresserons plutôt à l’injection de faute amenant non pas à des
erreurs de calculs mais à une modification du chemin d’exécution du code sur la carte. Ci-suit un
exemple illustrant le principe de ce type d’injection de faute.
1
int triesLeft = 2;
2
3
4
5
6
7
int verify(char buffer[], int ofs, int len) {
int i;
/* No comparison if PIN is blocked */
if(triesLeft < 0)
return EXIT_FAILURE;
8
/* Main Comparison */
for (i = 0; i < len; i++) {
if(buffer[ofs + i] != pin[i]) {
triesLeft--;
authenticated = 0;
return EXIT_FAILURE;
}
}
9
10
11
12
13
14
15
16
17
/* Comparison is successful */
triesLeft = maxTries;
authenticated = 1;
return EXIT_SUCCESS;
18
19
20
21
22
}
Figure 1 – Code de vérification du code PIN
Ce code est chargé de vérifier le code PIN entré par l’utilisateur de la carte. Nous voyons ici
que si l’utilisateur entre trois codes PIN erronés, la fonction renvoie EXIT_FAILURE, ce qui a pour
effet de déclencher une contre-mesure (par exemple un blocage de la carte). Dans ce cas précis,
l’objectif de l’attaquant est de sortir avec la valeur EXIT_SUCCESS, ou encore de ne pas sortir avec
la valeur EXIT_FAILURE qui peut entraîner le blocage de la carte. Pour ce faire, il peut par exemple
injecter une faute consistant à forcer le résultat du test de la ligne 6 à FALSE afin de disposer
de plus de trois essais. Dans la même idée, l’attaquant peut empêcher l’exécution de l’instruction triesLeft–– pour ne pas voir le nombre d’essais diminuer. C’est le principe d’injection de
3
Franck De Goër
Injections de NOP sur cartes à puce
faute : le comportement à l’exécution peut être modifié pour amener le système dans un état imprévu.
L’injection de faute peut se faire de plusieurs manières, mais la principale utilisée sur carte à
puce est l’attaque au laser : l’éclairage au laser d’un transistor l’amène régulièrement à introduire
une erreur de valeur (chaque attaque de ce genre a un taux de réussite d’environ 20%). Il est donc
possible de changer la valeur de tests, de modifier des instructions ou encore de modifier des adresses
de saut. L’attaque qui nous intéresse particulièrement dans la suite est l’introduction de NOP à la
place d’autres instructions, et notamment à la place de sauts, conditionnels ou non. L’introduction
d’un NOP consiste à remplacer une instruction assembleur (par exemple BRA 0x5000 ou un JCC,
dénotant un jump conditionnel générique 2 ) en instruction NOP qui est l’instruction null : le saut est
donc remplacé par une instruction vide, et le programme continue donc sans avoir effectué le BRA.
Dans la section qui suit, nous décrivons les approches existantes pour détecter des vulnérabilités
aux injections de faute à différents niveaux (binaire, source, etc.).
2
Détections de vulnérabilité aux injections de faute : approches existantes
Il existe plusieurs approches possibles dans le but de détecter des vulnérabilités aux injections de
faute sur un exécutable.
2.1
Au niveau binaire : brute-tests
La première solution (utilisée notamment par le CESTI) est une méthode d’injection à l’aveugle.
À partir d’un fichier binaire, des mutants 3 sont produits, correspondant chacun à l’exécutable
original dont un octet a été remplacé par 0x00 ou 0xFF (simulation d’une injection de faute, cf
section 2.4). Chaque mutant est exécuté. La plupart du temps, la modification d’un octet amène
à un exécutable qui ne s’exécute pas correctement, mais parfois le système est amené dans un
état imprévu (par exemple EXIT_SUCCESS alors que le PIN n’a pas été rentré). Pour certifier un
programme, toutes les injections de faute possibles sont testées et ne doivent pas aboutir à une
vulnérabilité. L’exhaustivité de cette méthode repose sur l’hypothèse que l’attaquant n’est en mesure
de faire qu’une unique injection de faute. Si l’on considère possible la double (voire triple) injection
de faute, le nombre de cas à couvrir devient trop important.
2.2
Au niveau source : Lazart
L’approche mise en place à Verimag n’a pas le même point de départ : à partir du graphe de
flot 4 (CFG) d’un programme, le but est de déterminer en combien d’injections possibles l’attaquant
peut arriver à une situation compromettant la sécurité du système. l’approche est découpée en trois
grandes étapes (correspondant aux trois blocs de la figure 2).
2.2.1
Coloration du graphe de flot
À partir du code dont on veut évaluer la robustesse, le graphe de flot est généré (à l’aide d’une
librairie llvm). Ensuite, étant donné un noeud du graphe dit critique (noeud à atteindre ou à éviter
2. Par exemple JEQ ou JNE
3. Un mutant correspond à une version modifiée du binaire original
4. Un graphe de flot est un graphe décrivant les chemins possibles que peut prendre une exécution. Chaque noeud
correspond à un bloc d’instructions séquentielles, et chaque arc correspond à un saut (conditionnel ou inconditionnel)
4
Franck De Goër
Injections de NOP sur cartes à puce
appli.ll
CFG
coloring
mutation points
mutation
generation
appli.ll
attack objectives
mutant.ll
KLEE
test directives
attack paths
inconclusive
robustness "proof"
Figure 2 – Schéma de l’approche Lazart
pour l’attaquant, e.g. EXIT_SUCCESS ou EXIT_FAILURE) l’outil Lazart colorie le CFG, en mettant
en évidence les noeuds vulnérables à une injection de faute. On fournit en entrée du programme de
colorisation un noeud à atteindre (par exemple EXIT_SUCCESS). En sortie, les noeuds conduisant
inévitablement vers le noeud à atteindre sont en vert, les noeuds ne pouvant plus conduire au but
sont en rouge. Les noeuds pouvant amener soit dans le rouge soit dans le vert (selon le résultat
des tests) sont en jaune, ou en orange s’ils ont un fils direct qui est rouge. La figure 3 illustre sur
l’exemple du PIN la coloration d’un CFG. Dans ce cas, le but à atteindre est le noeud SUCCESS. On
voit qu’une fois un noeud FAILURE atteint, il n’est plus possible d’atteindre le noeud SUCCESS, c’est
pourquoi ces noeuds ainsi que leurs fils sont rouges. L’algorithme de coloration de graphe a été écrit
et implémenté par J. Vivien [6].
entry:
triesLeft < 0?
T
F
bb1
FAILURE
bb5:
T
F
bb2:
T
SUCCESS
F
FAILURE
bb4:
bb7
return:
CFG de la fonction 'verify' colorié (objectif : atteindre SUCCESS)
Figure 3 – Exemple de coloration d’un CFG par Lazart
5
Franck De Goër
2.2.2
Injections de NOP sur cartes à puce
Génération de mutants
Une fois les noeuds vulnérables détectés, des mutants correspondant à des fautes injectées sont
produits. En particulier, des compteurs sont introduits afin de compter le nombre de conditions
forcées (et donc le nombre d’attaques à réaliser). Des paramètres sont également ajoutées, afin
d’activer ou non chaque attaque. Ces mutants sont générés au niveau llvm 5 . Les mutants générés
correspondent à des inversions de test, systématiques sur les noeuds oranges et non systématiques
sur les noeuds jaunes.
bb:
T
bb:
T
bb1:
F
bbTI:
F
bb2:
bb1:
(a) A mutation scheme
(b) Mandatory mutation
Figure 4 – Illustration des mutations sur le CFG - Source : [2]
La figure 4(a) représente un test critique (si évalué à false, l’objectif ne sera plus atteignable).
La figure 4(b) illustre un mutant modélisant une attaque non systématique : si le test est évalué à
true, l’exécution se prolonge avec le noeud bb1, et l’attaquant n’a pas besoin d’intervenir pour éviter
le rouge. En revanche, si le test est évalué à false, alors le compteur d’attaques est incrémenté et
on poursuit l’exécution dans bb1 : cela modélise une inversion de test. La figure 5 montre au niveau
C (afin d’être plus lisible) à quoi ressemble un mutant généré à partir du cfg figure 4(a).
1
2
3
4
5
6
7
8
/* Noeud bb */
if (triesLeft > 0) {
/* bb1 */
inst;
} else {
/* bb2 */
return EXIT_FAILURE;
}
1
2
3
4
5
6
7
8
(a) Code initial
/* Noeud bb */
if (triesLeft > 0) {
/* bb1 */
inst;
} else {
/* bb2 */
fault ++;
}
(b) Attaque non systématique
Figure 5 – Illustration de la génération de mutants
2.2.3
Tests symboliques sur les mutants
Ces mutants sont ensuite fournis à Klee qui détermine le nombre d’injections minimum amenant
à atteindre l’état critique. Klee est un outil de génération de cas de tests symboliques par couverture
de (k) chemins. En sortie, Klee fournit les entrées qui permettent d’activer les chemins faisables
(c’est-à-dire les chemins qui amènent à une fin d’exécution et un respect des post-conditions. Ici par
exemple, la valeur de retour doit être EXIT_SUCCESS. On peut aussi ajouter des conditions sur le
5. Le langage llvm est un langage intermédiaire entre le source et le binaire : il offre la modularité nécessaire à la
création automatique de mutants, tout en gardant une syntaxe assez aisément manipulable
6
Franck De Goër
Injections de NOP sur cartes à puce
nombre d’attaques, pour ne retenir que les chemins ne faisant pas intervenir plus de deux attaques 6 .).
Cette approche a l’avantage de détecter rapidement les vulnérabilités aux injections d’un programme, et ce quel que soit le nombre d’injections maximum considéré. Cependant, à l’heure actuelle,
seules les attaques d’inversion de test sont prises en compte.
2.3
Sensibilité à l’introduction de NOP
Nous proposons ici d’étendre l’approche Lazart aux attaques de type JUMP ← NOP 7 plus largement décrites section 3 (l’intérêt d’étudier ce type d’attaques est également décrit en section 3, ainsi
que dans la thèse de Xavier Kauffman-Tourkestansky [7] section 3.4.2).
Notre travail consiste à produire l’ensemble des graphes de flot modifiés correspondant à des
injections de NOP. Par la suite, Lazart nous fournira comme précédemment les noeuds vulnérables aux
attaques, et Klee le nombre d’attzaques réalsables, et pour chacune le nombre minimal d’injections
au laser nécessaires à leur réalisation. Nous présenterons en section 4 Noppy, une extension de Lazart
traitant des injections de NOP.
2.4
Pertinence des NOP
Les valeurs des fautes injectées dépendent beaucoup du composant cible, ainsi que du type de
mémoire embarquée (EEPROM, ROM, RAM). Les fautes sont injectées par laser, en faisant augmenter la
tension locale au niveau d’un transistor, ce qui donne les trois fautes injectables suivantes possibles :
– 0x00 : sur une carte où le 0 est décidé lorsque la tension aux bornes d’un transistor est
au-dessus d’un seuil critique
– 0xFF : à l’inverse, possible sur les cartes où la valeur 1 est décidée lorsque la tension est au
dessus d’un certain seul
– 0xRand : parfois les valeurs stockées en mémoire sont des valeurs chiffrées, qui subissent des
modifications lors de la lecture (déchiffrement). Dans ce cas, en injectant 0x00 ou 0xFF dans
la mémoire, cela correspondra après déchiffrement à une valeur a priori aléatoire
Dans la suite de ce papier, nous ne considérerons que les injections de 0x00 qui correspondent à
l’instruction NOP sur la plupart des architectures embarquées (voir Table 1).
Il est important de noter que cette approche ne correspond pas à toutes les cartes à puces : si
l’instruction NOP n’est pas codée par 0x00 ou si la valeur 0x00 n’est pas une valeur injectable sur la
carte en question, ce modèle n’est plus applicable. L’étude faite reste valide, mais devient caduque,
puisque l’attaquant n’est pas en mesure d’introduire des NOP.
3
Attaques de type JUMP ← NOP
Nous proposons dans cette section de présenter une étude détaillée des effets d’une attaque qui
consiste à remplacer une instruction de type JUMP par une instruction NOP. Une telle attaque est
relativement facile à mettre en oeuvre, étant donné que dans la plupart des langages assembleurs
utilisés sur les systèmes embarqués, l’instruction NOP est codée par un ou plusieurs octets nuls (voir
Table 1).
6. Par exemple klee_assume(fault <= 2);
7. Notation que nous utiliserons dans ce papier, désignant le remplacement d’un JUMP par un NOP
7
Franck De Goër
Injections de NOP sur cartes à puce
CPU Architecture
Intel x86
Intel 8051
ARM
MIPS
PIC
OPCODE
0x90
0x00
0x00000000
0x00000000
0b000000000000
SIZE (Byte)
1
1
4
4
1,5
Table 1 – Caractéristiques de l’instruction NOP en assembleur pour quelques architectures
En pratique, la réalisation d’une attaque de cette forme correspond à la modification de l’instruction exécutée par le processeur au moment où celle-ci est lue en mémoire. Nous ne nous
intéresserons qu’aux attaques qui amènent à une modification structurelle du graphe de flot (c’est-àdire l’ajout ou la suppression de noeuds ou d’arcs) : les attaques d’inversion de test par exemple
ne seront pas détaillées pour ce type d’attaque : cf [2]). Ces attaques en effet ont pour effet de modifier le déroulement de l’exécution (et donc le chemin dans le CFG), mais pas la structure du graphe.
Les structures de code sensibles aux attaques JUMP ← NOP sont les suivantes :
– les boucles while et for : les deux types de boucles se traitent de la même manière, par
conséquent nous ne nous intéresserons qu’aux boucles while
– les structures conditionnelles if|elsif|else
– les structures switch : si, sémantiquement, ces structures sont un cas particulier des structures
conditionnelles, elles sont généralement compilées différement, il serait donc intéressant de
faire une étude approfondie des switch. Ce ne sera néanmoins pas traité dans ce papier
– les retours de fonction (détaillées ci-après)
– les appels de fonction
Nous avons effectué une étude systématique des effets du remplacement d’un JUMP par un NOP sur
les structures conditionnelles if|elsif|else et while. Il est important de noter que les exemples
qui suivent illustrent le principe d’une attaque sur un schéma de compilation particulier. En effet,
si notre travaille se situe au niveau source, il est tout de même dépendant du placement des blocs
d’instructions dans le fichier binaire afin de réaliser les attaques réelles. Par exemple, nous supposons
que le bloc if se situe avant le bloc else dans le binaire (cf section 3.2). Nous avons utilisé le
modèle le plus fréquent (celui utilisé notamment par gcc), mais l’approche est à adapter au schéma
de compilation effectivement utilisé.
3.1
Boucle while
La figure 6 fournit un exemple de boucle while générique en assembleur.
Dans cet exemple, on peut effectuer une attaque de type JUMP ← NOP à deux endroits :
– Ligne 7 : le remplacement de ce jump par une instruction NOP amène à la sortie de boucle.
Dans le cas d’une boucle while censée s’exécuter n fois, l’attaquant peut donc la forcer à
s’exécuter seulement k fois (avec k ∈ 1..n) (en imposant la sortie de boucle après k exécutions).
– Ligne 3 : une attaque ici évite la sortie de boucle. Dans ce cas, on force l’exécution de la boucle
un nombre de fois strictement plus grand que n, mais a priori indéterminé et potentiellement
infini. On détaille en annexe B deux comportements différents obtenus dans ce cas, selon la
condition de sortie de la boucle while (condition ligne 3 de type JNE, ou de type JEQ - cf B).
8
Franck De Goër
1
2
3
while:
[...]
JCC endwhile
Injections de NOP sur cartes à puce
; test de la condition de boucle
; sortie de boucle
4
5
[...]
; instructions
BRA while
; saut au debut de la boucle
6
7
8
9
10
endwhile:
[...]
Figure 6 – Exemple de boucle while en assembleur
La seconde attaque décrite (ligne 3) ne modifie pas la structure du graphe de flot (mais seulement
son parcours lors de l’exécution), elle ne sera donc pas retenue pour la suite. En revanche, la
première (ligne 7) laisse la possibilité de sortir de la boucle sans retester la condition. Cela amène
donc à la modification du graphe de flot comme illustré figure 7 (la ligne ajoutée est en ligne pointillée).
entry
entry
while:
while
T
T
do
F
endwhile
do
return
F
endwhile
return
CFG d'une structure while
CFG d'une structure while {NOP}
Figure 7 – Exemple de modification d’un CFG par un NOP sur une structure while
3.2
Structure if|elsif|else
La compilation d’une structure de type if|elsif|else peut être faite de plusieurs façons différentes. On se propose d’en traiter deux, qui sont les plus courantes. On notera que les deux versions
sont identiquement vulnérables aux attaques JUMP ← NOP.
Schéma de compilation no 1. Au début de chaque bloc, la condition d’exécution du bloc est
testée. Si elle n’est pas vérifiée, l’exécution saute au bloc suivant. Ce schéma de compilation est
illustré par la figure 8.
Ici, l’attaquant peut obtenir deux types de résultat en changeant un saut en NOP :
9
Franck De Goër
1
Injections de NOP sur cartes à puce
if:
[...]
JNE elsif
2
3
; Test de la condition 1
; Si la condition n’est pas verifiee, on saute au second bloc
4
5
6
7
8
9
[...]
BRA endif
elsif:
[...]
JNE else
; Instructions du if
; sortie de la structure
; Test de la condition 2
; Si la condition n’est pas verifiee, on saute au bloc else
10
11
12
13
14
15
16
[...]
BRA endif
else:
[...]
endif:
[...]
; Instructions du elsif
; sortie de la structure
; Instructions du else
Figure 8 – Exemple de structure if|elsif|else en assembleur
– Lignes 3,9 : une attaque sur l’une de ces deux lignes a pour effet de forcer (partiellement)
l’évaluation de la condition. À la ligne 3 par exemple, on empêche quoi qu’il arrive d’exécuter
le bloc if. Ce type d’attaque n’est pas l’objet de cette étude, puisqu’elle ne modifie pas le
graphe de flot mais constitue une inversion partielle de test (se référer à [2] pour les inversions
de test)
– Lignes 6,12 : si l’un de ces branchements est remplacé par NOP, cela permet l’exécution de
deux blocs d’instructions consécutifs : on oblige potentiellement l’exécution du bloc suivant (de
manière certaine si le bloc suivant est le bloc else, sous réserve de l’évaluation de la condition
suivante sinon)
Schéma de compilation no 2. Les tests sont effectués dès le début, et l’exécution saute directement au bloc à exécuter 8 . Ce schéma est illustré par la figure 9. De même que précédemment,
une attaque sur un des sauts suivant l’évaluation d’une condition revient à forcer partiellement le
test, et ne modifie pas le flot de contrôle. En revanche, une attaque sur un saut BRA endif force, de
façon inconditionnelle cette fois-ci (à l’inverse du cas 1), l’exécution de deux blocs consécutifs.
Nous venons de voir qu’il est possible, grâce à une injection de NOP, d’exécuter deux blocs
consécutifs 9 . La figure 10 illustre l’impact d’une telle attaque sur le CFG.
3.3
Retours de fonction
Au niveau C, les retours de fonction ne paraissent pas constituer une cible pour les attaques NOP.
Cependant, si nous analysons au niveau assembleur (code assembleur généré par gcc fourni figure
11), nous pouvons constater que l’instruction return -1 en C est traduite en plusieurs instructions
assembleur, et se fait en plusieurs étapes :
1. Chargement de la valeur de retour (-1 soit 0xffff) dans le registre ax
2. jmp à la fin de la fonction (ici @4F7)
3. récupération de l’adresse de retour depuis la pile vers bp
8. Cette méthode, notamment utilisée par le compilateur Deca de l’équipe GL-24, présente l’avantage de n’effectuer
qu’un unique saut pour atteindre le bloc d’instructions à exécuter, quel qu’il soit.
9. Comprendre par consécutif : consécutif dans le code binaire. L’ordre des blocs dépend du compilateur.
10
Franck De Goër
tests:
[...]
JEQ if
[...]
JEQ elsif
BRA else
if:
[...]
BRA endif
elsif:
[...]
BRA endif
else:
[...]
endif:
[...]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Injections de NOP sur cartes à puce
;
;
;
;
;
Test de la condition 1
Si la condition est verifiee, on saute au bloc if
Test de la condition 2
Si la condition est verifiee, on saute au bloc elsif
Sinon saut au bloc else
; Instructions du if
; sortie de la structure
; Instructions du elsif
; sortie de la structure
; Instructions du else
Figure 9 – Autre schéma de structure if|elsif|else en assembleur
4. ret (correspond à un saut à l’adresse contenue dans bp)
Il est important de noter que si plusieurs instructions return apparaissent à différents endroits
d’une fonction au niveau C, elles correspondront toutes à un saut à l’adresse de fin de fonction (ici
@4F7) au niveau assembleur. Il est donc tout à fait possible de remplacer l’instruction jmp short
0x4f7 par l’instruction nop pour empêcher le retour de fonction, et prolonger l’exécution. Au niveau
du CFG, cela correspond à ajouter un arc comme illustré figure 12.
Dans la section suivante, nous présentons notre proposition d’implémentation pour traiter avec
l’approche Lazart des attaques JUMP ← NOP décrites précédemment.
4
4.1
Implémentation et résultats
Noppy
Noppy est un outil que nous avons développé. Il permet, à partir d’un CFG, de rajouter l’ensemble
des chemins possibles par une injection de NOP. Noppy s’appuie sur le travail réalisé en section 3
pour rajouter des chemins sur les structures conditionnelles (uniquement if|else à ce jour), en
prenant comme hypothèse de schéma de compilation celui utilisé en section 3.2 (c’est-à-dire que le
bloc if se trouve avant le bloc else dans le code binaire). À titre d’exemple, Noppy transforme le
CFG de gauche de la figure 10 en le CFG de droite.
4.1.1
Inclusion dans l’approche Lazart
Il est important de rappeler que Noppy a pour objectif d’enrichir l’approche Lazart décrite
section 2.2, et illustrée figure 2. En effet, l’ajout des chemins correspondant aux NOP dans un CFG
ne devient intéressant que si l’on utilise ce nouveau CFG pour déterminer des attaques possibles.
Noppy s’insère donc dans l’approche Lazart.
L’intervention de Noppy se fait avant la coloration du graphe de flot et la génération de mutants
- cf figure 13. Avant de colorier le graphe, les chemins correspondant à l’attaque de type JUMP
← NOP sont ajoutés. Pour l’outil de coloration, les noeuds modifiés (ceux avec un arc sortant
ajouté par Noppy) sont vus comme des noeuds de test, sur lesquels il est possible de forcer le
résultat (suivant le modèle de l’approche, à savoir les inversions de test). Noppy conserve une trace
des arcs qu’il a ajoutés (sous la forme de couples (N1, N2)), pour la fournir ensuite à l’outil de
11
Franck De Goër
Injections de NOP sur cartes à puce
if:
T
inst:
if:
F
T
elsif:
T
F
inst:
elsif:
F
int:
T
else:
int:
int:
F
else:
int:
return:
return:
CFG d'une structure if|elsif|else
CFG d'une structure if|elsif|else {NOP}
Figure 10 – Exemple de modification du CFG par attaque NOP dans une structure if|elsif|else
1
2
3
4
5
6
7
000004E4
000004E7
000004E8
000004E9
........
000004F7
000004F8
B8FFFF
FF
FF
EB0C
....
5D
C3
mov ax,0xffff
db 0xff
db 0xff
jmp short 0x4f7
[...]
pop bp
ret
Figure 11 – Exemple de code assembleur correspondant à un retour de fonction généré par gcc
génération des mutants. Ce dernier, pour chaque noeud à muter, va d’abord vérifier s’il est le
premier noeud d’un des couples fournis par Noppy. Si c’est le cas, la mutation est effectuée comme
décrit dans la figure 14. Sinon, la génération de mutants est la même que celle décrite en section 2.2.2.
L’ensemble de la suite de l’approche est conservée, à savoir la génération des mutants et la couverture symbolique des chemins (via Klee). De cette manière, il est désormais possible de déterminer
le nombre d’attaques minimum nécessaire pour atteindre un noeud ciblé en considérant les NOP. Un
exemple est développé section 4.2.
4.1.2
Algorithme de base d’ajout d’arcs
En premier lieu, nous décrivons l’algorithme de rajout de chemins dans un CFG qui correspondent
à une attaque par NOP sur une structure if|elsif|else :
G = (V,E): graphe de flot
Pour tout N in V tel que N a au moins deux fils faire:
/* Traitement de deux fils consécutifs */
Pour i allant de 1 à (N.nombreDeFils() - 1) faire :
12
Franck De Goër
Injections de NOP sur cartes à puce
entry:
entry:
T
T
F
bb1
return -1
bb1
while
T
F
bb2
while
return -1
F
return 0
T
F
bb2
bb5
return 0
bb5
return
return
CFG d'une fonction avec return
CFG d'une fonction avec return {NOP}
Figure 12 – Exemple de mutation de CFG d’une fonction contenant un retour
appli.ll
1111
0000
0000
1111
0000
1111
Noppy
CFG
coloring
mutation points
attack objectives
mutant
generation
appli.ll
mutant.ll
symbolic
test case
generator
attack paths
inconclusive
robustness "proof"
Figure 13 – Inclusion de Noppy dans l’approche Lazart
/* Détermination du point de conjonction */
PointDeRencontre = TrouverPointdeRencontre(N.fils[i], N.fils[i+1]);
si (PointDeRencontre == N) :
/* Si le point de rencontre est le point de disjonction
alors on est sur une structure de boucle */
N1 = NoeudPere(N.fils[i], N);
N2 = N.fils[i+1];
/* On prend le dernier noeud du premier bloc */
N1 = PointDeRencontre.pereGauche();
/* Le premier noeud du second bloc */
N2 = N.fils[i+1];
/* Et on les relie */
E.ajouterArc(N1,N2);
fin faire
fin faire
L’idée de l’algorithme est très simple : après chaque disjonction sur le CFG (e.g. if|else ou
encore while|endwhile), il faut ajouter un chemin entre la fin du premier bloc et le début du
13
Franck De Goër
Injections de NOP sur cartes à puce
entry
T
F
if
else
1
2
3
endif
4
5
return
6
CFG d'une structure if|else
7
/* Entry */
if (...) {
inst;
} else {
inst;
}
return;
(1a) CFG du code initial
(1b) Code initial
1
2
3
4
5
6
entry:
T
7
F
8
bb:
bb1:
9
10
11
bb2:
12
13
return:
CFG d'une structure if|else mutée (NOP)
14
15
(2a) CFG du code muté
bool nop_attack;
/* Entry */
if (...) {
inst;
if (nop_attack) {
/* if attack is performed */
/* incrementation of nb of faults */
fault++;
/* execution of else */
goto elseLabel;
}
} else {
elseLabel:
inst;
}
(2b) Code muté : attaque systématique
Figure 14 – Illustration de la génération de mutants pour un NOP
second (comme détaillé en section 3).
Spécification des fonctions utilisées :
– TrouverPointdeRencontre(N1, N2) : retourne le premier noeud commun aux fils de N1 et
N2 (requiert : un tel noeud existe). Ici, c’est le cas puisque N1 et N2 opnt au moins le noeud
return comme fils commun.
– NoeudPere(N1, N2) : retourne le premier noeud N parmi les fils de N1 tel que N2 soit un fils
de N ; c’est à dire le père de N2 qui soit un descendant de N1.
– ajouterArc(N1,N2) : ajoute un arc orienté allant du noeud N1 à N2
4.2
Sur un exemple : verify
Détaillons la démarche Lazart dotée de Noppy sur l’exemple présenté en section 2. Le code C est
rappelé Figure 15. L’objectif de cet exemple est de montrer que la prise en compte de NOP amène à
la détection d’attaques qui ne sont pas réalisables si l’on ne prend en compte que les inversions de test.
14
Franck De Goër
4.2.1
Injections de NOP sur cartes à puce
Mutation et coloration du CFG
La première étape est la génération du graphe de flot (la figure 16 partie gauche). Ensuite, Noppy
se charge de rajouter les arcs correspondant aux NOP possibles. La mutation du graphe de flot est
illustrée figure 16 (partie droite). La figure 17 présente la coloration de deux graphes de flot guidée
par l’objectif d’atteindre le noeud SUCCESS : à gauche la coloration du CFG non muté par Noppy, à
gauche celle du CFG muté par Noppy.
(Rappel des codes de couleur fournis section 2.2.1 : [vert] Noeud amenant de façon certaine à
l’objectif [jaune|orange] Noeud pouvant conduire ou non à l’objectif, selon le résultat de tests [rouge]
Noeud ne pouvant plus amener à l’objectif.)
Note : la flèche allant du noeud FAILURE au noeud bb7 correspond au saut à la fin (unique) de
la fonction dans le code binaire, à l’endroit de l’instruction de retour.
On s’aperçoit ici que la coloration n’est pas la même selon si l’on considère les NOP ou pas. Dans
la version non mutée, les noeuds FAILURE sont rouges par exemple, alors qu’ils sont orange dans la
version mutée. En effet, si elles sont prises en compte, les attaques NOP permettent d’atteindre le
noeud SUCCESS après avoir atteint un noeud FAILURE, en remplaçant le saut à la fin de la fonction
par un NOP.
4.2.2
Production de codes mutants et analyse dynamique sur le CFG modifié
La figure 15(b) fournit le code du mutant généré à partir du CFG modifié par Noppy (le mutant
est fourni en C pour être plus lisible, mais il est en réalité produit au format llvm). Dans ce mutant
n’apparaissent que les mutations correspondant à des NOP. Les inversions de test ne sont pas montrées.
Nous donnons en annexe C le code C du driver de test. Nous pouvons reconnaître les trois NOP
possibles dans les trois lignes klee_make_symbolic(&activNOPi, sizeof(int), "activNOPi");.
Voici les attaques trouvées par Klee en au plus deux fautes injectées :
– Attaque 1 : Un seul passage dans la boucle :
1. NOP sur la sortie du noeud FAILURE suivant bb2 (buffer[i] != pin[i])
2. NOP sur la sortie du noeud bb4 pour quitter la boucle immédiatement
Total : 2 fautes
– Attaque 2 : Un seul passage dans la boucle :
1. Inversion de test sur bb2 pour aller vers bb4
2. NOP sur la sortie du noeud bb4 pour sortir immédiatement
Total : 2 fautes
– Attaque 3 : Aucun passage dans la boucle :
1. Inversion du test d’entrée de boucle sur bb5 pour ne pas l’exécuter et sortir vers SUCCESS
Total : 1 faute
L’attaque 3 ne fait pas intervenir d’injection de NOP, elle est donc contenue dans l’approche
Lazart initiale (celle qui ne prend en compte que les inversions de test). L’attaque 1 fait intervenir
deux attaques de type NOP, mais ces deux injections de NOP sont équivalentes à l’inversion des
tests aux lignes 14 puis 12 (au second passage dans la boucle). L’attaque 2, quant à elle, est
15
Franck De Goër
Injections de NOP sur cartes à puce
intéressante, car elle combine les injections de NOP et les inversions de test. Il est à noter que les
préconditions de l’analyseur choisies dans le cadre de ces tests imposent que tous les chiffres du
code pin saisi soient erronés. Avec ces conditions de test, on trouve donc une nouvelle attaque qui
lie les inversions de test et les injections de NOP que l’on n’a pas en ne traitant que les inversions de test.
D’autre part, on peut modifier les préconditions. Par exemple, si l’on considère que l’attaquant
peut connaître le premier chiffre du code PIN, alors il est possible de passer l’authentification avec
succès en forçant la sortie de boucle à la fin de la comparaison : bb4 → SUCCESS). Comme l’attaquant
dispose de trois tentatives pour trouver le code Pin, la probabilité de succès avec cette attaque
devient 0.3 * 0.20 = 0.06 (probabilité de trouver le premier chiffre en trois essais multiplié par la
probabilité de réussite de l’attaque), ce qui est bien mieux que la probabilité de 0.0003 (trois essais
pour 10000 PIN possibles) pour un attaquant dépourvu de laser.
16
Franck De Goër
Injections de NOP sur cartes à puce
int verify(char buffer[], int ofs, int len) {
int i;
/* No comparison if PIN is blocked */
if(triesLeft < 0) {
// FAILURE
if (nop_attack_1) {
fault++;
goto bb1;
}
return EXIT_FAILURE;
}
1
2
3
4
5
6
// bb1
bb1:
/* Main Comparison */
// bb5
for (i = 0; i < len; i++) {
// bb2
if(buffer[ofs + i] != pin[i]) {
triesLeft--;
authenticated = 0;
// FAILURE
if (nop_attack_2) {
fault++;
goto bb4;
}
return EXIT_FAILURE;
}
// bb4
if (nop_attack_3[i]) {
fault++;
goto SUCCESS;
}
}
int verify(char buffer[], int ofs, int len) {
int i;
/* No comparison if PIN is blocked */
if(triesLeft < 0)
// FAILURE
return EXIT_FAILURE;
7
// bb1
8
9
/* Main Comparison */
// bb5
for (i = 0; i < len; i++) {
// bb2
if(buffer[ofs + i] != pin[i]) {
triesLeft--;
authenticated = 0;
// FAILURE
return EXIT_FAILURE;
}
// bb4
}
10
11
12
13
14
15
16
17
18
19
20
21
/* Comparison is successful */
// SUCCESS
SUCCESS:
triesLeft = maxTries;
authenticated = 1;
return EXIT_SUCCESS;
22
/* Comparison is successful */
// SUCCESS
triesLeft = maxTries;
authenticated = 1;
return EXIT_SUCCESS;
23
24
25
26
27
28
}
}
(a) Code initial de verify
(b) Mutant correspondant aux injections de NOP
Figure 15 – Code de vérification du code PIN et mutation
17
Franck De Goër
Injections de NOP sur cartes à puce
entry:
entry:
triesLeft < 0?
T
triesLeft < 0?
F
T
F
bb1
FAILURE
bb1
bb5:
T
FAILURE
T
bb2:
T
bb5:
F
SUCCESS
bb2:
F
FAILURE
F
T
bb4:
SUCCESS
F
FAILURE
bb7
bb4:
bb7
return
return
CFG for 'verify' function
CFG for 'verify' function after Noppy
Figure 16 – Graphe de flot de verify muté par Noppy
entry:
entry:
triesLeft < 0?
T
triesLeft < 0?
F
T
bb1
FAILURE
bb1
bb5:
T
FAILURE
T
SUCCESS
F
bb2:
F
FAILURE
bb5:
F
bb2:
T
F
T
bb4:
FAILURE
bb7
SUCCESS
F
bb4:
bb7
return:
return:
CFG de la fonction 'verify' colorié (objectif : atteindre SUCCESS)
CFG for 'verify' function - Coloration after Noppy
Figure 17 – Graphe de flot de verify
18
Franck De Goër
Injections de NOP sur cartes à puce
Conclusion
Nous avons dans ce papier tenté de fournir un apport à la détection automatique de vulnérabilités
de code aux attaques par injection de faute. Nous nous sommes intéressés spécialement aux attaques
consistant à remplacer un JUMP par un NOP. Après une étude détaillée des impacts de ce type
d’attaque sur le CFG, nous avons proposé une implémentation permettant d’inclure ces attaques à
l’approche Lazart développée à Verimag.
L’algorithme proposé permet de traiter les structures de type if|elsif|else ainsi que les structures while, à supposer que l’on sache détecter les boucles dans un programme (c’est là un problème
classique de compilation et d’optimisation de code que nous n’avons pas traité). L’inclusion dans
Lazart se fait bien, puisque nous avons détecté des attaques faisant intervenir à la fois des inversions
de test et des injections de NOP. L’implémentation détaillée dans ce papier ne prend cependant pas
en compte les appels de fonctions mentionnés en section 3, ni les structures particulières que sont les
switch. Il serait également intéressant de pouvoir moduler l’implémentation en fonction du schéma
de compilation effectif utilisé. Enfin, dans la section 4.2, nous nous essayons à des comparaisons
entre les différentes attaques trouvées ("L’attaque 1 fait intervenir deux attaques de type NOP,
mais ces deux injections de NOP sont équivalentes à l’inversion des tests aux lignes 14 puis 12 (au
second passage dans la boucle)."). Cependant, la comparaison entre deux attaques reste à formaliser (doit-on considérer les instructions exécutées ? Le parcours dans le CFG ? Le résultat en sortie ?).
La majorité du travail présenté dans ce papier a été effectué dans les locaux de Verimag, en
collaboration avec Marie-Laure Potet. Le contenu de ce papier est étroitement lié au travail de
Maxime Puys (TER - UJF), qui développe l’automatisation de la génération des mutants en sortie
de la coloration. Par ailleurs, ce papier s’appuie sur les travaux de Jonathan Vivien, à qui nous
devons le module de coloration du CFG [6]. Enfin, de précieuses informations sur la pratique réelle
des attaques laser ont été fournies par Jessy Clediere, du CESTI.
19
Franck De Goër
Injections de NOP sur cartes à puce
Références
[1] H. Bar-El, H. Choukri, D. Naccache, M. Tunstall et C. Whelan, « The sorcerer’s
apprentice guide to fault attacks », Proceedings of the IEEE, vol. 94, no. 2, p. 370–382, 2006.
[2] M.-L. Potet, L. Mounier et J. Vivien, « A static approach for evaluating the robustness of
secured codes against fault injection by test inverting », 2013.
[3] A. Pellegrini, V. Bertacco et T. Austin, « Fault-based attack of rsa authentication », in
Proceedings of the Conference on Design, Automation and Test in Europe, p. 855–860, European
Design and Automation Association, 2010.
[4] A. Barenghi, L. Breveglieri, I. Koren, G. Pelosi et F. Regazzoni, « Countermeasures
against fault attacks on software implemented aes : effectiveness and cost », in Proceedings of
the 5th Workshop on Embedded Systems Security, p. 7, ACM, 2010.
[5] M. Christofi, Preuves de sécurité outillées d’implémentations cryptographiques. Thèse doctorat,
Université de Versailles Saint-Quentin-En-Yvelines, 2013.
[6] J. Vivien, « Evaluation of code vulnerabilities against fault attacks on smartcards », 2012.
[7] X. Kauffmann-Tourkestansky, Analyses sécuritaires de code de carte à puce sous attaques
physiques simulées. Thèse doctorat, Université d’Orléans, 2012.
[8] Application of Attack Potential to Smartcards. Joint Interpretation Library, 2009.
[9] P. Berthome, K. Heydemann, X. Kauffmann-Tourkestansky et J.-F. Lalande, « High
level model of control flow attacks for smart card functional security », in Availability, Reliability
and Security (ARES), 2012 Seventh International Conference on, p. 224–229, IEEE, 2012.
20
Franck De Goër
A
Injections de NOP sur cartes à puce
Efficacité des détections au niveau source et au niveau binaire
Nous nous proposons ici de résumer les résultats du papier de Berthomé, Heydemann, KauffmanTourkestansky et Lalande [9], comparant l’efficacité des analyses de vulnérabilités aux injections
de faute au niveau binaire et au niveau source. Leur expérimentation a été effectuée sur le logiciel
bzip2. À partir d’un fichier source d’environ 500K, a été produit un fichier compressé de référence
avec bzip2. Puis ce même fichier a été compressé avec un bzip2 sur lequel a été simulé une attaque
laser, soit au niveau source, soit au niveau ASM. Le tableau 2 récapitule leurs résultats. La deuxième
ligne donne le nombre total d’attaques effectuées par chacun des deux modèles. La ligne "Nb of
Bads" donne le nombre de simulations aboutissant à une compression (pas d’erreur à l’exécution) qui
diffère de la compression de référence. Parmi ces compressions corrompues, la ligne "Nb of Empty
file" donne le nombre de fichiers vides et la ligne "Uniq Bads" le nombre de fichiers compressés
différents obtenu.
Statistics
Code Size
Nb of attacks
Simulation time
Nb of Bads
Nb of Enmpty file
Uniq Bads
ASM Coverage
ASM
26103
3531954
2d18h
273129
103952
2326
100%
C
8643
117802
8h
14050
6514
1245
21%
Table 2 – Statistics for simulated attacks on bzip2 [Source : [9]]
Parmi les 1245 fichiers compressés trouvés par l’analyse au niveau C, seulement 503 sont des
attaques trouvées également au niveau ASM. Cela signifie d’une part que l’analyse au niveau source
n’a trouvé que 21% des attaques trouvées par une analyse au niveau binaire, mais d’autre part que
cette analyse est plus efficace, puisqu’elle dure 8 fois moins de temps qu’une analyse complète du
binaire. Il est donc intéressant de travailler au niveau source en terme d’efficacité, mais il faut encore
améliorer cette approche afin de la rendre le plus exhaustive possible.
B
Deux conséquences differentes à une attaque de type NOP
sur une boucle while
Voici deux cas de boucles while donnant des comportements différents si soumis à une unique
attaque sur le test de condition :
Cas particulier 1. Une attaque de type JUMP ← NOP à la ligne 3 amène la boucle à s’exécuter
une fois de plus que prévu (donc 11 fois).
Cas particulier 2. Dans ce cas-ci, la même attaque à la ligne 3 amène la boucle à s’exécuter
infiniement (ou au moins le nombre de fois qu’il faut pour que i fasse un tour de l’ensemble des
INTEGER).
21
Franck De Goër
STORE $1, i
while:
CMP i, 10
JGE endwhile
[...]
ADD $1, i
BRA while
1
2
3
4
5
6
7
Injections de NOP sur cartes à puce
; initialisation de i
;
;
;
;
;
comparaison du compteur
sortie de boucle
instructions
incrementation du compteur
saut au debut de la boucle
8
endwhile:
[...]
9
10
Figure 18 – Cas particulier [1]
STORE $1, i
while:
CMP i, 10
JNE endwhile
[...]
ADD $1, i
BRA while
1
2
3
4
5
6
7
; initialisation de i
;
;
;
;
;
comparaison du compteur
sortie de boucle
<- Difference avec le premier exemple
instructions
incrementation du compteur
saut au debut de la boucle
8
endwhile:
[...]
9
10
Figure 19 – Cas particulier [2]
C
1
2
Mutant de verify pour analyse dynamique
#include <stdio.h>
#include <stdlib.h>
3
4
5
#define SIZE_OF_PIN 4
#define MAX_TRIES 3
6
7
8
9
10
typedef unsigned char BYTE;
BYTE triesLeft = MAX_TRIES;
BYTE authenticated;
BYTE pin[SIZE_OF_PIN] = {1, 2, 3, 4} ;
11
12
13
14
15
16
17
18
19
20
21
int
int
int
int
int
int
int
int
int
int
fault;
activ1;
activ2;
activ3;
activ4;
activ5;
activ6;
activNOP1;
activNOP2;
activNOP3;
22
23
24
void verify_precond(char buffer[SIZE_OF_PIN]) {
int i;
25
26
klee_make_symbolic(&activ1, sizeof(int), "activ1");
22
Franck De Goër
klee_make_symbolic(&activ2,
klee_make_symbolic(&activ3,
klee_make_symbolic(&activ4,
klee_make_symbolic(&activ5,
klee_make_symbolic(&activ6,
27
28
29
30
31
Injections de NOP sur cartes à puce
sizeof(int),
sizeof(int),
sizeof(int),
sizeof(int),
sizeof(int),
"activ2");
"activ3");
"activ4");
"activ5");
"activ6");
32
klee_make_symbolic(&activNOP1, sizeof(int), "activNOP1");
klee_make_symbolic(&activNOP2, sizeof(int), "activNOP2");
klee_make_symbolic(&activNOP3, sizeof(int), "activNOP3");
33
34
35
36
klee_make_symbolic(buffer, sizeof(char)*SIZE_OF_PIN, "buffer");
for (i=0 ; i<SIZE_OF_PIN ; i++) {
klee_assume(buffer[i] != pin[i]);
}
37
38
39
40
41
}
42
43
44
45
void verify_postcond() {
klee_assume(fault <= 2);
}
46
47
48
int verify(BYTE buffer[SIZE_OF_PIN]) {
int i = 0;
49
if (triesLeft == 0) {
return 0;
}
50
51
52
53
for(i=0 ; i<4 ; i++) {
if(buffer[i] != pin[i]) {
triesLeft--;
authenticated = 0;
return 0;
}
}
54
55
56
57
58
59
60
61
triesLeft = MAX_TRIES;
authenticated = 1;
62
63
64
return EXIT_SUCCESS;
65
66
}
67
68
69
70
int main(int argc, char *argv[]) {
BYTE buffer[SIZE_OF_PIN];
71
verify_precond(buffer);
verify(buffer);
verify_postcond();
72
73
74
75
return EXIT_SUCCESS;
76
77
}
—————————————————————————————-
23
Téléchargement