La mémoire


<< Segmentation de la mémoire Les buffer-overflows >>


IV°) Illustration de l'utilisation de la mémoire d'un programme

   Nous allons prendre en exemple le court programme en C suivant :
    //exemple.c : exemple de déclarations et de mise en mémoire

    int variable_globale = 1;
    char variable_globale2;

    void fonction(int entier1, int entier2, char caractere) {

      char variable_interne;

      char buffer[10];

      //Corps de la fonction

    }

    int main() {

      int entier;
      entier = 24;

      fonction(entier,variable_globale,variable_globale2);

      //Corps du programme

      return 0;

    }
Dans un programme en C, la fonction principale qui est éxécutée en premier est la fonction main(). Les variables déclarées en dehors du corps de toute fonction sont les variables globales, accessibles par n'importe quelle fonction et n'importe où dans le programme. Par conséquent, après la création des variables globales, la fonction main() s'éxécute. Ensuite, on fait appel à la fonction "fonction". Cette fonction prend en arguments deux entiers et un caractère. Elle possède elle-même deux variables locales créées pendant l'appel de la fonction. Le type void de la fonction signifie qu'elle ne renvoie aucune variable.
Nous allons maintenant traduire ce programme en termes d'occupation de la mémoire :
  1. Le code ici présent est traduit en code machine et implémenté dans le text
  2. On réserve l'espace pour les variables globales. Ici, on a la variable "variable_globale" de type entier qui est initialisée dès le début du programme. Cette variable sera donc ajoutée au data. La variable "variable_globale2" de type caractère n'est pas initialisée, elle ira donc dans le bss.
  3. Les choses sérieuses commencent : la fonction "fonction" est appellée... On commence donc à remplir la pile. Tout d'abord, il faut sauvegarder les variables qui sont passées en argument, à savoir le caractère puis l'entier2 puis l'entier1. On sauvegarde la prochaine adresse de l'EIP et on la push sur la pile en tant que return address. On sauvegarde l'adresse de l'EBP et on l'ajoute à la pile en tant que stack frame pointer. La position des variables du second bloc étant sauvegardée, on peut affecter à l'EBP sa nouvelle valeur. Enfin, les variables locales de la fonction sont ajoutées à la pile dans l'ordre, donc "variable_interne" puis "buffer". Ce moment est schématisé ci-dessous.
  4. Pour finir, on dépile le bloc de la fonction qui a été appellée, EBP peut reprendre sa valeur pré-appel et le pointeur return address est affecté à l'EIP
Pour compléter cette explication, voici deux schémas, l'un représentant la mémoire du programme dans sa totalité et l'autre l'état de la pile après l'étape 3 :
illustration segmentation et pile en mémoire


Comme explicité plus tôt, on comprend maintenant pourquoi le heap et le stack grandissent dans des directions opposées : étant tous les deux dynamiques, ils grandissent l'un vers l'autre et minimisent ainsi l'espace perdu et la possibilité de chevauchement des segments (qui aurait été beaucoup plus probable si les deux segments grandissaient dans le même sens). Voilà, vous savez désormais le fonctionnement théorique de la mémoire et comment sont placées les variables dans les différents segments. Nous pouvons enfin rentrer dans le vif du sujet et attaquer les mystiques buffer-overflows, accrochez-vous !

<< Segmentation de la mémoire Les buffer-overflows >>



19 Commentaires
Afficher tous


FrizN 06/01/14 11:03
On accède au premier argument à ebp+8, au deuxième à ebp+12, etc. On accède donc au i-ème argument par ebp + 8 + 4*i

Si les arguments étaient rangés dans l'autre sens, il faudrait connaitre le nombre d'arguments, le premier serait par exemple à ebp + 8 + (nb arg - 1)*4, donc sans connaître nbarg on ne pourrait pas retrouver les arguments.

Anonyme 06/01/14 00:33
Merci, c'est plus clair.

Cependant, "on ne connait pas le nombre d'arguments passés a priori", et alors, je ne comprends pas où est le problème ?

FrizN 05/01/14 18:34
2. Convention encore une fois, mais c'est également la manière logique de faire, puisque pour certaines fonctions on ne connait pas le nombre d'arguments passés a priori (c'est par exemple le cas de printf).

FrizN 05/01/14 18:34
1. Les variables locales sont à ebp - X et les arguments commencent à partir de ebp + 8, oui. L'ebp sauvegardé est bien une donnée locale à la fonction en cours (lié à son site d'appel tout comme l'adresse de retour). Il faut bien garder en tête que tout ceci n'est que convention omissible, et seulement valable pour les archi intel.




Commentaires désactivés.

Apprendre la base du hacking - Liens sécurité informatique/hacking - Contact

Copyright © Bases-Hacking 2007-2014. All rights reserved.