mercredi 10 décembre 2008

Integer Overflow


Intro
Dans cet article je vais vous présenter les integer overflow. C’est une faille logicielle au même titre que les stack / heap overflow &Cie. Cet article requiert certaines connaissances : langage C, binaire, les concepts de base la gestion de la mémoire de votre OS (il n’y a pas besoin d’être un expert dans ces domaines pour comprendre l’article). Je rappellerais certaines connaissances au début de cet article (que vous connaissez peut-être déjà, au pire un rappel ne fais pas de mal ;-), puis je présenterai la faille en elle-même, et enfin montrerai un cas pratique.

Les différents types de variables :
En programmation il existe différents type de variables. Ces variables ont des capacités différentes et donc une plage de valeurs différente. Certaines sont codé sur 1 octet d’autres sur 2, ou encore 8 …
Elles peuvent stocker différents types de valeur, (nombres à virgule, entiers, signés, non signés). Les variables de type signed contiendront des nombres positifs et des nombres négatifs, tandis qu’une variable unsigned contiendra uniquement des nombres positifs.
Voici les différents types de variables en C :















Type de variableTaillePlage de valeurs
char1 octet-128 à 127
unsigned char1 octet0 à 255
short2 octets-32 768 à 32 767
unsigned short2 octets0 à 65 535
int2 octets (proc 16 bits)

4 octets (proc 32 bits)
-32 768 à 32 767
-2 147 483 648 à 2 147 483 647
unsigned int2 octets (proc 16 bits)

4 octets (proc 32 bits)
0 à 65 535
0 à 4 294 967 295
long int4 octets-2 147 483 648 à 2 147 483 647
unsigned long int4 octets0 à 4 294 967 295
float4 octets3.4*10-38 à 3.4*10 38
double8 octets1.7*10-308 à 1.7*10 308
long double10 octets3.4*10-4932 à 3.4*10 4932



Représentation des variables :
Selon si la variable est signed ou unsigned elle sera stockée de différentes manières en mémoire. Prenons le cas d’un char :

Si il est unsigned (donc un nombre forcément positif) sa plage de valeur s’étend de 0 à 255. Il sera représenté en mémoire de la manière la plus simple, à chaque valeur décimale correspond sont équivalent binaire.

Si il est signed (donc un nombre pouvant être négatif et positif) sa plage de valeurs s’étend de -128 à 127. La notation utilisée pour pouvoir représenter à la fois des nombres positifs et négatifs est la notation du complément à 2. Pour cela il faut passer par le binaire : un short est codé sur 1 octets, soit 8 bits. La méthode du complément à deux est simple, et se réalise par étape (le bit de poids fort n’est plus utilisé pour coder la valeur du nombre, mais le signe comme le montre la 3ème étape) :

1ère étape : on prend le nombre positif et on inverse tout les bits, par exemple 9 est égale en binaire à 0000 1001(2) , -9 donnerait alors 1111 0110(2)
2ème étape : à ce nombre on ajoute 1, si on suit l’exemple du -9 cela donnerait alors 1111 0111(2)
3ème étape : enfin on met le bit de poids fort à 1 pour montrer la négativité du nombre, dans notre cas -9 donnerait 1111 0111(2)

Voila vous savez (ou vous souvenez) comment sont stockés les nombres en mémoire.

Présentation de la faille :
Un integer overflow peut se trouver dans un programme de différentes manières, en regardant le code source, par fuzzing, par reverse engineering, …

Un integer overflow se présente sous différentes formes. Il n’est pas généralement directement visible dans le code source du programme.
Le principe de l’integer overflow est comme sa traduction l’indique le dépassement de la capacité d’un entier.

Par exemple prenons un unsigned char et affectons lui la valeur 253 et ajoutons lui 10 (cela revient exactement au même que de lui affecter directement 263 …)
unsigned char a = 253;
//a vaut 253 soit 1111 1101 en binaire
a += 10;

253 + 10 = 263 soit 1 0000 0111 en binaire, mais a ne peut contenir un tel nombre car un char n’est codé que sur 8 bits, donc le programme tronquera en ne gardant que les 8 premiers bits, ce qui donnera 0000 0111 soit 7, a vaudra donc 7. On a donc dépassé la capacité de a, et par conséquent a se retrouve avec une valeur faussée.

Maintenant imaginons à peu près le même exemple avec un signed char, et affectons lui la valeur 122, et ajoutons 15.

char a = 122 ;
a vaut 0111 1010 en binaire
a += 15 ;

a vaut maintenant 1000 1001, mais nous travaillons sur un nombre signé, donc nous utilisons le complément à 2. En l’appliquant on se rend compte que a vaut -119.

Des integer overflow peuvent aussi se produire lorsque l’on essaye d’affecter deux types différents de variables entres-eux. Par exemple si l’on essaye d’affecter un short (2 octets) d’une valeur de 456 dans un unsigned char (1 octet), alors il se produira un integer overflow. Effectivement 456 s’écrivant en binaire : 1 1100 1000, lorsqu’il sera affecté au char alors il sera tronqué comme dans le premier exemple et l’on ne gardera que les 8 premiers bits, ce qui donnera 1100 1000 soit 200 en décimal.

Ces trois exemples sont des cas d’integer overflow.

Exemple de faille :
Voici un exemple de code incluant une faille de type integer overflow, illustrant le troisième cas vu ci-dessus :

#include
#define BUFF_SIZE 160

int main(int argc, char *argv[])
{
char buffer[BUFF_SIZE] = "debut chaine …";
/*
...
...
*/

if (argc <= 1) { printf("Aucun argument passé en paramètre.\n"); return -1; } char length_concat = strlen(buffer) + strlen(argv[1]); //integer overflow possible if (length_concat > BUFF_SIZE) //le test sera facilement être faussé
{
printf("La chaine passé en paramètre est trop longue.\n");
return -1;
}

strncpy(buffer, argv[1], strlen(argv[1]));
/*


*/
printf("Fin.\n");
return 0;
}

Explications :
Notre chaine « buffer » est initialisé à une taille de 160, et on lui affecte la chaîne « debut chaine … ». La ligne intéressante et celle où est indiqué « integer overflow possible » en commentaire. En effet on voit que l’on récupère la taille de notre chaîne actuelle, ainsi que la taille de la chaîne passée en paramètre au programme, et l’on somme ces deux tailles. Le problème est que l’on va stocker cette somme dans un char. Si l’utilisateur a passé en paramètre une chaîne d’une taille supérieure à 255 caractères alors un integer overflow se produira obligatoirement, et donc faussera le test servant à savoir si la taille des deux chaînes peut loger dans « buffer ».

Exemple :
On passe en paramètre une chaine de 267 caractères, et la chaîne actuelle contenue dans buffer fait 9 caractères, alors la somme de la taille de ces chaînes fera 276 caractères, donc length_concat est censée contenir la valeur 276, soit 1 0001 0100 en binaire, ce qui donne 0001 0100 stockée dans un char soit 20 en décimal. A cause de la troncature le test sera donc faussé et 20 sera inférieur à 160 alors que dans la réalité la chaîne fait 276 caractères. La copie de la seconde chaîne de caractère à la suite de la première aura donc lieu, et débordera du buffer ce qui produira un buffer overflow. Et il sera alors possible de détourner l’exécution du programme.

Conclusion :
Les integer overflow sont de réelles failles, comme en témoigne cette liste sur le site de secunia.
Ces failles sont difficilement détectables car il n’est pas facile de contrôler la cohérence d’un résultat. Mais même s’il existe un integer overflow dans un programme, cela ne veut pas dire pour autant qu’il est exploitable.