Prérequis

Hello ! o/

Dans cet article vous allez comprendre comment est segmentée et sectionnée la mémoire lorsqu’un programme est chargé ! Chouette non ? Cela va vous permettre de mieux comprendre l’organisation de votre programme en mémoire, et quoi sert à quoi.

Segments

Les segments sont simplement des regroupements de sections qui ont leur propres flags, base address etc. Pour les obtenir les segments d’un programme vous pouvez utiliser la commande

$ readelf -l ./program

Donc voici les différents segments (ici pour la liste complète):

Sections

Les sections sont les composantes des segments. Nous commencerons des adresses basses (0x0) vers les adresse hautes (0xf).

Adresses Segment Section Description
0x0 LOAD #2 .plt Procedure Linkage Table, elle est utilisée pour appeler les fonctions/procédures externes, comme printf@plt
.text Contient les opcodes (Operations Codes) de l'architecture CPU (Control Process Unit), ce qui signifie que cette section contient le code source du programme
LOAD #3 .rodata Constantes en RO
LOAD #4 .ctors/init_array Liste des constructeurs (pointeurs sur fonction), c'est une liste de pointeurs sur fonction qui sont appelés avant l'appel de main (elle se définit avec l'attribut pré-déclaratif __attribute__((constructor)))
.dtors/fini_array Liste des destructeurs (pointeurs sur fonction), c'est une liste de pointeurs sur fonction qui sont appelés après l'appel de main, comme atexit(3) (elle se définit avec l'attribut pré-déclaratif __attribute__((destructor)))
.got Global Offset Table, elle est utilisée pour pointer sur des variables globales importées de bibliothèques (comme EXIT_SUCCESS de stdlib.h)
.got.plt C'est la GOT de la PLT, donc elle agit sur les fonctions/procédures externes, comme printf@libc
.data Comme .rodata mais en RW (Read-Write)
.bss Comme .data mais ici c'est juste un chunk de 0 pour les variables globales non initialisées
N/A [heap] Sur le DS (Data Segment), c'est l'espace mémoire réservé aux allocations dynamiques, elle grandit vers les adresses hautes, c'est les adresses retournées par malloc(3),brk(2),etc
N/A [libs] C'est après la heap que les bibliothèques sont chargées (libc, OpenCL, GTK, ...), elles ont leur propres segments/sections, ce sont des programmes à part entière
GNU_STACK [stack] Allocation statique, elle grandit vers les adresses basses, elle contient les variables locales, les retours d'adresses et les sauvegardes des registres de stack comme RBP (EBP en Intel x86)
0xf N/A [kernel] Evidemment on ne charge pas le kernel lors du lancement d'un programme quelconque, mais il faut savoir que le kernel est par défaut chargé dans les adresses les plus hautes

Appel d’une fonction

Imaginons que nous soyons dans .text, que EIP (le pointeur sur l’instruction courante) pointe vers une instruction call sur printf@plt, nous savons qu’en résultat, printf(3) sera appelée. En réalité voici ce qu’il se passe: printf@plt appelle printf@got.plt qui appelle printf@libc.

Dans le cas où l’on compile avec le flag -static, voici un exemple d’appel sur printf:

La suite ici !