Bonjour à tous.
Dans ce tutoriel nous allons voir ce qu'est un Buffer Overflow et comment l'exploiter succintement. Nous verrons aussi comment fonctionne la mémoire au sein d'un programme.
Les dépassements mémoires sont très largement utilisés à notre époque en ce qui concerne le domaine des failles applicatives. En effet, l'accès à la mémoire d'un programme permet de le modifier, de détourner ses flux de données, d'obtenir des droits (bit SUID) etc etc.
I) La mémoire
A) Définition et rappels
Tout d'abord, qu'est-ce que la mémoire ?
Lorsqu'un programme s'exécute cela correspond à une série d'opération réalisées par l'ordinateur. Les valeurs des variables utilisées, les résultats et les valeurs temporaires des opérations sont stockés afin d'être réutilisés plus tard. Chacune valeur de ces valeurs correspond à un octet (codé sur 8 bits), et à chacun de ces octets correspond une adresse mémoire unique. Ainsi, il est possible d'accéder à ces valeurs en utilisant leur adresse en mémoire. Une adresse est toujours unique pour une valeur unique afin d'éviter les collisions.
Ces adresses peuvent être de format différent dépendant de l'architecture du processeur utilisé. Ainsi, pour un processeur 32 bits, l'adresse sera une séquence numérique composée de 32 chiffres. Pour un processeur 64 bits, l'adresse sera une séquence numérique composée de 64 chiffres.
Rappel: 1 octet = 8 bits, donc 32 bits = 4 octets
Chaque composant physique possède un bloc mémoire spécifique, généralement de petite taille.
Le processeur possède un bloc mémoire spécifique primordial puisqu'il permet d'enregistrer les évènements générés lors de l'exécution d'une routine, d'un service ou d'un programme. Ces évènements enregistrés sont ensuite stockés puisqu'ils sont utilisés dans le cas des diagnostics systèmes réalisés dans le cas d'un crash du programme par exemple.
Ce bloc mémoire est divisé en secteurs que l'on appelle les registres. Il existe plusieurs registres, mais seuls trois sont vraiment important pour le processeur.
Le premier, et le plus important, est l'EIP pour Extended Instruction Pointer ; il permet d'enregistrer les différentes adresses mémoire des instructions générées par le programme. Ensuite vient l'ESP pour Extended Stack Pointer et l'EBP pour Extended Base Pointer.
Nous parlons donc maintenant de pointeur. Mais qu'est-ce qu'un pointeur ?
Même pour les programmeurs chevronnés, il est toujours bon de rappeler ce qu'ils sont et leur utilité.
Un pointeur permet de stocker une adresse mémoire afin qu'elle soit utilisée par une fonction dans un programme. Pour réutiliser un bloc mémoire il est nécessaire de le recopier ; en effet, la mémoire est statique, elle ne peut être déplacée manuellement. Le pointeur permet donc de passer l'adresse mémoire rapidement quand une fonction en a besoin. On gagne ainsi de la vitesse et du temps.
B ) Répartition de la mémoire
Il existe différentes zones composant le bloc mémoire ; on dit que la mémoire est segmentée.
Ces zones sont au nombre de cinq. Le schéma suivant illustre la répartition de ces cinq zones:
Ces cinq zones sont donc (dans l'ordre) le text, le data, le bss, le heap et le stack.
La zone "text" permet de stocker les instructions que le programme exécutera au lancement. Quand le programme est lancé, le registre EIP commence à lire le début de ce segment puis évolue au long de l'exécution. Cette zone n'est pas accessible en écriture car les instructions du programmes ne peuvent pas être modifiées !
La zone "data" permet de stocker la valeur d'initialisation des variables globales utilisées par le programme. Cette zone est accessible en écriture mais non modifiable en espace de stockage.
La zone "bss" permet de stocker la valeur d'initialisation des variables statiques utilisées par le programme. Cette zone est accessible en écriture mais non modifiable en espace de stockage.
La zone "heap" permet de stocker la valeur des variables dynamiques utilisées par le programme. Cette zone est accessible en écriture et modifiable en espace de stockage.
La zone "stack" (pile) permet de stocker des valeurs temporaires, des résultats de transition. Quand une fonction utilise une variable, sa valeur est stockée dans le stack puis libérée ensuite. La pile permet donc de stocker des valeurs. Cependant, l'ordinateur a besoin de connaitre différentes choses au sein de la pile: son point d'entrée, la position des valeurs dans la pile etc etc. C'est à ça que vont servir les trois registres du processeur cités plus haut.
Le registre ESP permet de connaitre le point d'entrée de la pile, donc le "haut" de la pile. Tandis que le registre EBP permet de localiser une valeur dans la pile afin de la lire, l'extraire ou autre.
II) Buffer Overflow
Le Buffer Overflow est le dépassement de la mémoire allouée initialement lors de l'exécution d'un programme. En effet, quand l'on surcharge un programme en lui passant un argument trop grand pour la mémoire allouée, cela va entraîner une corruption du bloc et des différentes variables qui s'y trouvent. Généralement cela permet de crasher le programme ou d'accéder à des zones mémoires non-autorisées.
Voilà un exemple très simple de Buffer Overflow écrit en C:
On définit un tableau (bufferMDP) pouvant contenir 10 caractères.
Exécutons le programme:
Dans le deuxième cas, on rentre un mot de passe supérieur à la taille du tableau, la mémoire est donc corrompue et le programme crash avec une erreur (généralement l'erreur comporte l'adresse de la corruption).
Cette exemple a créé un Buffer Overflow dans la pile, ce que l'on appelle un stack-based overflow, qui consiste a réécrire la mémoire allouée afin de déborder sur des instructions privées et donc de réécrire leurs valeurs. Il existe des Buffer Overflow pour chaque zone mémoire: heap-based overflow, bss based-overflow...
Comment s'accorder les privilèges root par un stack-based overflow ?
Tout simplement en passant un shell (il en existe des milliers sur Internet) argumentaire supérieur à la taille du tableau alloué. Ce shell sera réécrit sur les emplacements mémoire privés et sera donc exécuté par le programme. On peut de cette façon s'accorder le bit SUID afin d'avoir les privilèges maximaux sur le programme. Par exemple, un buffer overflow sur le terminal de commande linux nous permettrait de l'exécuter en privilège root (sudo).
Dans ce tutoriel nous allons voir ce qu'est un Buffer Overflow et comment l'exploiter succintement. Nous verrons aussi comment fonctionne la mémoire au sein d'un programme.
Les dépassements mémoires sont très largement utilisés à notre époque en ce qui concerne le domaine des failles applicatives. En effet, l'accès à la mémoire d'un programme permet de le modifier, de détourner ses flux de données, d'obtenir des droits (bit SUID) etc etc.
I) La mémoire
A) Définition et rappels
Tout d'abord, qu'est-ce que la mémoire ?
Lorsqu'un programme s'exécute cela correspond à une série d'opération réalisées par l'ordinateur. Les valeurs des variables utilisées, les résultats et les valeurs temporaires des opérations sont stockés afin d'être réutilisés plus tard. Chacune valeur de ces valeurs correspond à un octet (codé sur 8 bits), et à chacun de ces octets correspond une adresse mémoire unique. Ainsi, il est possible d'accéder à ces valeurs en utilisant leur adresse en mémoire. Une adresse est toujours unique pour une valeur unique afin d'éviter les collisions.
Ces adresses peuvent être de format différent dépendant de l'architecture du processeur utilisé. Ainsi, pour un processeur 32 bits, l'adresse sera une séquence numérique composée de 32 chiffres. Pour un processeur 64 bits, l'adresse sera une séquence numérique composée de 64 chiffres.
Rappel: 1 octet = 8 bits, donc 32 bits = 4 octets
Chaque composant physique possède un bloc mémoire spécifique, généralement de petite taille.
Le processeur possède un bloc mémoire spécifique primordial puisqu'il permet d'enregistrer les évènements générés lors de l'exécution d'une routine, d'un service ou d'un programme. Ces évènements enregistrés sont ensuite stockés puisqu'ils sont utilisés dans le cas des diagnostics systèmes réalisés dans le cas d'un crash du programme par exemple.
Ce bloc mémoire est divisé en secteurs que l'on appelle les registres. Il existe plusieurs registres, mais seuls trois sont vraiment important pour le processeur.
Le premier, et le plus important, est l'EIP pour Extended Instruction Pointer ; il permet d'enregistrer les différentes adresses mémoire des instructions générées par le programme. Ensuite vient l'ESP pour Extended Stack Pointer et l'EBP pour Extended Base Pointer.
Nous parlons donc maintenant de pointeur. Mais qu'est-ce qu'un pointeur ?
Même pour les programmeurs chevronnés, il est toujours bon de rappeler ce qu'ils sont et leur utilité.
Un pointeur permet de stocker une adresse mémoire afin qu'elle soit utilisée par une fonction dans un programme. Pour réutiliser un bloc mémoire il est nécessaire de le recopier ; en effet, la mémoire est statique, elle ne peut être déplacée manuellement. Le pointeur permet donc de passer l'adresse mémoire rapidement quand une fonction en a besoin. On gagne ainsi de la vitesse et du temps.
B ) Répartition de la mémoire
Il existe différentes zones composant le bloc mémoire ; on dit que la mémoire est segmentée.
Ces zones sont au nombre de cinq. Le schéma suivant illustre la répartition de ces cinq zones:
Ces cinq zones sont donc (dans l'ordre) le text, le data, le bss, le heap et le stack.
La zone "text" permet de stocker les instructions que le programme exécutera au lancement. Quand le programme est lancé, le registre EIP commence à lire le début de ce segment puis évolue au long de l'exécution. Cette zone n'est pas accessible en écriture car les instructions du programmes ne peuvent pas être modifiées !
La zone "data" permet de stocker la valeur d'initialisation des variables globales utilisées par le programme. Cette zone est accessible en écriture mais non modifiable en espace de stockage.
La zone "bss" permet de stocker la valeur d'initialisation des variables statiques utilisées par le programme. Cette zone est accessible en écriture mais non modifiable en espace de stockage.
La zone "heap" permet de stocker la valeur des variables dynamiques utilisées par le programme. Cette zone est accessible en écriture et modifiable en espace de stockage.
La zone "stack" (pile) permet de stocker des valeurs temporaires, des résultats de transition. Quand une fonction utilise une variable, sa valeur est stockée dans le stack puis libérée ensuite. La pile permet donc de stocker des valeurs. Cependant, l'ordinateur a besoin de connaitre différentes choses au sein de la pile: son point d'entrée, la position des valeurs dans la pile etc etc. C'est à ça que vont servir les trois registres du processeur cités plus haut.
Le registre ESP permet de connaitre le point d'entrée de la pile, donc le "haut" de la pile. Tandis que le registre EBP permet de localiser une valeur dans la pile afin de la lire, l'extraire ou autre.
II) Buffer Overflow
Le Buffer Overflow est le dépassement de la mémoire allouée initialement lors de l'exécution d'un programme. En effet, quand l'on surcharge un programme en lui passant un argument trop grand pour la mémoire allouée, cela va entraîner une corruption du bloc et des différentes variables qui s'y trouvent. Généralement cela permet de crasher le programme ou d'accéder à des zones mémoires non-autorisées.
Voilà un exemple très simple de Buffer Overflow écrit en C:
Code:
#include <stdio.h> int main() { char bufferMDP[10]; printf("Choisissez un mot de passe pour votre compte: "); scanf("%s",&bufferMDP); printf("\nMot de passe choisi: %s",bufferMDP); return 0; }
On définit un tableau (bufferMDP) pouvant contenir 10 caractères.
Exécutons le programme:
Code:
-> "Choisissez un mot de passe: "
-> azerty1234
-> "Mot de passe choisi: azerty1234"
-> Programme fermé correctement.
-> "Choisissez un mot de passe: "
-> azerty123456789
-> "Mot de passe choisi: azerty123456789"
-> Erreur de segmentation 0x80000 le programme ne s'est pas fermé correctement.
Dans le deuxième cas, on rentre un mot de passe supérieur à la taille du tableau, la mémoire est donc corrompue et le programme crash avec une erreur (généralement l'erreur comporte l'adresse de la corruption).
Cette exemple a créé un Buffer Overflow dans la pile, ce que l'on appelle un stack-based overflow, qui consiste a réécrire la mémoire allouée afin de déborder sur des instructions privées et donc de réécrire leurs valeurs. Il existe des Buffer Overflow pour chaque zone mémoire: heap-based overflow, bss based-overflow...
Comment s'accorder les privilèges root par un stack-based overflow ?
Tout simplement en passant un shell (il en existe des milliers sur Internet) argumentaire supérieur à la taille du tableau alloué. Ce shell sera réécrit sur les emplacements mémoire privés et sera donc exécuté par le programme. On peut de cette façon s'accorder le bit SUID afin d'avoir les privilèges maximaux sur le programme. Par exemple, un buffer overflow sur le terminal de commande linux nous permettrait de l'exécuter en privilège root (sudo).
Commentaire