Travaillez-vous dans une entreprise de cybersécurité? Voulez-vous voir votre entreprise annoncée dans HackTricks? ou voulez-vous avoir accès à la dernière version du PEASS ou télécharger HackTricks en PDF? Consultez les PLANS D'ABONNEMENT!
Le programme précédent a 9 en-têtes de programme, puis, la cartographie des segments indique dans quel en-tête de programme (de 00 à 08) chaque section est située.
PHDR - Program HeaDeR
Contient les tables d'en-têtes de programme et les métadonnées elles-mêmes.
INTERP
Indique le chemin du chargeur à utiliser pour charger le binaire en mémoire.
LOAD
Ces en-têtes sont utilisés pour indiquer comment charger un binaire en mémoire.
Chaque en-tête LOAD indique une région de mémoire (taille, autorisations et alignement) et indique les octets du binaire ELF à copier dedans.
Par exemple, le deuxième a une taille de 0x1190, devrait être situé à 0x1fc48 avec des autorisations de lecture et d'écriture et sera rempli avec 0x528 à partir du décalage 0xfc48 (il ne remplit pas tout l'espace réservé). Cette mémoire contiendra les sections .init_array .fini_array .dynamic .got .data .bss.
DYNAMIC
Cet en-tête aide à lier les programmes à leurs dépendances de bibliothèque et à appliquer les relocalisations. Vérifiez la section .dynamic.
NOTE
Stocke des informations de métadonnées du fournisseur sur le binaire.
GNU_EH_FRAME
Définit l'emplacement des tables de déroulement de la pile, utilisées par les débogueurs et les fonctions d'exécution des exceptions C++.
GNU_STACK
Contient la configuration de la défense de prévention de l'exécution de la pile. Si activé, le binaire ne pourra pas exécuter de code à partir de la pile.
GNU_RELRO
Indique la configuration RELRO (Relocation Read-Only) du binaire. Cette protection marquera comme en lecture seule certaines sections de la mémoire (comme le GOT ou les tables init et fini) après le chargement du programme et avant son exécution.
Dans l'exemple précédent, il copie 0x3b8 octets à 0x1fc48 en lecture seule affectant les sections .init_array .fini_array .dynamic .got .data .bss.
Notez que RELRO peut être partiel ou complet, la version partielle ne protège pas la section .plt.got, qui est utilisée pour la liaison paresseuse et a besoin que cet espace mémoire ait des autorisations d'écriture pour écrire l'adresse des bibliothèques la première fois que leur emplacement est recherché.
TLS
Définit une table d'entrées TLS, qui stocke des informations sur les variables locales aux threads.
En-têtes de section
Les en-têtes de section donnent une vue plus détaillée du binaire ELF.
Table des chaînes: Elle contient toutes les chaînes nécessaires au fichier ELF (mais pas celles réellement utilisées par le programme). Par exemple, elle contient des noms de sections comme .text ou .data. Et si .text est à l'offset 45 dans la table des chaînes, il utilisera le nombre 45 dans le champ name.
Pour trouver où se trouve la table des chaînes, l'ELF contient un pointeur vers la table des chaînes.
Table des symboles: Elle contient des informations sur les symboles comme le nom (offset dans la table des chaînes), l'adresse, la taille et plus de métadonnées sur le symbole.
Sections Principales
.text: Les instructions du programme à exécuter.
.data: Variables globales avec une valeur définie dans le programme.
.bss: Variables globales non initialisées (ou initialisées à zéro). Les variables ici sont automatiquement initialisées à zéro, empêchant ainsi l'ajout de zéros inutiles au binaire.
.rodata: Variables globales constantes (section en lecture seule).
.tdata et .tbss: Comme le .data et le .bss lorsque des variables locales au thread sont utilisées (__thread_local en C++ ou __thread en C).
.dynamic: Voir ci-dessous.
Symboles
Les symboles sont des emplacements nommés dans le programme qui peuvent être une fonction, un objet de données global, des variables locales au thread...
readelf -s lnstat
Symbol table '.dynsym' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
Chaque entrée de symbole contient :
Nom
Attributs de liaison (faible, local ou global) : Un symbole local ne peut être accédé que par le programme lui-même tandis que les symboles globaux sont partagés en dehors du programme. Un objet faible est par exemple une fonction qui peut être remplacée par une autre.
Type : NOTYPE (type non spécifié), OBJECT (variable de données globale), FUNC (fonction), SECTION (section), FILE (fichier source pour les débogueurs), TLS (variable locale au thread), GNU_IFUNC (fonction indirecte pour la relocalisation)
Le répertoire NEEDED indique que le programme doit charger la bibliothèque mentionnée pour continuer. Le répertoire NEEDED est complété une fois que la bibliothèque partagée est entièrement opérationnelle et prête à être utilisée.
Réadressages
Le chargeur doit également relocaliser les dépendances après les avoir chargées. Ces réadressages sont indiqués dans la table de réadressage aux formats REL ou RELA et le nombre de réadressages est donné dans les sections dynamiques RELSZ ou RELASZ.
Si le programme est chargé à un emplacement différent de l'adresse préférée (généralement 0x400000) car l'adresse est déjà utilisée ou en raison de ASLR ou toute autre raison, un réadressage statique corrige les pointeurs qui avaient des valeurs s'attendant à ce que le binaire soit chargé à l'adresse préférée.
Par exemple, toute section de type R_AARCH64_RELATIV devrait avoir modifié l'adresse au décalage de réadressage plus la valeur de l'addend.
Réadressages dynamiques et GOT
Le réadressage pourrait également faire référence à un symbole externe (comme une fonction d'une dépendance). Comme la fonction malloc de libC. Ensuite, le chargeur lors du chargement de libC à une adresse vérifiant où la fonction malloc est chargée, écrira cette adresse dans la table GOT (Global Offset Table) (indiquée dans la table de réadressage) où l'adresse de malloc devrait être spécifiée.
Table de liaison de procédures
La section PLT permet d'effectuer une liaison paresseuse, ce qui signifie que la résolution de l'emplacement d'une fonction sera effectuée la première fois qu'elle est accédée.
Ainsi, lorsque qu'un programme appelle malloc, il appelle en réalité l'emplacement correspondant de malloc dans la PLT (malloc@plt). La première fois qu'elle est appelée, elle résout l'adresse de malloc et la stocke, de sorte que la prochaine fois que malloc est appelée, cette adresse est utilisée à la place du code PLT.
Initialisation du programme
Après que le programme a été chargé, il est temps pour lui de s'exécuter. Cependant, le premier code qui est exécuté n'est pas toujours la fonction main. Cela est dû, par exemple, en C++, si une variable globale est un objet d'une classe, cet objet doit être initialiséavant l'exécution de main, comme dans:
Notez que ces variables globales sont situées dans .data ou .bss, mais dans les listes __CTOR_LIST__ et __DTOR_LIST__, les objets à initialiser et à détruire sont stockés afin de les suivre.
À partir du code C, il est possible d'obtenir le même résultat en utilisant les extensions GNU :
__attributte__((constructor)) //Add a constructor to execute before__attributte__((destructor)) //Add to the destructor list
Du point de vue du compilateur, pour exécuter ces actions avant et après l'exécution de la fonction main, il est possible de créer une fonction init et une fonction fini qui seraient référencées dans la section dynamique comme INIT et FIN et placées dans les sections init et fini de l'ELF.
L'autre option, comme mentionné, est de référencer les listes __CTOR_LIST__ et __DTOR_LIST__ dans les entrées INIT_ARRAY et FINI_ARRAY de la section dynamique et leur longueur est indiquée par INIT_ARRAYSZ et FINI_ARRAYSZ. Chaque entrée est un pointeur de fonction qui sera appelé sans arguments.
De plus, il est également possible d'avoir un PREINIT_ARRAY avec des pointeurs qui seront exécutés avant les pointeurs INIT_ARRAY.
Ordre d'initialisation
Le programme est chargé en mémoire, les variables globales statiques sont initialisées dans .data et celles non initialisées sont mises à zéro dans .bss.
Toutes les dépendances du programme ou des bibliothèques sont initialisées et le liaison dynamique est exécutée.
Les fonctions PREINIT_ARRAY sont exécutées.
Les fonctions INIT_ARRAY sont exécutées.
Si une entrée INIT existe, elle est appelée.
Si c'est une bibliothèque, dlopen se termine ici, si c'est un programme, il est temps d'appeler le point d'entrée réel (fonction main).
Stockage local aux threads (TLS)
Ils sont définis en utilisant le mot-clé __thread_local en C++ ou l'extension GNU __thread.
Chaque thread maintiendra un emplacement unique pour cette variable, de sorte que seul le thread peut accéder à sa variable.
Lorsque cela est utilisé, les sections .tdata et .tbss sont utilisées dans l'ELF. Ce sont comme .data (initialisé) et .bss (non initialisé) mais pour TLS.
Chaque variable aura une entrée dans l'en-tête TLS spécifiant la taille et le décalage TLS, qui est le décalage qu'il utilisera dans la zone de données locale du thread.
Le __TLS_MODULE_BASE est un symbole utilisé pour faire référence à l'adresse de base du stockage local aux threads et pointe vers la zone en mémoire qui contient toutes les données locales aux threads d'un module.