« Attaque par Buffer Overflow » : différence entre les versions

De Wiki du LAMA (UMR 5127)
Aller à la navigation Aller à la recherche
 
(29 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 26 : Ligne 26 :
Dans ce schéma, les adresses sont classées de haut en bas mais les valeurs sont empilées en haut car il s’agit d’une pile. Les différentes zones (en partant du bas du schéma) sont détaillées ci-dessous :
Dans ce schéma, les adresses sont classées de haut en bas mais les valeurs sont empilées en haut car il s’agit d’une pile. Les différentes zones (en partant du bas du schéma) sont détaillées ci-dessous :


* '''les paramètres supplémentaires''' sont ceux passés à la fonction en cours d’exécution : on parle de paramètres « supplémentaires » car les 6 premiers paramètres seront passés dans des registres ;
* '''les paramètres supplémentaires''' sont ceux passés à la fonction en cours d’exécution : ils correspondent à tous les paramètres après le 6ème passé dans la fonction car les 6 premiers sont placés dans le registre
* '''l’adresse de retour''' est l’adresse de l’instruction suivant l’appel de la fonction en cours d’exécution dans la fonction appelante (i.e. c’est là qu’on revient quand on exécute return à la fin de la
* '''l’adresse de retour''' est l’adresse de l’instruction de la fonction en cours d'exécution (ex: return) ;
* '''le %rbp « précédent »''' correspond au pointeur de base de la fonction appelante pour les assembleurs 64 bits (l'équivalent en système 32 bits est l''''EBP''' ;
fonction) ;
* '''les variables locales''' sont celles de la fonction en cours d’exécution, stockées dans sa pile, à des adresses toujours inférieurs et immédiates au %rbp ;
* '''le %rbp « précédent »''' est la sauvegarde du pointeur de base de la fonction appelante (on détaillera son rôle plus bas) ;
* des valeurs '''temporaires''' peuvent aussi être stockées ;
* '''les variables locales''' de la fonction en cours d’exécution sont localisées dans son cadre de pile, à des adresses fixes par rapport à %rbp ;
* à chaque appels de fonctions, le même schéma est répété
* on peut éventuellement utiliser de la place dans le cadre de pile pour sauvegarder des registres ou des valeurs '''temporaires''' si besoin ;
* les contextes d’exécution s’empilent les uns au dessus des autres lors des appels de fonctions, donc on retrouve la même structure en haut du schéma si la fonction appelée appelle elle-même une autre fonction.


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.
La pile d'exécution correspond donc à la zone mémoire allouée pour chaque fonction du programme en cours d'exécution. Son comportement est le même que celui d'une pile : dernier entré premier sortie. 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, 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 du programme qui suit l'instruction qui a appelé la fonction.


== Technique ==
== Technique ==


Le principe est de créer une erreur (ou un crash) du programme en cours d'exécution. Pour cela, on va exploiter la mémoire du programme et en particulier la pile d’exécution. Un programme simple alloue 24 caractères dans un tableau et copie la chaîne de caractères passée en paramètre du programme dans ce tableau. La variable sera alors copiée dans la pile à partir de l'adresse de l'ESP, qui est l'adresse mémoire correspondant au sommet de la pile. Si on choisit de mettre 24 fois la lettre A dans cette variable, on pourrait représenter l'état de la pile d’exécution avec le schéma suivant :
D'un point de vue plus technique, la pile (stack en anglais) est une partie de la mémoire utilisée par l'application pour stocker ses variables locales. Nous allons utiliser l'exemple d'une architecture Intel (32 bits). Lors d'un appel à une sous-routine, le programme empile (push) le pointeur d'instruction (EIP) sur la pile (stack) et saute au code de la sous-routine pour l'exécuter. Après l'exécution, le programme dépile (pop) le pointer d'instruction et retourne juste après l'endroit où a été appelée la sous-routine, grâce à la valeur d'EIP. En effet, comme EIP pointe toujours vers l'instruction suivante, lors de l'appel de la sous-routine il pointait déjà vers l'instruction suivante, autrement dit l'instruction à exécuter après la sous-routine (= adresse de retour).


[[Fichier:Stack2.png]]
D'autre part, lors de l'appel de la sous-routine, celle-ci va dans la majorité des cas créer sa propre pile dans la pile (pour éviter de gérer des adresses compliquées). Pour cela elle va empiler la valeur de la base de la pile (EBP) et affecter la valeur du pointeur de pile (ESP) à celle de la base (EBP).


La pile d'exécution ne semble pas avoir de dysfonctionnement, les valeurs de la variables sont stockées entre l'adresse de l'ESP et l'adresse de l'EBP.
Si on souhaite maintenant passer en paramètre du programme une chaîne de taille supérieure à 24 caractères, le schéma suivant représenterait l'état de la pile d'exécution :


[[Fichier:EBP-ESP.PNG]]
[[Fichier:Stack3.png]]
* ESP : pointeur du sommet de la pile.
* EBP : (Extended Base Pointer) pointeur de la base de la pile
* ESP : (Extended Stack Pointer) pointeur du sommet de la pile


On remarque que toutes les adresses entre celles de l'EBP et l'ESP sont remplies, mais aussi celles de l'EBP et de l'EIP (adresse de l'instruction courante). Dans ce cas, lorsque le programme va se terminer et qu'on va passer par l'instruction correspondant à la valeur de retour du programme, l'EIP va donc pointer vers l'adresse AAAA qui, en hexadécimal, est 0x41414141. La probabilité de pouvoir accéder à cette adresse mémoire est quasi nulle, il se peut même que cette adresse ne soit pas mappée. Il est donc inévitable de se retrouver face à une erreur de segmentation.
En résumé, on sauvegarde la valeur originale de la base et on décale le tout ensuite. Lors du retour de la sous-routine, on dépile EBP et réaffecte sa valeur originale pour restaurer la pile initiale.


Pour exploiter cette faille, l'idée serait de rediriger l'adresse de l'EIP vers une partie du programme que l'on souhaiterai exécuter (en général, programme injecté dans la pile d'exécution). Il serait alors possible de faire presque tout ce qu'on voudrait sur la machine victime de l'attaque, comme par exemple ouvrir un shell et s'approprier tous les droits.
Voici pour le déroulement "normal" des opérations. Un point intéressant à citer est le fait que dans notre architecture, les zones mémoires allouées dans la stack se remplissent dans le sens croissant des adresses (de 0..0H à F..FH) ce qui semble logique. Par contre, l'empilement sur la stack s'effectue dans le sens décroissant! C'est-à-dire que l'ESB originale est l'adresse la plus grande et que le sommet est 0..0H. De là naît la possibilité d'écraser des données vitales et d'avoir un buffer overflow.
En effet, si notre buffer se trouve dans la pile d'une sous-routine et si nous le remplissons jusqu'à déborder sa taille allouée, nous allons écrire par-dessus les données qui se trouvent à la fin du buffer, c'est-à-dire les adresses qui ont été empilées précédemment : EBP, EIP... Une fois la routine terminée, le programme va dépiler EIP et sauter à cette adresse pour poursuivre son exécution. Le but est donc d'écraser EIP avec une adresse différente que nous pourrons utiliser pour accéder à une partie de code qui nous appartient. (par exemple le contenu du buffer)
Un problème à ce stade est de connaitre l'adresse exacte de la stack (surtout sous Windows) pour pouvoir sauter dedans. On utilise généralement des astuces propres à chaque système (librairies, etc..) qui vont permettre -indirectement- d'atteindre notre stack et d'exécuter notre code. Cela nécessite un débogage intensif qui n'est pas à la portée de tout le monde...


== Exemple d'attaque ==
== Exemple d'attaque ==
Ligne 65 : Ligne 59 :
Il est à noter que strcpy ne vérifie pas la taille du buffer lors de la copie. On va donc choisir une chaîne de caractères plus longue que celle du buffer, comme 200 octets.
Il est à noter que strcpy ne vérifie pas la taille du buffer lors de la copie. On va donc choisir une chaîne de caractères plus longue que celle du buffer, comme 200 octets.


Le principe de l'exemple va etre la mise en place d'un shellcode,
Si on tente d'afficher la chaîne de caractères, elle s'affichera correctement mais il y aura une erreur de segmentation car on essaie d'écrire dans une zone mémoire non autorisée en écriture.
c'est à dire executer un code machine qui lance un shell.

[[Fichier:EtatPileAvant.png]]

Si, on regarde la pile, on a tout en bas, les variables de la fonction main: ici le pointeur args, haut dessus, la sauvegarde eip, qu'on va vouloir remplacer, puis la sauvegarde epb dessus. Dessus, on arrive cette fois-ci sur l'espace dédié à lafonction func, avec en bas de cette espace, notre buffer.

[[Fichier:Shellcode.png]]

On peut deviner la taille de notre buffer (puisque ce que c'est ce qui déborde), on sait donc que on doit entrer un texte de la longueur du buffer, plus un octet pour remplir sebp, concaténer a l’adresse de ce qu'on veut exécuter.
Le contenu du texte contiendra le shellcode, qu'on met à la fin pour plus de facilité, et on complète au début avec des \x90, car c'est l'instruction qui, en assembleur, signifie "passer à l'instruction suivante".
on met une des adresse de la zone des x90 (l'avantage est qu'on a pas besoin d’être très précis), les x90 vont donc s’exécuter jusqu’à arriver au début du shellcode.
A savoir que si le buffer est trop petit pour le code à exécuter, on peut déborder encore plus, dans la mémoire de main, pour placer notre shellcode après seip.


= Attaques références =
= Attaques références =


* Morris worm
== Morris worm ==

* SQL Slammer
Le ver Morris a été écrit par Robert Tappan Morris en 1988. Sa principale caractéristique est de se propager sur une machine et de machines en machines.
* Format string attack

Il exploitait deux vulnérabilités dans sendmail (serveur de messagerie éléctronique open source) et dans fingerd (utilitaire permettant de connaitre l'heure de connexion sur un poste à distance). La faille de fingerd est une faille de type buffer overflow. Il permettait de prendre le contrôle de la machine à distance et d'accéder à l'utilitaire réseau de la machine. Il était alors possible de se connecter à d'autres machines depuis celles infectée, et de propager le ver grâce à la faille de sendmail. Il était possible, avec sendmail, en mode debug, d'envoyer des fichiers à distance par le shell. Le shell compilait le code source des fichiers. Il était donc possible d'envoyer le code source du ver Morris sur une machine distante.

Ce ver était aussi connu pour se diviser et ne laisser aucune traces de son passage, en effaçant les fichiers qui le contenait. Il pouvait aussi bloquer la création de processus sur la machine, ce qui rendait la machine inutilisable.

== SQL Slammer ==

SQL Slammer, tout comme le ver Morris, est un ver informatique. Il a été créé en 2003 et a provoqué des dénis de service (attaque empêchant l'utilisation d'un service informatique), qui entraînait un ralentissement du trafic internet. Le ver exploitait une faille de sécurité de Microsoft SQL Server.

Son principe est de générer des adresses IP de façon aléatoire, et d'envoyer à ces adresse IP son code source. Les machines qui recevaient le code pouvaient être infectées si elles n'avaient pas reçu le correctif de la faille Microsoft SQL Server. Ces machines, à leur tour, envoient le code source du ver à d'autres machines. Le ver se répand alors très rapidement.

Ce ver a causé des ralentissements internet importants. De nombreux routeurs sont tombés par l'infection de serveurs par le ver, ce qui avait pour conséquence des envoies massifs d'adresses du ver depuis les serveurs, et donc une saturation du trafic internet au niveau des routeurs.


= Solutions pour éviter l'attaque =
= Solutions pour éviter l'attaque =
Ligne 83 : Ligne 104 :


Les systèmes d'exploitations eux-mêmes peuvent intégré des dispositifs qui rendent ce genre d'attaque plus compliqué:
Les systèmes d'exploitations eux-mêmes peuvent intégré des dispositifs qui rendent ce genre d'attaque plus compliqué:
* [https://fr.wikipedia.org/wiki/Address_space_layout_randomization address space layout randomization (ASLR)]: placement aléatoire des différents éléments de la mémoire virtuelle.
* [https://fr.wikipedia.org/wiki/Address_space_layout_randomization address space layout randomization (ASLR)]: technique de placement aléatoire des différents éléments de la mémoire virtuelle.
* NX protection: technique qui consiste à ne pas donner les droits d’exécution qu'à la zone mémoire contenant les instructions du programme
* NX 
* Stack-Smashing Protector: ajoute une valeur aléatoire prés de la zone mémoire critique. Ainsi, si la valeur à changer, c'est qu'il y a eu un dépassement, et on peut lever une erreur
* stack canary


=Sources=
=Sources=
Ligne 110 : Ligne 131 :


https://www.veracode.com/security/buffer-overflow
https://www.veracode.com/security/buffer-overflow

https://beta.hackndo.com/buffer-overflow/

https://beta.hackndo.com/technique-du-canari-bypass/

Dernière version du 26 novembre 2018 à 01:24

Auteurs : Olivier STHIOUL et Ludovic MILLON

Introduction

L'attaque par Buffer Overflow (dépassement de tampon mémoire) est l'exploitation d'une faille de mémoire, durant laquelle l'écriture en mémoire du buffer dépasse son espace mémoire alloué. Le processus subit alors une modification des informations nécessaires au fonctionnement du programme. Ces attaques ciblent le plus souvent des sites web, des applications de bureau ou même le système d'exploitation du serveur qui héberge le site web.

La plupart des failles se trouvent (pour un site web), dans les modules installés sur le serveur comme mod_php ou mod_ssl. Il y a par exemple la possibilité de désactiver des fonctions php dans son fichier de configuration. Par défaut, certaines de ces fonctions sont désactivées car elles ont été reconnues comme comportant des failles de sécurités.

L'attaque a pour objectif de faire crasher le programme ou l'application et d'exploiter une faille, en remplaçant, par exemple, le code source du programme. Un de ses avantages est qu'il n'y a pas besoin d'avoir accès à la machine victime.

Ce type d'attaque nécessite des compétences pointilleuses en informatique et particulièrement en sécurité informatique, et en code assembleur.

Principe de l'attaque

Comme l'attaque Buffer Overflow se base principalement sur la mémoire, il est important d'avoir quelques notions sur la pile d'exécution et sur le code assembleur.

Rappels sur la pile d'exécution

On peut représenter un processus ainsi que sa pile d'exécution par les schémas suivants :

Pile execution.PNG Cadre pile.PNG

Les zones text et data contiennent respectivement le code source du programme et ses données statiques. Le tas est la zone dans laquelle sont stockées toutes les données allouées dynamiquement.

Dans ce schéma, les adresses sont classées de haut en bas mais les valeurs sont empilées en haut car il s’agit d’une pile. Les différentes zones (en partant du bas du schéma) sont détaillées ci-dessous :

  • les paramètres supplémentaires sont ceux passés à la fonction en cours d’exécution : ils correspondent à tous les paramètres après le 6ème passé dans la fonction car les 6 premiers sont placés dans le registre
  • l’adresse de retour est l’adresse de l’instruction de la fonction en cours d'exécution (ex: return) ;
  • le %rbp « précédent » correspond au pointeur de base de la fonction appelante pour les assembleurs 64 bits (l'équivalent en système 32 bits est l'EBP ;
  • les variables locales sont celles de la fonction en cours d’exécution, stockées dans sa pile, à des adresses toujours inférieurs et immédiates au %rbp ;
  • des valeurs temporaires peuvent aussi être stockées ;
  • à chaque appels de fonctions, le même schéma est répété

La pile d'exécution correspond donc à la zone mémoire allouée pour chaque fonction du programme en cours d'exécution. Son comportement est le même que celui d'une pile : dernier entré premier sortie. 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.

Technique

Le principe est de créer une erreur (ou un crash) du programme en cours d'exécution. Pour cela, on va exploiter la mémoire du programme et en particulier la pile d’exécution. Un programme simple alloue 24 caractères dans un tableau et copie la chaîne de caractères passée en paramètre du programme dans ce tableau. La variable sera alors copiée dans la pile à partir de l'adresse de l'ESP, qui est l'adresse mémoire correspondant au sommet de la pile. Si on choisit de mettre 24 fois la lettre A dans cette variable, on pourrait représenter l'état de la pile d’exécution avec le schéma suivant :

Stack2.png

La pile d'exécution ne semble pas avoir de dysfonctionnement, les valeurs de la variables sont stockées entre l'adresse de l'ESP et l'adresse de l'EBP. Si on souhaite maintenant passer en paramètre du programme une chaîne de taille supérieure à 24 caractères, le schéma suivant représenterait l'état de la pile d'exécution :

Stack3.png

On remarque que toutes les adresses entre celles de l'EBP et l'ESP sont remplies, mais aussi celles de l'EBP et de l'EIP (adresse de l'instruction courante). Dans ce cas, lorsque le programme va se terminer et qu'on va passer par l'instruction correspondant à la valeur de retour du programme, l'EIP va donc pointer vers l'adresse AAAA qui, en hexadécimal, est 0x41414141. La probabilité de pouvoir accéder à cette adresse mémoire est quasi nulle, il se peut même que cette adresse ne soit pas mappée. Il est donc inévitable de se retrouver face à une erreur de segmentation.

Pour exploiter cette faille, l'idée serait de rediriger l'adresse de l'EIP vers une partie du programme que l'on souhaiterai exécuter (en général, programme injecté dans la pile d'exécution). Il serait alors possible de faire presque tout ce qu'on voudrait sur la machine victime de l'attaque, comme par exemple ouvrir un shell et s'approprier tous les droits.

Exemple d'attaque

Le code ci-dessous stimule une attaque buffer overflow :

Buffer overflow c.PNG

La fonction func alloue 64 octets sur la pile. Le programme prend en entrée un argument (un string) et la place dans le buffer. Il est à noter que strcpy ne vérifie pas la taille du buffer lors de la copie. On va donc choisir une chaîne de caractères plus longue que celle du buffer, comme 200 octets.

Le principe de l'exemple va etre la mise en place d'un shellcode, c'est à dire executer un code machine qui lance un shell.

EtatPileAvant.png

Si, on regarde la pile, on a tout en bas, les variables de la fonction main: ici le pointeur args, haut dessus, la sauvegarde eip, qu'on va vouloir remplacer, puis la sauvegarde epb dessus. Dessus, on arrive cette fois-ci sur l'espace dédié à lafonction func, avec en bas de cette espace, notre buffer.

Shellcode.png

On peut deviner la taille de notre buffer (puisque ce que c'est ce qui déborde), on sait donc que on doit entrer un texte de la longueur du buffer, plus un octet pour remplir sebp, concaténer a l’adresse de ce qu'on veut exécuter.

Le contenu du texte contiendra le shellcode, qu'on met à la fin pour plus de facilité, et on complète au début avec des \x90, car c'est l'instruction qui, en assembleur, signifie "passer à l'instruction suivante".

on met une des adresse de la zone des x90 (l'avantage est qu'on a pas besoin d’être très précis), les x90 vont donc s’exécuter jusqu’à arriver au début du shellcode.

A savoir que si le buffer est trop petit pour le code à exécuter, on peut déborder encore plus, dans la mémoire de main, pour placer notre shellcode après seip.

Attaques références

Morris worm

Le ver Morris a été écrit par Robert Tappan Morris en 1988. Sa principale caractéristique est de se propager sur une machine et de machines en machines.

Il exploitait deux vulnérabilités dans sendmail (serveur de messagerie éléctronique open source) et dans fingerd (utilitaire permettant de connaitre l'heure de connexion sur un poste à distance). La faille de fingerd est une faille de type buffer overflow. Il permettait de prendre le contrôle de la machine à distance et d'accéder à l'utilitaire réseau de la machine. Il était alors possible de se connecter à d'autres machines depuis celles infectée, et de propager le ver grâce à la faille de sendmail. Il était possible, avec sendmail, en mode debug, d'envoyer des fichiers à distance par le shell. Le shell compilait le code source des fichiers. Il était donc possible d'envoyer le code source du ver Morris sur une machine distante.

Ce ver était aussi connu pour se diviser et ne laisser aucune traces de son passage, en effaçant les fichiers qui le contenait. Il pouvait aussi bloquer la création de processus sur la machine, ce qui rendait la machine inutilisable.

SQL Slammer

SQL Slammer, tout comme le ver Morris, est un ver informatique. Il a été créé en 2003 et a provoqué des dénis de service (attaque empêchant l'utilisation d'un service informatique), qui entraînait un ralentissement du trafic internet. Le ver exploitait une faille de sécurité de Microsoft SQL Server.

Son principe est de générer des adresses IP de façon aléatoire, et d'envoyer à ces adresse IP son code source. Les machines qui recevaient le code pouvaient être infectées si elles n'avaient pas reçu le correctif de la faille Microsoft SQL Server. Ces machines, à leur tour, envoient le code source du ver à d'autres machines. Le ver se répand alors très rapidement.

Ce ver a causé des ralentissements internet importants. De nombreux routeurs sont tombés par l'infection de serveurs par le ver, ce qui avait pour conséquence des envoies massifs d'adresses du ver depuis les serveurs, et donc une saturation du trafic internet au niveau des routeurs.

Solutions pour éviter l'attaque

plusieurs solutions s'offrent au développeurs:

  • utiliser des langages haut-niveau qui intègre une gestion complète de la mémoire. Ex: Java, Cyclone...
  • utiliser des librairies et des fonctions sécurisés (fonction strncpy()..., librairie Libsafe) et éviter scanf / strcpy / gets
  • tester son code avec des logiciels spécialisé comme Qaudit ou Flawfinder
  • Appliquer rapidement les correctifs
  • CerberHost (https://www.youtube.com/watch?v=q1HODJaMY5M)

Les systèmes d'exploitations eux-mêmes peuvent intégré des dispositifs qui rendent ce genre d'attaque plus compliqué:

  • address space layout randomization (ASLR): technique de placement aléatoire des différents éléments de la mémoire virtuelle.
  • NX protection: technique qui consiste à ne pas donner les droits d’exécution qu'à la zone mémoire contenant les instructions du programme
  • Stack-Smashing Protector: ajoute une valeur aléatoire prés de la zone mémoire critique. Ainsi, si la valeur à changer, c'est qu'il y a eu un dépassement, et on peut lever une erreur

Sources

https://fr.wikipedia.org/wiki/D%C3%A9passement_de_tampon

https://www.securiteinfo.com/attaques/hacking/buff.shtml

http://www.student.montefiore.ulg.ac.be/~blaugraud/node2.html

https://www.nbs-system.com/blog/cerberhost-les-attaques-de-type-buffer-overflow/

https://zestedesavoir.com/articles/143/exploitez-votre-premier-stack-based-overflow/

https://www.consultingit.fr/fr/buffer-overflow-attack-example-website-attaques-systemes-par-buffer-overflow-stack-overflow

https://web.maths.unsw.edu.au/~lafaye/CCM/attaques/buffer-overflow.htm

http://chamilo2.grenet.fr/inp/courses/ENSIMAG3MM1LDB/document/asm_fonctions.pdf

https://fr.wikibooks.org/wiki/Programmation_Assembleur/x86/Registres

https://insecure.org/stf/smashstack.html

https://www.veracode.com/security/buffer-overflow

https://beta.hackndo.com/buffer-overflow/

https://beta.hackndo.com/technique-du-canari-bypass/