Dépassement de tampon - Définition et Explications

Source: Wikipédia sous licence CC-BY-SA 3.0.
La liste des auteurs de cet article est disponible ici.

Introduction

En informatique, un dépassement de tampon ou débordement de tampon (en anglais, buffer overflow) est un bug causé par un processus qui, lors de l'écriture dans un tampon, écrit à l'extérieur de l'espace alloué au tampon, écrasant ainsi des informations nécessaires au processus.

Lorsque le bug se produit non intentionnellement, le comportement de l'ordinateur (Un ordinateur est une machine dotée d'une unité de traitement lui permettant d'exécuter des programmes enregistrés. C'est un ensemble de circuits...) devient imprévisible. Il en résulte souvent un blocage du programme, voire de tout (Le tout compris comme ensemble de ce qui existe est souvent interprété comme le monde ou l'univers.) le système.

Le bug peut aussi être provoqué intentionnellement et être exploité pour violer la politique de sécurité d’un système. Cette technique est couramment utilisée par les pirates informatiques. La stratégie (La stratégie - du grec stratos qui signifie « armée » et ageîn qui signifie « conduire » - est :) du pirate est alors de détourner le programme bugué en lui faisant exécuter des instructions qu'il a introduites dans le processus.

Utilisation malveillante et contre-mesures

Le principe de l'utilisation malveillante du dépassement de tampon (En informatique, un dépassement de tampon ou débordement de tampon (en anglais, buffer overflow) est un bug causé par un processus qui, lors de l'écriture dans un tampon, écrit...) est de profiter de l’accès à certaines variables du programme, souvent par le biais de fonctions telles que scanf() (analyse d'une chaîne (Le mot chaîne peut avoir plusieurs significations :) de caractères) ou strcpy() (copie d'une chaîne de caractères) en langage C (Le C++ est un langage de programmation permettant la programmation sous de multiples paradigmes comme la programmation procédurale, la programmation orientée objet et la programmation générique. C++ est...), qui ne contrôlent pas la taille de la chaîne à traiter, afin d’écraser la mémoire (D'une manière générale, la mémoire est le stockage de l'information. C'est aussi le souvenir d'une information.) du processus jusqu’à l’adresse de retour de la fonction en cours d’exécution. Le pirate peut ainsi choisir quelles seront les prochaines instructions exécutées par le processus et faire exécuter un code malveillant qu'il aura introduit dans le programme.

Afin d’éviter ces dépassements, certaines fonctions ont été réécrites pour prendre en paramètre (Un paramètre est au sens large un élément d'information à prendre en compte pour prendre une décision ou pour effectuer un calcul.) la taille du tampon dans lequel les données (Dans les technologies de l'information (TI), une donnée est une description élémentaire, souvent codée, d'une chose, d'une transaction d'affaire, d'un événement, etc.) sont copiées, et éviter ainsi de copier des informations à l'extérieur du tampon. Ainsi strncpy() est une version de strcpy() qui tient compte de la taille du tampon.

La fonction strncpy présente l'inconvénient de remplir toute la fin du tampon de zéros, ce qui la rend moins efficace. Les fonctions strlcpy et strlcat, qui ne présentent pas ce défaut, ont été initialement disponibles sous OpenBSD (OpenBSD est un système d'exploitation libre de type Unix, dérivé de 4.4BSD. Créé en 1994 par Theo de Raadt, il est issu de la...) et elles tendent à se répandre dans divers logiciels comme rsync (rsync (remote synchronization, synchronisation distante) est un logiciel de synchronisation de fichiers, distribué sous licence GPL. La synchronisation est unidirectionnelle, c'est-à-dire qu'elle copie les...) et KDE (KDE est un projet de logiciel libre historiquement centré autour d'un environnement de bureau pour systèmes UNIX. Ce projet a évolué et il se compose maintenant d'un ensemble de...).

Détails techniques sur architecture (L’architecture peut se définir comme l’art de bâtir des édifices.) x86 (La famille x86 regroupe les microprocesseurs compatibles avec le jeu d'instructions de l'Intel 8086. Cette série est nommée IA-32 (pour Intel architecture 32 bits) par Intel pour ses processeurs à partir du Pentium.) (Intel)

Un programme en exécution (un processus) découpe la mémoire adressable en zones distinctes :

  • la zone de code où sont stockées les instructions du programme en cours ;
  • la zone des données où sont stockées certaines des données que manipule le programme ;
  • la zone de la pile d'exécution ;
  • la zone du tas.

Contrairement aux deux premières, les deux dernières zones sont dynamiques, c'est-à-dire que leur pourcentage (Un pourcentage est une façon d'exprimer une proportion ou une fraction dans un ensemble. Une expression comme « 45 % » (lue « 45 pour cent ») est...) d'utilisation et leur contenu varient tout au long de l’exécution d’un processus.

La zone de la pile d'exécution est utilisée par les fonctions (stockage des variables locales et passage des paramètres). Elle se comporte comme une pile, c'est-à-dire dernier entré, premier sorti. Les variables et les paramètres d’une fonction sont empilés avant le début de la fonction et dépilés à la fin de la fonction.

Une fonction est une suite d'instructions. Les instructions d'une fonction peuvent être exécutées (en informatique (L´informatique - contraction d´information et automatique - est le domaine d'activité scientifique, technique et industriel en rapport avec le traitement automatique de l'information par des machines telles que les...), on dit que la fonction est appelée) à partir de n'importe quel endroit d'un programme. À la fin de l'exécution des instructions de la fonction, l'exécution doit se continuer à l'instruction (Une instruction est une forme d'information communiquée qui est à la fois une commande et une explication pour décrire l'action, le comportement, la méthode ou la tâche qui devra commencer,...) du programme qui suit l'instruction qui a appelé la fonction.

Pour permettre le retour au programme qui a appelé la fonction, l’instruction d'appel de la fonction (l'instruction call) enregistre l'adresse (Les adresses forment une notion importante en communication, elles permettent à une entité de s'adresser à une autre parmi un ensemble d'entités. Pour qu'il n'y ait pas d'ambiguïté, chaque adresse doit correspondre à une unique...) de retour dans la pile d'exécution. Lors de l’exécution de l’instruction ret qui marque la fin de la fonction, le processeur (Le processeur, ou CPU (de l'anglais Central Processing Unit, « Unité centrale de traitement »), est le composant de l'ordinateur...) récupère l’adresse de retour qu’il a précédemment stockée dans la pile d'exécution et le processus peut continuer son exécution à cette adresse.

Plus précisément, le traitement d'une fonction inclut les étapes suivantes :

  1. l'empilage des paramètres de la fonction sur la pile d'exécution (avec l'instruction push) ;
  2. l'appel de la fonction (avec l'instruction call) ; cette étape déclenche la sauvegarde (En informatique, la sauvegarde (backup en anglais) est l'opération qui consiste à dupliquer et à mettre en sécurité les données contenues dans un...) de l’adresse de retour de la fonction sur la pile d'exécution ;
  3. le début de la fonction qui inclut :
    1. la sauvegarde de l'adresse de la pile qui marque le début de l'enregistrement de l'état actuel du programme,
    2. l'allocation des variables locales dans la pile d'exécution ;
  4. l'exécution de la fonction ;
  5. la sortie de la fonction qui inclut la restauration du pointeur qui marquait le début de l'enregistrement de l'état du programme au moment de l'appel de la fonction,
  6. l'exécution de l’instruction ret qui indique la fin de la fonction et déclenche la récupération de l’adresse de retour et le branchement à cette adresse.

Illustration

Soit l’extrait de programme C suivant (volontairement simplifié) :

       #include               void foo(char *str)       {         char buffer[32];         strcpy(buffer, str);         /* ... */       }        int main (La main est l’organe préhensile effecteur situé à l’extrémité de l’avant-bras et relié à ce dernier par le poignet. C'est un...)(int argc, char *argv[])       {         if (argc > 1) {           /* appel avec le premier argument de la ligne de commandes */           foo(argv[1]);         }         /* ... */         return 0;       }      

Ce qui est traduit ainsi par un compilateur (Un compilateur est un programme informatique qui traduit un langage, le langage source, en un autre, appelé le langage cible (ou langage objet), en préservant...) C (ici le compilateur GCC avec architecture x86) :

       push ebp              ; entrée de la fonction       mov ebp,esp           ;       sub esp,40            ; 40 octets sont « alloués » (32 + les 2 variables qui serviront                             ; à l'appel de strcpy)              mov eax,[ebp+0x8]     ; paramètre de la fonction (str)       mov [esp+0x4],eax     ; préparation de l'appel de fonction: deuxième paramètre       lea eax,[ebp-0x20]    ; ebp-0x20 contient la variable (En mathématiques et en logique, une variable est représentée par un symbole. Elle est utilisée pour marquer un rôle dans une formule, un prédicat ou un algorithme.      En statistiques, une variable peut...) locale 'buffer'       mov [esp],eax         ; premier paramètre       call strcpy                             ; sortie de la fonction       leave                 ; équivalent à mov esp,ebp et pop ebp       ret      

Voici l'état de la pile d'exécution et des deux registres (ebp et esp) juste avant l'appel de la fonction strcpy :

État de la pile avant l'appel à la fonction strcpy

La fonction strcpy copiera le contenu de str dans buffer. La fonction strcpy ne fait aucune vérification : elle copiera le contenu de str jusqu'à ce qu'elle rencontre un caractère de fin de chaîne (caractère nul).

Si str contient plus de 32 octets avant le caractère nul, la fonction strcpy continuera à copier le contenu de la chaîne au-delà de la zone allouée par la variable locale buffer. C’est ainsi que les informations stockées dans la pile d'exécution (incluant l’adresse de retour de la fonction) pourront être écrasées comme indiqué dans l'illustration suivante :

Dépassement (Un dépassement est le fait de rouler pendant un instant, en général relativement court, à côté d’un autre véhicule à une...) de tampon : la fonction strcpy copie la chaîne vers la zone mémoire indiquée, dépasse la zone allouée et écrase l'adresse de retour de la fonction foo

Dans l’exemple précédent, si str contient un code malveillant sur 32 octets suivi de l’adresse de buffer, au retour de la fonction, le processeur exécutera le code contenu dans str.

Pour que cette « technique » fonctionne, il faut deux conditions :

  • le code malveillant ne doit contenir aucun caractère nul, sans quoi strcpy() arrêtera sans copier les octets suivants le caractère nul ;
  • l'adresse de retour ne doit pas non plus contenir de caractère nul.

Le dépassement de tampon avec écrasement de l'adresse de retour est un bug du programme. L’adresse de retour ayant été écrasée, à la fin de la fonction, le processeur ne peut brancher vers l'adresse de retour originale, car cette adresse a été modifiée par le dépassement de tampon. Dans le cas d'un bug involontaire, l'adresse de retour a généralement été remplacée par une adresse en dehors de la plage (La Plage est un film anglo-américain réalisé par Danny Boyle en 2000 et adapté du roman The Beach d'Alex Garland) adressable et le programme plante (Les plantes (Plantae Haeckel, 1866) sont des êtres pluricellulaires à la base de la chaîne alimentaire. Elles forment l'une des subdivisions (ou règne) des Eucaryotes. Elles sont,...) en affichant un message (La théorie de l'information fut mise au point pour déterminer mathématiquement le taux d’information transmis dans la communication d’un message...) d’erreur (erreur de segmentation).

Un pirate informatique peut utiliser ce comportement à ses fins. Par exemple, en connaissant la taille du tampon (dans l’exemple précédent 32 octets), il peut écraser l’adresse de retour pour la remplacer par une adresse qui pointe vers un code à lui, de manière à prendre le contrôle (Le mot contrôle peut avoir plusieurs sens. Il peut être employé comme synonyme d'examen, de vérification et de maîtrise.) du programme. De cette façon, il obtient les droits d’exécution associés au programme qu'il a détourné et peut dans certains cas accéder à des ressources critiques.

La forme d’attaque la plus simple consiste à inclure dans une chaîne de caractères (En informatique, une chaîne de caractères est une suite ordonnée de caractères. La chaîne de caractères est un type de donnée dans de nombreux langages informatiques. En anglais, on...) copiée dans le tampon un programme malveillant et d'écraser l'adresse de retour par une adresse pointant vers ce code malveillant.

Pour arriver à ses fins, le pirate doit surmonter deux difficultés :

  • trouver l'adresse de début de son code malveillant dans le programme attaqué ;
  • construire son code d’exploitation en respectant les contraintes imposées par le type de variable dans lequel il place son code (dans l'exemple précédent, la variable est une chaîne de caractères).

Trouver l'adresse de début du code malveillant

La technique précédente nécessite généralement pour l’attaquant de connaître l’adresse de début du code malveillant. Ceci est assez complexe, car cela demande généralement des essais successifs, ce qui n’est pas une méthode très « discrète ». De plus, il y a de fortes chances que l’adresse de la pile change d’une version à l’autre du programme visé et d’un système d'exploitation à l’autre.

Le pirate veut généralement exploiter une faille sur le plus de versions possible du même programme (afin peut-être de concevoir un virus (Un virus est une entité biologique qui nécessite une cellule hôte, dont il utilise les constituants pour se multiplier. Les virus existent sous une forme extracellulaire ou intracellulaire. Sous la forme intracellulaire...) ou ver). Pour s’affranchir du besoin (Les besoins se situent au niveau de l'interaction entre l'individu et l'environnement. Il est souvent fait un classement des besoins humains en trois grandes...) de connaître l'adresse du début du code malveillant, il doit trouver une méthode qui lui permette de brancher sur son code sans se préoccuper de la version du système d'exploitation et du programme visé tout en évitant de faire de multiples tentatives qui prendraient du temps (Le temps est un concept développé par l'être humain pour appréhender le changement dans le monde.) et dévoileraient peut-être sa présence.

Il est possible dans certains cas de se servir du contexte (Le contexte d'un évènement inclut les circonstances et conditions qui l'entourent; le contexte d'un mot, d'une phrase ou d'un texte inclut les mots qui...) d’exécution du système cible. Par exemple, sur des systèmes Windows (Windows est une gamme de systèmes d'exploitation produite par Microsoft, principalement destinées aux machines compatibles PC. C'est le remplaçant de MS-DOS. Depuis les...), la plupart des programmes, même les plus simples contiennent un ensemble (En théorie des ensembles, un ensemble désigne intuitivement une collection d’objets (les éléments de l'ensemble), « une multitude qui peut être...) de primitives systèmes accessibles au programme (DLL). Il est possible de trouver des bibliothèques dont l’adresse mémoire lors de l’exécution change peu en fonction de la version du système. Le but pour l’attaquant est alors de trouver dans ces plages mémoires des instructions qui manipulent la pile d'exécution et lui permettront d’exécuter son code.

Par exemple, en supposant que la fonction strcpy manipule un registre processeur (eax) pour y stocker l’adresse source. Dans l’exemple précédent, eax contiendra une adresse proche de l’adresse de buffer au retour de strcpy. Le but de l’attaquant est donc de trouver dans la zone mémoire supposée « fixe » (la zone des DLL par exemple) un code qui permet de sauter vers le contenu de eax (call eax ou jmp eax). Il construira alors son buffer en plaçant son code suivi de l’adresse d’une instruction de saut (call eax ou jmp eax). Au retour de strcpy, le processeur branchera vers une zone mémoire contenant call eax ou jmp eax et puisque eax contient l’adresse du buffer, il branchera de nouveau vers le code et l’exécutera.

Construire son code d’exploitation

La deuxième difficulté pour l'attaquant est la construction du code d’exploitation appelé shellcode. Dans certains cas, le code doit être construit avec un jeu de caractères réduit : chaîne Unicode (Unicode est une norme informatique, développée par le Consortium Unicode, qui vise à donner à tout caractère de n’importe quel système...), chaîne alphanumérique, etc.

En pratique, ces limitations n'arrêtent pas un pirate déterminé. On recense des cas de piratage utilisant du code limité aux caractères légaux d'une chaîne Unicode (mais il faut pouvoir exécuter du code automodifiant).

Préventions

Pour se prémunir contre de telles attaques, plusieurs options sont offertes au programmeur (En informatique, un développeur (ou programmeur) est un informaticien qui réalise du logiciel en créant des algorithmes et en les mettant en œuvre dans un...). Quelques unes de ces options sont décrites dans les deux sections suivantes.

Protections logicielles

  • Modifier le compilateur pour qu’il insère des instructions NOP de façon aléatoire dans le code du noyau et des applications (opérations de routine en Linux). Cela ralentit peu les programmes et complique énormément la tâche du pirate qui ne sait plus quelles adresses il doit viser.
  • Utiliser un autre langage que C ou C++ qui ne dispose d'aucun mécanisme de vérification de dépassement de tampon.
  • Utiliser des outils externes qui permettent, en mode développement, de tester les cas litigieux, par exemple la bibliothèque Electric Fence ou Valgrind.
  • Bannir de son utilisation les fonctions dites « non protégées ». Préférer par exemple strncpy à strcpy ou alors fgets à scanf qui effectue un contrôle de taille. Les compilateurs récents peuvent prévenir le programmeur s’il utilise des fonctions à risque, même si leur utilisation demeure possible.
  • Protéger, côté système, la pile d'exécution :
    • Rendre la pile non exécutable,
    • Mettre en place un mécanisme de vérification de la pile (technique du canari (Le Canari est la forme domestiquée du Serin des Canaries (Serinus canaria). La sélection de ces oiseaux au sein des élevages a permis de...). Le principe est de stocker une clé de valeur aléatoire, générée à l’exécution, entre la fin de la pile et l'adresse de retour. Si cette clé est modifiée, l’exécution est avortée (disponible en option dans les compilateurs C récents - éventuellement avec recours à des patchs). La principale critique de cette méthode est que l’appel de fonction est ainsi ralenti.

Aucune de ces solutions logicielles ne s’est imposée (en 2008) dans le monde (Le mot monde peut désigner :) du développement industriel. Pourtant, les dépassements de tampon représentent une part encore importante des failles permettant le développement de vers, de virus et d'attaques manuelles.

Protections matérielles

  • Les microprocesseurs récents, 64 bits notamment, implémentent des protections efficaces (technologies NX Bit et XD bit).
Page générée en 0.298 seconde(s) - site hébergé chez Amen
Ce site fait l'objet d'une déclaration à la CNIL sous le numéro de dossier 1037632
Ce site est édité par Techno-Science.net - A propos - Informations légales
Partenaire: HD-Numérique