macOS Universal binaries & Mach-O Format
Last updated
Last updated
Les binaires Mac OS sont généralement compilés en tant que binaires universels. Un binaire universel peut prendre en charge plusieurs architectures dans le même fichier.
Ces binaires suivent la structure Mach-O qui est essentiellement composée de :
En-tête
Commandes de chargement
Données
Recherchez le fichier avec : mdfind fat.h | grep -i mach-o | grep -E "fat.h$"
L'en-tête contient les octets magic suivis du nombre d'architectures contenues dans le fichier (nfat_arch
) et chaque architecture aura une structure fat_arch
.
Vérifiez-le avec :
ou en utilisant l'outil Mach-O View :
Comme vous pouvez le penser, un binaire universel compilé pour 2 architectures double généralement la taille de celui compilé pour une seule architecture.
L'en-tête contient des informations de base sur le fichier, telles que les octets magiques pour l'identifier comme un fichier Mach-O et des informations sur l'architecture cible. Vous pouvez le trouver dans : mdfind loader.h | grep -i mach-o | grep -E "loader.h$"
Types de fichiers:
MH_EXECUTE (0x2): Exécutable Mach-O standard
MH_DYLIB (0x6): Une bibliothèque dynamique Mach-O (c'est-à-dire .dylib)
MH_BUNDLE (0x8): Un bundle Mach-O (c'est-à-dire .bundle)
Ou en utilisant Mach-O View:
La disposition du fichier en mémoire est spécifiée ici, détaillant l'emplacement de la table des symboles, le contexte du thread principal au démarrage de l'exécution, et les bibliothèques partagées requises. Des instructions sont fournies au chargeur dynamique (dyld) sur le processus de chargement du binaire en mémoire.
Il utilise la structure load_command, définie dans le fichier loader.h
:
Il existe environ 50 types de commandes de chargement différents que le système gère différemment. Les plus courants sont : LC_SEGMENT_64
, LC_LOAD_DYLINKER
, LC_MAIN
, LC_LOAD_DYLIB
et LC_CODE_SIGNATURE
.
Essentiellement, ce type de commande de chargement définit comment charger les segments __TEXT (code exécutable) et __DATA (données du processus) selon les décalages indiqués dans la section Data lorsque le binaire est exécuté.
Ces commandes définissent des segments qui sont cartographiés dans l'espace mémoire virtuel d'un processus lors de son exécution.
Il existe différents types de segments, tels que le segment __TEXT, qui contient le code exécutable d'un programme, et le segment __DATA, qui contient les données utilisées par le processus. Ces segments sont situés dans la section des données du fichier Mach-O.
Chaque segment peut être divisé en plusieurs sections. La structure de la commande de chargement contient des informations sur ces sections dans le segment respectif.
Dans l'en-tête, vous trouvez d'abord l'en-tête du segment :
Exemple d'en-tête de segment :
Cet en-tête définit le nombre de sections dont les en-têtes apparaissent après lui :
Exemple de en-tête de section :
Si vous ajoutez le décalage de section (0x37DC) + le décalage où l'architecture commence, dans ce cas 0x18000
--> 0x37DC + 0x18000 = 0x1B7DC
Il est également possible d'obtenir des informations d'en-tête depuis la ligne de commande avec :
Les segments courants chargés par cette commande :
__PAGEZERO
: Il indique au noyau de mapper l'adresse zéro afin qu'elle ne puisse pas être lue, écrite ou exécutée. Les variables maxprot et minprot dans la structure sont définies à zéro pour indiquer qu'il n'y a aucun droit de lecture-écriture-exécution sur cette page.
Cette allocation est importante pour atténuer les vulnérabilités de référence de pointeur NULL.
__TEXT
: Contient du code exécutable avec des autorisations de lecture et d'exécution (pas d'écriture). Sections courantes de ce segment :
__text
: Code binaire compilé
__const
: Données constantes
__cstring
: Constantes de chaîne
__stubs
et __stubs_helper
: Impliqués lors du processus de chargement de bibliothèque dynamique
__DATA
: Contient des données lisibles et inscriptibles (non exécutables).
__data
: Variables globales (qui ont été initialisées)
__bss
: Variables statiques (qui n'ont pas été initialisées)
__objc_*
(__objc_classlist, __objc_protolist, etc) : Informations utilisées par le runtime Objective-C
__LINKEDIT
: Contient des informations pour le lien (dyld) telles que "entrées de table de symboles, de chaînes et de réadressage."
__OBJC
: Contient des informations utilisées par le runtime Objective-C. Bien que ces informations puissent également être trouvées dans le segment __DATA, dans diverses sections __objc_*.
LC_MAIN
Contient le point d'entrée dans l'attribut entryoff. Au moment du chargement, dyld ajoute simplement cette valeur à la base du binaire (en mémoire), puis saute vers cette instruction pour démarrer l'exécution du code binaire.
Contient des informations sur la signature de code du fichier Mach-O. Il contient uniquement un décalage qui pointe vers le blob de signature. Cela se trouve généralement à la toute fin du fichier. Cependant, vous pouvez trouver des informations sur cette section dans cet article de blog et ce gist.
Contient le chemin vers l'exécutable du chargeur dynamique qui mappe les bibliothèques partagées dans l'espace d'adressage du processus. La valeur est toujours définie sur /usr/lib/dyld
. Il est important de noter que sous macOS, le mappage dylib se fait en mode utilisateur, pas en mode noyau.
LC_LOAD_DYLIB
Cette commande de chargement décrit une dépendance de bibliothèque dynamique qui indique au chargeur (dyld) de charger et lier ladite bibliothèque. Il y a une commande de chargement LC_LOAD_DYLIB pour chaque bibliothèque requise par le binaire Mach-O.
Cette commande de chargement est une structure de type dylib_command
(qui contient une structure dylib, décrivant la bibliothèque dynamique dépendante réelle) :
Vous pouvez également obtenir ces informations depuis l'interface de ligne de commande avec :
Certains bibliothèques potentiellement liées aux logiciels malveillants sont :
DiskArbitration : Surveillance des lecteurs USB
AVFoundation : Capture audio et vidéo
CoreWLAN : Balayages Wifi.
Un binaire Mach-O peut contenir un ou plusieurs constructeurs, qui seront exécutés avant l'adresse spécifiée dans LC_MAIN. Les décalages de tout constructeur sont conservés dans la section __mod_init_func du segment __DATA_CONST.
Au cœur du fichier se trouve la région des données, composée de plusieurs segments tels que définis dans la région des commandes de chargement. Une variété de sections de données peut être contenue dans chaque segment, chaque section contenant du code ou des données spécifiques à un type.
Les données sont essentiellement la partie contenant toutes les informations chargées par les commandes de chargement LC_SEGMENTS_64
Cela inclut :
Table des fonctions : Qui contient des informations sur les fonctions du programme.
Table des symboles : Qui contient des informations sur les fonctions externes utilisées par le binaire
Il pourrait également contenir des noms de fonctions internes, de variables, etc.
Pour vérifier, vous pourriez utiliser l'outil Mach-O View :
Ou depuis la ligne de commande :