hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-files-folders-and-binaries/universal-binaries-and-mach-o-format.md

16 KiB

Binaires universels macOS & Format Mach-O

Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres moyens de soutenir HackTricks :

Informations de base

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 composée essentiellement de :

  • En-tête
  • Commandes de chargement
  • Données

En-tête Fat

Recherchez le fichier avec : mdfind fat.h | grep -i mach-o | grep -E "fat.h$"

#define FAT_MAGIC	0xcafebabe
#define FAT_CIGAM	0xbebafeca	/* NXSwapLong(FAT_MAGIC) */

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC ou FAT_MAGIC_64 */
	uint32_t	nfat_arch;	/* nombre de structures qui suivent */
};

struct fat_arch {
cpu_type_t	cputype;	/* spécificateur de CPU (int) */
cpu_subtype_t	cpusubtype;	/* spécificateur de machine (int) */
uint32_t	offset;		/* décalage dans le fichier vers ce fichier objet */
uint32_t	size;		/* taille de ce fichier objet */
uint32_t	align;		/* alignement en puissance de 2 */
};

L'en-tête contient les octets magic suivis du nombre d'architectures que le fichier contient (nfat_arch) et chaque architecture aura une structure fat_arch.

Vérifiez-le avec :

% file /bin/ls
/bin/ls: binaire universel Mach-O avec 2 architectures : [x86_64 : exécutable Mach-O 64 bits x86_64] [arm64e : exécutable Mach-O 64 bits arm64e]
/bin/ls (pour l'architecture x86_64) :	exécutable Mach-O 64 bits x86_64
/bin/ls (pour l'architecture arm64e) :	exécutable Mach-O 64 bits arm64e

% otool -f -v /bin/ls
En-têtes Fat
fat_magic FAT_MAGIC
nfat_arch 2
architecture x86_64
    cputype CPU_TYPE_X86_64
cpusubtype CPU_SUBTYPE_X86_64_ALL
capabilities 0x0
    offset 16384
    size 72896
    align 2^14 (16384)
architecture arm64e
    cputype CPU_TYPE_ARM64
cpusubtype CPU_SUBTYPE_ARM64E
capabilities PTR_AUTH_VERSION USERSPACE 0
    offset 98304
    size 88816
    align 2^14 (16384)

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 d'un compilé pour juste 1 arch.

En-tête Mach-O

L'en-tête contient des informations de base sur le fichier, telles que les octets magiques pour l'identifier en tant que 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$"

#define	MH_MAGIC	0xfeedface	/* the mach magic number */
#define MH_CIGAM	0xcefaedfe	/* NXSwapInt(MH_MAGIC) */
struct mach_header {
uint32_t	magic;		/* mach magic number identifier */
cpu_type_t	cputype;	/* cpu specifier (e.g. I386) */
cpu_subtype_t	cpusubtype;	/* machine specifier */
uint32_t	filetype;	/* type of file (usage and alignment for the file) */
uint32_t	ncmds;		/* number of load commands */
uint32_t	sizeofcmds;	/* the size of all the load commands */
uint32_t	flags;		/* flags */
};

#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
struct mach_header_64 {
uint32_t	magic;		/* mach magic number identifier */
int32_t		cputype;	/* cpu specifier */
int32_t		cpusubtype;	/* machine specifier */
uint32_t	filetype;	/* type of file */
uint32_t	ncmds;		/* number of load commands */
uint32_t	sizeofcmds;	/* the size of all the load commands */
uint32_t	flags;		/* flags */
uint32_t	reserved;	/* reserved */
};

Types de fichiers :

  • MH_EXECUTE (0x2) : Exécutable Mach-O standard
  • MH_DYLIB (0x6) : Bibliothèque liée dynamiquement Mach-O (par exemple, .dylib)
  • MH_BUNDLE (0x8) : Bundle Mach-O (par exemple, .bundle)
# Checking the mac header of a binary
otool -arch arm64e -hv /bin/ls
Mach header
magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64          E USR00     EXECUTE    19       1728   NOUNDEFS DYLDLINK TWOLEVEL PIE

Ou en utilisant Mach-O View :

Commandes de chargement Mach-O

Cela spécifie la disposition du fichier en mémoire. Il contient l'emplacement de la table des symboles, le contexte du thread principal au début de l'exécution, et quelles bibliothèques partagées sont requises. Les commandes instruisent essentiellement le chargeur dynamique (dyld) sur la manière de charger le binaire en mémoire.

Toutes les commandes de chargement commencent par une structure load_command, définie dans le loader.h mentionné précédemment :

struct load_command {
uint32_t cmd;           /* type of load command */
uint32_t cmdsize;       /* total size of command in bytes */
};

Il existe environ 50 types différents de commandes de chargement que le système gère différemment. Les plus courantes sont : LC_SEGMENT_64, LC_LOAD_DYLINKER, LC_MAIN, LC_LOAD_DYLIB et LC_CODE_SIGNATURE.

LC_SEGMENT/LC_SEGMENT_64

{% hint style="success" %} En gros, ce type de commande de chargement définit comment charger les segments __TEXT (code exécutable) et __DATA (données pour le processus) selon les décalages indiqués dans la section des données lorsque le binaire est exécuté. {% endhint %}

Ces commandes définissent des segments qui sont cartographiés dans l'espace mémoire virtuel d'un processus lorsqu'il est exécuté.

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 encore divisé en plusieurs sections. La structure de commande de chargement contient des informations sur ces sections au sein du segment respectif.

Dans l'en-tête, vous trouvez d'abord l'en-tête de segment :

struct segment_command_64 { /* pour les architectures 64 bits */
uint32_t	cmd;		/* LC_SEGMENT_64 */
uint32_t	cmdsize;	/* inclut la taille des structures section_64 */
char		segname[16];	/* nom du segment */
uint64_t	vmaddr;		/* adresse mémoire de ce segment */
uint64_t	vmsize;		/* taille mémoire de ce segment */
uint64_t	fileoff;	/* décalage du fichier de ce segment */
uint64_t	filesize;	/* quantité à mapper depuis le fichier */
int32_t		maxprot;	/* protection maximale de la VM */
int32_t		initprot;	/* protection initiale de la VM */
	uint32_t	nsects;		/* nombre de sections dans le segment */
	uint32_t	flags;		/* drapeaux */
};

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 :

struct section_64 { /* for 64-bit architectures */
char		sectname[16];	/* name of this section */
char		segname[16];	/* segment this section goes in */
uint64_t	addr;		/* memory address of this section */
uint64_t	size;		/* size in bytes of this section */
uint32_t	offset;		/* file offset of this section */
uint32_t	align;		/* section alignment (power of 2) */
uint32_t	reloff;		/* file offset of relocation entries */
uint32_t	nreloc;		/* number of relocation entries */
uint32_t	flags;		/* flags (section type and attributes)*/
uint32_t	reserved1;	/* reserved (for offset or index) */
uint32_t	reserved2;	/* reserved (for count or sizeof) */
uint32_t	reserved3;	/* reserved */
};

Exemple de titre 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 :

otool -lv /bin/ls

Segments courants chargés par cette cmd :

  • __PAGEZERO : Il indique au noyau de mapper l'adresse zéro de sorte qu'elle ne puisse ê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 déréférencement de pointeur NULL.
  • __TEXT : Contient du code exécutable avec des permissions de lecture et d'exécution (non modifiable). Sections courantes de ce segment :
  • __text : Code binaire compilé
  • __const : Données constantes
  • __cstring : Constantes de chaînes de caractères
  • __stubs et __stubs_helper : Impliqués pendant le processus de chargement de la bibliothèque dynamique
  • __DATA : Contient des données qui sont lisibles et modifiables (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 l'éditeur de liens (dyld) telles que "les entrées de tableaux de symboles, de chaînes et de relocalisation."
  • __OBJC : Contient des informations utilisées par le runtime Objective-C. Bien que ces informations puissent également se trouver 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 (en mémoire) du binaire, puis saute à cette instruction pour commencer l'exécution du code du binaire.

LC_CODE_SIGNATURE

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 ce billet de blog et ce gists.

LC_LOAD_DYLINKER

Contient le chemin vers l'exécutable du lieur 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 de dylib se fait en mode utilisateur, et non en mode noyau.

LC_LOAD_DYLIB

Cette commande de chargement décrit une dépendance de bibliothèque dynamique qui instruit le chargeur (dyld) de charger et lier ladite bibliothèque. Il y a une commande de chargement LC_LOAD_DYLIB pour chaque bibliothèque dont le binaire Mach-O a besoin.

  • Cette commande de chargement est une structure de type dylib_command (qui contient une struct dylib, décrivant la bibliothèque dynamique dépendante réelle) :
struct dylib_command {
uint32_t        cmd;            /* LC_LOAD_{,WEAK_}DYLIB */
uint32_t        cmdsize;        /* includes pathname string */
struct dylib    dylib;          /* the library identification */
};

struct dylib {
union lc_str  name;                 /* library's path name */
uint32_t timestamp;                 /* library's build time stamp */
uint32_t current_version;           /* library's current version number */
uint32_t compatibility_version;     /* library's compatibility vers number*/
};
Vous pouvez également obtenir ces informations depuis le cli avec :
otool -L /bin/ls
/bin/ls:
/usr/lib/libutil.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)

Certaines bibliothèques potentiellement liées à des malwares sont :

  • DiskArbitration : Surveillance des clés USB
  • AVFoundation : Capture audio et vidéo
  • CoreWLAN : Scans Wifi.

{% hint style="info" %} 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 tous les constructeurs se trouvent dans la section __mod_init_func du segment __DATA_CONST. {% endhint %}

Données Mach-O

Le cœur du fichier est la région finale, les données, qui se compose de plusieurs segments tels qu'organisés dans la région des commandes de chargement. Chaque segment peut contenir un certain nombre de sections de données. Chacune de ces sections contient du code ou des données d'un type particulier.

{% hint style="success" %} Les données sont essentiellement la partie contenant toutes les informations qui sont chargées par les commandes de chargement LC_SEGMENTS_64 {% endhint %}

Cela inclut :

  • Table des fonctions : Qui contient des informations sur les fonctions du programme.
  • Table des symboles : Qui contient des informations sur la fonction externe utilisée par le binaire
  • Elle pourrait également contenir des noms de fonctions internes, de variables et plus encore.

Pour le vérifier, vous pourriez utiliser l'outil Mach-O View :

Ou depuis le cli :

size -m /bin/ls
Apprenez le hacking AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres moyens de soutenir HackTricks :