mardi 28 octobre 2008

Integer Overflow

Hello :),
je me suis souvent posé la question du pourquoi du comment j'obtenais parfois des résultats ambigus avec mes entiers. Par exemple une somme de deux entiers positifs qui donnait un résultat négatif...
Ceci est en fait du à ce qu'on appelle "Integer Overflow" ou débordement d'entier.
Tout d'abord essayons de comprendre comment tout ceci fonctionne.

Les Entiers



Tout d'abord nous savons tous qu'il existe différents types de données (BYTE, WORD, DWORD, ...). Prenons quelques exemples de type de données :

-Le type BYTE/SBYTE (en C c'est notre cher unsigned/signed char) : il est codé sur 1 octet donc 8 bits. Sa plage de valeur est donc comprise entre 0x00 et 0xFF.

-Le type WORD/SWORD (en C unsigned/signed short) : il est codé sur 2 octets donc 16 bits. Sa plage de valeur est comprise entre entre 0x0000 et 0xFFFF.

-De même le type DWORD/SDWORD (notre fameux unsigned/signed int) : codé sur 4 octets soit 32bits. Sa plage va de 0x00000000 à 0xFFFFFFFF;

De plus nous savons qu'un entier est soit positif, non-signé(unsigned), soit relatif, signé(signed). Pour les entiers positifs, il n'y a pas de complication. Pour un BYTE par exemple la plage de valeur est de 0 à 255.
Cependant voyons maintenant les entiers signés ou relatifs. Et oui ils peuvent être soit positifs soit négatifs d'où notre (petit) problème. Ce n'est en fait pas très compliqué. Prenons l'exemple d'un SBYTE (codé sur 1 octet et donc 8 bits). Il va en fait être partagé en deux parties, la première constituant le bit de poids fort et la seconde les 7 autres bits. Ce qui donne :



Si on parle binaire, les nombres compris entre 00000000 et 011111111 sont positifs et les nombres compris entre 10000000 et 11111111 sont négatifs. En hexa cela nous donne positif de 0x00 à 0x7F et négatif de 0x80 à 0xFF avec 0x80 = -128 < 0xff =" -1.">Débordement d'entier
Pour commencer qu'est-ce qu'un débordement d'entier ? Et bien c'est tout simplement lorsque un entier prend une valeur qui n'est pas comprise dans sa plage. Par exemple un BYTE ne peut contenir la valeur 395 puisque supérieure à 255. Tout de même si vous tentez l'expérience vous verrez que votre variable prend une valeur (139 pour être précis). Tiens c'est bizarre tout ça.
En fait, lorsqu'on va tenter de mettre 395 dans notre variable c'est le résultat de 395 % 255 = 139 qui va être placé dans la variable.
Il se fera de même pour les autres types de données et les entiers relatifs.
Voici un code qui illustre tout ça :



Maintenant vous allez me dire, mais que se passe-t-il d'autre lors d'un débordement d'entier, une exception n'est pas levée ? Et bien à part dans le cas d'une division non. On verras plus tard pour ce cas particulier. Il y a parcontre des changements au niveau des flags notament le Carry Flag et le Overflow Flag. Le CF lui est armé à chaque fois qu'une opération déborde de la plage de valeur du type de donnée. Pour le OF c'est un peu plus compliqué. Il est mis à un lorsque le résultat d'une opération est de signe logiquement contraire à celui qu'il aurait du être, ceci étant du à un int overflow. Quelques exemples pour illustrer ceci :

-L'addition de deux nombres positifs qui donne un nombre négatif ou inversement. Ce qui est intéressant c'est si l'entier est signé ou non-signé le flag OF prend les mêmes valeurs. Voyez le code si dessous vous comprendrez peutêtre mieux:




Pour le premier résultat il n'y a pas de problème, on constate bien qu'il est négatif à cause de l'int overflow.
Pour le second c'est un peu plus surprenant. Le résultat 254 est positif cependant si on le converti en hexa 0xFE ou en binaire 11111110 on voit bien que si c'était un entier signé il serait négatif. Et bien c'est ce qui est pris en compte pour notre OF.

-La soustraction d'un nombre positif avec un nombre négatif qui donne un résultat négatif ou inversement.

-La multiplication de deux nombres positifs qui donne un résultat négatif, de même pour la division.

Tout à l'heure je parlais de l'instruction DIV mais aussi IDIV qui génèrent une exception lorsque le résultat ou quotient est trop grand. Voici un morceau de code qui génère l'exception :

MOV AX, 1000h
MOV BL, 10h
DIV BL

Et on obtient un joli message d'erreur:




A noter que l'exception générée est Integer Overflow, on s'en serait douté. Cette exception correspond à l'interruption 04h et comme nous le dis le manuel intel le handler de cette interruption ne va pas modifier grand chose au programme et celui-ci va planter. On nous dis aussi que l'instruction INTO (INTerruption on Overflow) génère l'exception Integer Overflow lorsque le OF est à 1. On va donc pouvoir « catcher » ou détecter une partie des Int Overflow là où on pense qu'ils peuvent avoir lieu. Je dis une partie en effet car l'OF n'est pas set à chaque fois comme dis précédemment. Il suffit de mettre un INTO après notre calcul puis de poser un handler et le tour est joué. Voici ce que ça donne :




Et voilà on arrive à détecter un Int overflow qui agit sur l'OF avant qu'il ne puisse faire mal.
Pour ce qui est des débordements d'entiers qui agissent seulement sur le CF il faut soit vérifier son état soit utiliser une instruction comme ADC (ADd with Carry)par exemple qui ajoute à l'opérande destination l'oprérande source et le contenu de CF.

C'est à peu près tout ce que je voulais expliquer. Je vous laisse donc avec un petit divertissement qui a fais peur à Overclok :p : MiniCrackme :)

Les codes et binaires sont disponibles ici.

Voilà à bientôt.

4 commentaires:

Anonyme a dit…

J'ai pas bien compris la subtilité ou la difficulté du crackme. L'analyse peut se faire directement en statique:
[spoil+ à l'arrache]
avant tout on a nombre saisi > 0x5f, à retenir
après al-0x67 <= 0x10 <=> al < 0x77
puis nombre saisi > 0x1F3
et enfin al+0x12 necessite un overflow, sinon badboy. On va prendre AL positif, donc après le add il doit passer en négatif pour overflow (soir MSB=1, donc al > 0x80)
Après l'overflow, le into nous ramène en unhandledexceptionfilter ou ya rien de particulier (j'ai pas pigé l'utilité de la div et pas cherché plus loin vu que le reste fonctionne)

Avec ça on en déduit:
nombre saisi > 0x2yy (avec yy < 0x77)
Puisqu'on veut l'overflow, il faut yy+0x12 >= 0x80, d'où 6E<=yy<=77
Au final le nombre doit être:
en hexa: de type AB, avec A sur 3 octets, 0x2<=A<0xFFFFFF, et 6e<=B<=77

le premier nombre possible est donc 0x0026E = 622 (enfin il me semble, du moins ça marche)
[/spoil]

En tout cas bon exemple :)

lilxam a dit…

Salut,
tout d'abord le but du crackme n'est pas d'être difficile mais juste de mettre en pratique un peu tout ça.
Sinon ton analyse est pas mal :). Pour la division elle est là pour limiter le nombre de solutions...
Ton "AB, avec A sur 3 octets, 0x2<=A<0xFFFFFF, et 6e<=B<=77" n'est donc pas tout à fait juste. Il te manque l'analyse de la 3e étape. Enfin tu as raison 622 marche très bien.
En tout cas bien joué et merci pour le comm.
Lilxam.

0vercl0k a dit…

Salut,
Déjà bravo lilxam j'ai bien aimé, je connaissais très peu le principe (tu l'as vite constaté :)) mais
maintenant tu m'as eclairé pas mal de point. Bien joué ;).
Ensuite quand au monsieur qui réalise l'analyse statiquement c'est loin d'être mon cas !
J'ai du sortir olly et m'y pencher pendant quelques temps, j'ai pris des notes au cours de la recherche de solutions
je vous les propose :

Lilxam Crackme - Integer Overflow
---------------------------------

N : nombre saisis.
N1 : 1premier byte (à droite).
..
NN : N ème byte.

s : solution.

1°) -N > 0x5F
0x60XXXXXX <= s <= 0xFFXXXXXXXX

-N1 - 0x67 <= 0x10
N1 <= 77
0xXXXXXX00 <= s <= 0xXXXXXX77

En théorie on a les résultat ci dessous, mais si nous avons par exemple 0x60 - 0x67 nous allons avoir 0xF9 autrement dit 0xF9 > 0x10,
c'est pour cela qu'on rehausse la limite à 0x67.

Stage 1 completed:
0xXXXXXX67 <= s <= 0xXXXXXX77

2°) -N > 0x1F3
0x1F3 <= s

3°) N1+0x12 L'overflow doit être réalisé ici ; autrement dit nous avons un entier positif il faut donc que le résultat
de l'addition donne un byte négatif pour que le Overflow flag soit armé, l'instruction INTO declanchera alors l'exception cautché par
la fonction définit par SetUnhandledFunction().

N1+0x12 ; N1 doit etre positif et doit donc avoir le bit de poid fort (MSB) à 0, mais le résultat doit être négatif (on peut même voir
cela dans le SF (sign flag)).
N1+0x12 => 0x80 soit N1 => 6E

Pour le moment, je passe les tests avec 0x0000026F (623).

4°) -N-0x70
-N2N1/8 <= 0xFF
N2N1 <= 0x7F8

Recapitulons:
N4N3N2N1 -> notre dword avec les contraintes suivante :
6E <= N1 <= 77
N > 0x1F3
N2N1 <= 0x7F8

A partir de ces règles on peut obtenir les solutions ; la mienne sera 0x1F31086F soit 523307119 mwhaha :).

Il y'a peut être des endroits où je me suis planté (ça m'etonnerait pas) m'enfin ça a l'air de fonctionner, encore une fois merci lilxam :).
Cordialement, 0vercl0k.

lilxam a dit…

Voilà bravo c'est tout à fait ça :), du moins je crois ^^.
Tu vois que c'était pas si compliqué :p.
Merci et bonne continuation.
Lilxam.