Вы находитесь на странице: 1из 7

Gestion de la mémoire - hackndo https://beta.hackndo.

com/memory-allocation/

hackndo

Gestion de la mémoire
11 Jan 2015 · 10 min Auteur : Pixis

User Land Linux

Dans cet article


» Mémoire virtuelle
» Segmentation de la mémoire
» Registres

Aujourd’hui, je vais tenter de rassembler tout ce que j’ai pu comprendre sur la gestion de la
mémoire lors de l’exécution d’un programme. Cet article est écrit en vu de comprendre
l’exploitation de certaines failles applicatives, telles que le buffer overflow, le heap overflow
ou encore la format string, failles que je décrirai dans les prochains articles.

Mémoire virtuelle
Les processus tournant sur une machine ont besoin de mémoire, et dans un ordinateur, la
quantité de mémoire est limitée. Il faut donc que les processus aillent chercher de la
mémoire disponible pour pouvoir travailler. Cependant, les processus tournent de nos jours
dans des systèmes d’exploitation multi-tâches. Plusieurs processus s’exécutent en même
temps. Que se passerait-il si deux processus voulaient accéder, au même instant, à la
même zone mémoire ? Et surtout, si jamais un processus écrivait dans une zone mémoire,

1 sur 9 10/03/2020 à 16:24


Gestion de la mémoire - hackndo https://beta.hackndo.com/memory-allocation/

puis un autre processus écrasait cette même zone mémoire avec ses propres données,
alors le processus A, le pauvre, pensera retrouver ses données, mais il trouvera en fait les
données de B. Et là, c’est le drame ! Il faudrait alors que les processus communiquent en
permanence entre eux pour savoir qui fait quoi, où et quand. Ce serait une vraie perte de
temps et d’une complexité effroyable pour ce problème.

C’est là qu’intervient la mémoire virtuelle : Les processus ne vont plus piocher directement
dans la mémoire physique. On les met dans des bacs à sable (sand box), en leur allouant
une plage de mémoire virtuelle (de 4Go pour les machines 32 bits), en leur faisant croire
qu’ils sont les seuls à s’exécuter sur la machine. C’est alors que le kernel intervient, et
effectue le lien entre les différentes plages de mémoires virtuelles et la mémoire réelle.
Ceci est fait par le biais de tables de pages (page tables). Voici un schéma pour y voir plus
clair :

Le processus n’a alors plus à se soucier de l’implémentation de la mémoire. Toutes les


opérations bas niveau sont gérées par le noyau de l’OS. C’est une sorte de couche
d’abstraction qui simplifie la vie du processus.

2 sur 9 10/03/2020 à 16:24


Gestion de la mémoire - hackndo https://beta.hackndo.com/memory-allocation/

Chaque processus a sa propre table de pages. Cependant, si l’adressage virtuel est activé,
il s’applique à tous les programmes qui tournent sur la machine, kernel compris. Ainsi, il
faut réserver une portion de l’espace virtuel de chaque programme pour le noyau !

Segmentation de la mémoire
Ainsi, nous allons voir ici comment est segmentée la mémoire d’un programme compilé
lorsqu’il est chargé en mémoire afin de créer un processus (Son image, une sorte
d’instance, si ça vous parle).

On retrouve les 3 sections suivantes :

1. Texte (.text)
2. Données (.data)
3. bss (.bss)

Et les 2 zones mémoire suivantes :

1. Tas (heap)
2. Pile (stack)

Chacune de ces zones représente une partie de la mémoire allouée au processus en


question.

Rapidement, la première section texte (.text) est celle qui contient le code du programme,
et plus exactement les instructions en langage machine. C’est une section en lecture seule,
une fois qu’elle a été définie, elle est immuable. Elle sert seulement à stocker du code, pas
des variables. Des erreurs de programmation peuvent entraîner cette fameuse erreur :

3 sur 9 10/03/2020 à 16:24


Gestion de la mémoire - hackndo https://beta.hackndo.com/memory-allocation/

“Segmentation Fault”, qui indique à l’utilisateur qu’une écriture non autorisée a tenté d’être
faite dans cette zone mémoire.

Du fait de son immuabilité, c’est une zone mémoire de taille fixe. Le programme démarrera
donc au début de ce segment, puis il va lire les instructions une par une. Cependant, cette
lecture n’est pas linéaire. En effet, avec le code haut niveau que nous produisons, il existe
beaucoup de structures de contrôles qui engendrent des appels à des bout de code qui ne
sont pas les uns à la suite des autres. On expliquera par la suite comment l’exécution du
programme fonctionne, notamment avec l’aide des registres.

La section de données (data) et la section bss stockent les variables globales et statiques
du programme. Si ces données sont initialisées, elles sont enregistrées dans la section
data, tandis que les autres sont dans la section bss. Ce sont également des zones
mémoires de taille fixe. Malgré la possibilité en écriture, les variables finales et statiques ne
changeront pas au cours de l’exécution du programme ou du contexte. C’est parce qu’elles
sont dans cette zone mémoire qu’elles peuvent persister.

Nous pouvons prendre un exemple en C. Soit le programme suivant, vide. examinons la


taille de ses différentes sections.

#include <stdio.h>

int main(void) {
return 0;
}

hackndo@becane:~/exemples$ gcc memory.c -o memory


hackndo@becane:~/exemples$ size memory

text data bss dec hex filename


1073 560 8 1641 669 memory

Maintenant, ajoutons une variable globale non initialisée et étudions les tailles des
différentes sections à nouveau

#include <stdio.h>

int global;

int main(void) {
return 0;
}

hackndo@becane:~/exemples$ gcc memory.c -o memory


hackndo@becane:~/exemples$ size memory

text data bss dec hex filename

4 sur 9 10/03/2020 à 16:24


Gestion de la mémoire - hackndo https://beta.hackndo.com/memory-allocation/

1073 560 12 1641 669 memory

On remarque que la section bss a augmenté de 4 octets pour stocker la variable statique
non-initialisée. Si de la même manière on ajoute une variable statique à l’intérieur de la
fonction main()

#include <stdio.h>

int global;

int main(void) {
static int var;
return 0;
}

hackndo@becane:~/exemples$ gcc memory.c -o memory


hackndo@becane:~/exemples$ size memory

text data bss dec hex filename


1073 560 16 1641 669 memory

Encore une fois, on remarque que bss a augmenté de 4 octets pour stocker cette variable.
Si maintenant on initialise la variable var

#include <stdio.h>

int global;

int main(void) {
static int var = 10;
return 0;
}

hackndo@becane:~/exemples$ gcc memory.c -o memory


hackndo@becane:~/exemples$ size memory

text data bss dec hex filename


1073 564 12 1641 669 memory

Cette fois-ci, la variable n’est plus stockée dans la section bss, mais dans la section data,
puisqu’on remarque qu’elle est passée de 560 à 564 alors que la section bss a diminué de
4 octets. Enfin, si on initialise également la variable globale global

#include <stdio.h>

int global = 200;

int main(void) {
static int var = 10;
return 0;

5 sur 9 10/03/2020 à 16:24


Gestion de la mémoire - hackndo https://beta.hackndo.com/memory-allocation/

hackndo@becane:~/exemples$ gcc memory.c -o memory


hackndo@becane:~/exemples$ size memory

text data bss dec hex filename


1073 568 8 1641 669 memory

Les deux variables sont stockées dans la section data, et non plus dans la section bss.

Le tas (heap) est, quant à lui, manipulable par le programmeur. C’est la zone dans laquelle
sont écrites les zones mémoires allouées dynamiquement (malloc() ou calloc()). Tout
comme la pile, cette zone mémoire n’a pas de taille fixe. Elle augmente et diminue en
fonction des demandes du programmeur, qui peut réserver ou supprimer des blocs via des
algorithmes d’allocation ou de libération pour une utilisation future. Plus la taille du tas
augmente, plus les adresses mémoires augmentent, et s’approchent des adresses
mémoires de la pile. La taille des variables dans le tas n’est pas limitée (sauf limite
physique de la mémoire), contrairement à la pile.

Par ailleurs, les variables stockées dans le tas sont accessibles partout dans le
programme, par l’intermédiaire des pointeurs. Cependant, l’accès aux variables stockées
dans le tas ne se faisant qu’avec des pointeurs, cela ralentit un peu ces accès,
contrairement aux accès dans la pile.

La pile (stack) possède également une taille variable, mais plus sa taille augmente, plus
les adresses mémoires diminuent, en s’approchant du haut du tas. C’est ici qu’on retrouve
les variables locales des fonctions ainsi que le cadre de pile (stack frame) de ces fonctions.
La stack frame d’une fonction est une zone mémoire, dans la pile, dans laquelle toutes les
informations, nécessaires à l’appel de cette fonction, sont stockées. S’y trouvent également
les variables locales de la fonction.

Vous avez donc j’espère une idée plus claire de la segmentation de la mémoire lors de
l’exécution d’un programme. Cependant il manque une notion importante qui est la gestion
des registres. En expliquant leur fonctionnement et leur utilité, nous seront à même de
mieux comprendre la notion de stack frame.

Registres
Les registres sont des emplacements mémoire qui sont à l’intérieur du processeur. Or dans
un ordinateur, les emplacements mémoire les plus proches du processeur sont ceux à qui
il est le plus rapide d’accéder, mais également les plus chers. Ainsi, plus on s’éloigne du
processeur, plus les accès sont longs, mais les coûts sont faibles. Les registres sont les
emplacements mémoire les plus proches (puisqu’ils sont internes au processeur), c’est
alors la mémoire la plus rapide de l’ordinateur. Cette pyramide de la mémoire est
représentée dans la figure suivante, qui oppose le coût de la mémoire à son temps d’accès

6 sur 9 10/03/2020 à 16:24


Gestion de la mémoire - hackndo https://beta.hackndo.com/memory-allocation/

par le processeur :

Le processeur x86 32 bits possède (logiquement) 8 registres généraux (EAX, EBX, ECX,
EDX, ESP, EBP, ESI, EDI)

Pour les processeurs 64 bits, il y a 16 registres logiques. Mais dans la réalité, les derniers
processeurs en ont 168, pour pouvoir paralléliser les instructions.

On distingue deux groupes :

Les 4 EAX, EBX, ECX et EDX appelés Accumulateur, Base, Compteur, Données ont
pour rôle de stocker des données temporaires pour le processeur lorsqu’il exécute un
programme.
Les 4 autres registres ESP, EBP, ESI et EDI appelés Pointeur de Pile (Stack),
Pointeur de Base, Index de Source et Index de Destination sont plutôt utilisés en tant
que pointeurs et index, comme leur nom l’indique. Par exemple, les deux premiers
stockent des adresses 32 bits (désignant des emplacements mémoire) pour délimiter
le stack frame courant.

Nous avons également deux autres registres, un peu plus spéciaux :

Le registre EIP est appelé Pointeur d’Instruction. Il contient l’adresse de la prochaine


instruction que le processeur doit exécuter.
Enfin, le registre EFLAGS qui, en réalité, contient des indicateurs, des interrupteurs,
des drapeaux (flags) essentiellement utilisés pour des comparaisons, mais pas
uniquement.

7 sur 9 10/03/2020 à 16:24

Оценить