Il existe beaucoup de trous de sécurité dans les programmes [mal] écris en C dont notament les buffers overflows ou encore les format string.
Contrairement aux BoF (buffer overflow) et aux formats strings, les bug d'integer overflow ne touchent pas directement la mémoire.
Les integer overflow sont appelés, en français, des débordements d'entiers.
Voici le plan que nous allons développer dans cet article :
Dans le langage C, les données (constantes, variables, etc.) sont typées. Chaque variable est donc associée à des informations qui occupent une certaine taille en mémoire.
Toujours en C, il existe trois types :
Étant donné que c'est un article sur les débordements d'entiers, nous n'allons parler que des entiers ;-).
En C, comme en maths, les entiers ont un signe par défaut. Les entiers ont typiquement la même taille qu'un pointeur du système sur lequel ils sont compilés (c.-à-d. sur un système de 32 bits, tel qu'i386, un entier est 32 bits de long, alors que sur un système de 64 bits, tel que SPARC, un entier est 64 bits de long).
Comme vous le savez, lorsqu'on définit une variable contenant un nombre en C, on précise s'il est signé ou non (en rajoutant 'unsigned' devant l'int par exemple).
On détermine donc si c'est un nombre entier naturel (strictement positif ou nul) ou relatif. Nous pouvons dès lors savoir le nombre maximum attribué à cet entier: (2^n)-1 où n est le nombre de bit de votre système. Donc sur un système 32bits, le nombre entier maximum est 4294967295 pour un entier naturel ;-).
- Comment trouver ces valeurs?
/* valeur-entier-sur-votre-machine.c */ #include <stdio.h> #include <limits.h> int main(void){ int intmax = INT_MAX; int intmin = INT_MIN; unsigned int unsignedmax = UINT_MAX; printf("INT_MAX: entier relatif maximum %d\n",intmax); printf("INT_MIN: entier relatif minimum %d\n",intmin); printf("UINT_MAX: entier naturel maximum 0x%x\n",unsignedmax); return 0; }
[n-0-x@3Com noxistes]$ ./valeur-entier-max.o
INT_MAX: entier relatif maximum 2147483647
INT_MIN: entier relatif minimum -2147483648
UINT_MAX: entier naturel maximum 0xffffffff
[n-0-x@3Com noxistes]$
Pour différencier les nombres négatifs des nombres positifs en binaire, on doit regarder le chiffre le plus à gauche, nommé MSB pour Most Significant Byte. Si ce chiffre est un 1 alors le nombre est négatif.
011010010 est donc un nombre positif.
Comme nous l'avons vu, un entier possède une taille qui est fixe et qui dépend de la machine sur lequel le programme a été compilé. Vous l'aurez sûrement compris, le débordement d'entier consiste à lui attribuer une valeur plus grande que la valeur maximale possible.
En C, la valeur maximale est tronquée pour pouvoir entrer sur 32bits (lorsque la porgramme a été compilé sur un système 32bits bien sûr) bien que le programme ne soit pas averti du fait que la valeur maximale a été tronquée. La norme ISO C99 dit qu'un débordement d'entier cause un "comportement indéfini", signifiant que les compilateurs, conformément à la norme, peuvent faire ce qu'ils veulent : ignorer complètement le débordement, arrêter le programme, etc..
La plupart des compilateurs semblent ignorer le débordement, ayant pour résultat un résultat inattendu ou incorrect stocké. Un integer overflow va donc modifier complètement la logique des opérations arithmétiques du programme.
Comme nous l'avons dit dans l'introduction, un integer overflow n'est pas dangeureux directement, mais peut causer des dégâts puisqu'il peut engendrer, par exemple, un BoF en modifiant le nombre stocké dans une variable qui écrasera alors une partie de la mémoire (voir l'article récapitulatif sur les BoF).
Les integer overflow engendrent le plus souvent des heap overflow, mais ces types de bugs, toujours en rapport avec le C, seront le sujet d'un autre article ;-).
Les débordements d'entiers ne sont pas comme la plupart des bogues communs.
Ils ne permettent pas la réécriture directe de la mémoire ou du contrôle direct d'exécution, mais sont beaucoup plus subtiles.
La racine du problème se situe dans le fait qu'il n'y a aucune manière pour qu'un processus vérifie le résultat d'un calcul après qu'il se soit produit. Il peut y avoir une anomalie entre le résultat stocké et le résultat correct.
Pour cette raison, la plupart des débordements d'entiers ne sont pas réellement exploitables.
Néanmoins, dans certains cas il est possible de forcer une variable cruciale à contenir une valeur incorrecte, et ceci peut mener à des problèmes plus tard dans le code.
En raison de la subtilité de ces bogues, il y a un nombre énorme de situations dans lesquelles ils peuvent être exploités, ainsi je n'essayerai pas de couvrir toutes les conditions exploitables. Au lieu de cela, je fournirai des exemples de quelques situations qui sont exploitables: c'est l'exemple de Phrack (60-0x0a, voir les sources).
/* width1.c - exploiting a trivial widthness bug */
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]){
unsigned short s;
int i;
char buf[80];
if(argc < 3){
return -1;
}
i = atoi(argv[1]);
s = i;
if(s >= 80){ /* [w1] */
printf("Oh no you don't!\n");
return -1;
}
printf("s = %d\n", s);
memcpy(buf, argv[2], i);
buf[i] = '\0';
printf("%s\n", buf);
return 0;
}
[n-0-x@3Com noxistes]$ ./width1.o 8 noxistes.org
s = 8
noxistes
[n-0-x@3Com noxistes]$ ./width1.o 79 noxistes.org
s = 79
noxistes.org
[n-0-x@3Com noxistes]$ ./width1.o 80 noxistes.org
Oh no you don't!
[n-0-x@3Com noxistes]$ ./width1.o 65536 noxistes.org
s = 0
Segmentation fault
[n-0-x@3Com noxistes]$
L'argument de longueur est pris de la ligne de commande et tenu dans le nombre entier i. Quand cette valeur est transférée dans le nombre entier court s, il est tronqué si la valeur est trop grande pour s'adapter dans s (c-à-d. si la valeur est plus grande que 65535).
Pour cette raison, il est possible de dévier le système de vérification du buffer à [ w1 ] et déborder l'amortisseur.
Après ceci, des techniques habituelles de BoF peuvent être employées pour exploiter le processus.