Teilen Sie Hacking-Tricks, indem Sie PRs an dieHackTricks und HackTricks Cloud Github-Repositories einreichen.
Grundlegende Informationen
Mac OS-Binärdateien sind normalerweise als universelle Binärdateien kompiliert. Eine universelle Binärdatei kann mehrere Architekturen in derselben Datei unterstützen.
Diese Binärdateien folgen der Mach-O-Struktur, die im Wesentlichen aus folgendem besteht:
Header
Ladebefehle
Daten
Fat-Header
Suchen Sie nach der Datei mit: mdfind fat.h | grep -i mach-o | grep -E "fat.h$"
#defineFAT_MAGIC0xcafebabe#defineFAT_CIGAM0xbebafeca /* NXSwapLong(FAT_MAGIC) */struct fat_header {uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */uint32_t nfat_arch; /* Anzahl der folgenden Strukturen */};struct fat_arch {cpu_type_t cputype; /* CPU-Spezifikator (int) */cpu_subtype_t cpusubtype; /* Maschinenspezifikator (int) */uint32_t offset; /* Dateioffset zu dieser Objektdatei */uint32_t size; /* Größe dieser Objektdatei */uint32_t align; /* Ausrichtung als Potenz von 2 */};
Der Header enthält die magischen Bytes gefolgt von der Anzahl der Architekturen, die die Datei enthält (nfat_arch) und jede Architektur wird eine fat_arch-Struktur haben.
Wie Sie vielleicht denken, verdoppelt eine universelle Binärdatei, die für 2 Architekturen kompiliert ist, normalerweise die Größe einer, die nur für 1 Architektur kompiliert ist.
Mach-O-Header
Der Header enthält grundlegende Informationen über die Datei, wie magische Bytes zur Identifizierung als Mach-O-Datei und Informationen über die Zielarchitektur. Sie finden ihn unter: mdfind loader.h | grep -i mach-o | grep -E "loader.h$"
#defineMH_MAGIC0xfeedface /* the mach magic number */#defineMH_CIGAM0xcefaedfe /* 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 */};#defineMH_MAGIC_640xfeedfacf /* the 64-bit mach magic number */#defineMH_CIGAM_640xcffaedfe /* 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 */};
Mach-O Dateitypen
Es gibt verschiedene Dateitypen, die in der Quellcodebeispiel hier definiert sind. Die wichtigsten sind:
MH_OBJECT: Relokalisierbare Objektdatei (Zwischenprodukte der Kompilierung, noch keine ausführbaren Dateien).
MH_EXECUTE: Ausführbare Dateien.
MH_FVMLIB: Datei einer festen VM-Bibliothek.
MH_CORE: Code-Dumps
MH_PRELOAD: Vorab geladene ausführbare Datei (nicht mehr in XNU unterstützt)
MH_DYLIB: Dynamische Bibliotheken
MH_DYLINKER: Dynamischer Linker
MH_BUNDLE: "Plugin-Dateien". Generiert mit -bundle in gcc und explizit geladen von NSBundle oder dlopen.
MH_DYSM: Begleitende .dSym-Datei (Datei mit Symbolen für Debugging).
MH_KEXT_BUNDLE: Kernelerweiterungen.
# Checking the mac header of a binaryotool-archarm64e-hv/bin/lsMachheadermagiccputypecpusubtypecapsfiletypencmdssizeofcmdsflagsMH_MAGIC_64ARM64EUSR00EXECUTE191728NOUNDEFSDYLDLINKTWOLEVELPIE
Der Quellcode definiert auch mehrere nützliche Flags zum Laden von Bibliotheken:
MH_NOUNDEFS: Keine undefinierten Verweise (vollständig verknüpft)
MH_DYLDLINK: Dyld-Verknüpfung
MH_PREBOUND: Dynamische Verweise vorab gebunden.
MH_SPLIT_SEGS: Datei teilt r/o- und r/w-Segmente auf.
MH_WEAK_DEFINES: Binärdatei hat schwach definierte Symbole
MH_BINDS_TO_WEAK: Binärdatei verwendet schwache Symbole
MH_ALLOW_STACK_EXECUTION: Den Stack ausführbar machen
MH_NO_REEXPORTED_DYLIBS: Bibliothek ohne LC_REEXPORT-Befehle
MH_PIE: Positionsunabhängige ausführbare Datei
MH_HAS_TLV_DESCRIPTORS: Es gibt einen Abschnitt mit thread-lokalen Variablen
MH_NO_HEAP_EXECUTION: Keine Ausführung für Heap-/Daten-Seiten
MH_HAS_OBJC: Binärdatei hat oBject-C-Abschnitte
MH_SIM_SUPPORT: Simulatorunterstützung
MH_DYLIB_IN_CACHE: Verwendet auf dylibs/Frameworks im gemeinsamen Bibliotheks-Cache.
Mach-O Load-Befehle
Die Speicherlayout der Datei ist hier festgelegt, wobei der Speicherort der Symboltabelle, der Kontext des Hauptthreads beim Start der Ausführung und die erforderlichen gemeinsam genutzten Bibliotheken beschrieben werden. Anweisungen werden dem dynamischen Loader (dyld) zum Laden des Binärprogramms in den Speicher bereitgestellt.
Es wird die load_command-Struktur verwendet, die in der genannten loader.h definiert ist:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Es gibt ungefähr 50 verschiedene Arten von Ladungsbefehlen, die das System unterschiedlich behandelt. Die häufigsten sind: LC_SEGMENT_64, LC_LOAD_DYLINKER, LC_MAIN, LC_LOAD_DYLIB und LC_CODE_SIGNATURE.
LC_SEGMENT/LC_SEGMENT_64
Grundsätzlich definieren diese Arten von Ladungsbefehlen, wie der __TEXT (ausführbarer Code) und __DATA (Daten für den Prozess) Segmenten entsprechend den Offsets geladen werden, die im Datenabschnitt angegeben sind, wenn die Binärdatei ausgeführt wird.
Diese Befehle definieren Segmente, die in den virtuellen Speicherbereich eines Prozesses abgebildet werden, wenn er ausgeführt wird.
Es gibt verschiedene Arten von Segmenten, wie das __TEXT-Segment, das den ausführbaren Code eines Programms enthält, und das __DATA-Segment, das Daten enthält, die vom Prozess verwendet werden. Diese Segmente befinden sich im Datenabschnitt der Mach-O-Datei.
Jedes Segment kann weiter in mehrere Abschnitte unterteilt werden. Die Ladungsbefehlsstruktur enthält Informationen zu diesen Abschnitten innerhalb des jeweiligen Segments.
Im Header finden Sie zuerst den Segment-Header:
struct segment_command_64 { /* für 64-Bit-Architekturen */uint32_t cmd; /* LC_SEGMENT_64 */uint32_t cmdsize; /* enthält die Größe der section_64-Strukturen */char segname[16]; /* Segmentname */uint64_t vmaddr; /* Speicheradresse dieses Segments */uint64_t vmsize; /* Speichergröße dieses Segments */uint64_t fileoff; /* Dateioffset dieses Segments */uint64_t filesize; /* Menge, die aus der Datei abgebildet werden soll */int32_t maxprot; /* maximale VM-Schutzmaßnahme */int32_t initprot; /* anfänglicher VM-Schutz */uint32_t nsects; /* Anzahl der Abschnitte im Segment */uint32_t flags; /* Flags */};
Beispiel für einen Segment-Header:
Dieser Header definiert die Anzahl der Abschnitte, deren Header danach erscheinen:
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 */};
Beispiel für Abschnittsüberschrift:
Wenn Sie den Abschnittsversatz (0x37DC) + den Versatz hinzufügen, an dem die Architektur beginnt, in diesem Fall 0x18000 --> 0x37DC + 0x18000 = 0x1B7DC
Es ist auch möglich, Headerinformationen von der Befehlszeile aus abzurufen:
otool-lv/bin/ls
Gemeinsame Segmente, die von diesem Befehl geladen werden:* **`__PAGEZERO`:** Es weist den Kernel an, die **Adresse Null** so zu **zuordnen**, dass sie **nicht gelesen, geschrieben oder ausgeführt werden kann**. Die Variablen maxprot und minprot in der Struktur sind auf Null gesetzt, um anzuzeigen, dass es **keine Lese-Schreib-Ausführungsrechte auf dieser Seite** gibt.
* Diese Zuweisung ist wichtig, um **NULL-Pointer-Dereferenz-Schwachstellen zu mildern**. Dies liegt daran, dass XNU eine harte Nullseite durchsetzt, die sicherstellt, dass die erste Seite (nur die erste) des Speichers unzugänglich ist (außer in i386). Ein Binärfile könnte diese Anforderungen erfüllen, indem es ein kleines \_\_PAGEZERO (unter Verwendung von `-pagezero_size`) erstellt, um die ersten 4 KB abzudecken und den Rest des 32-Bit-Speichers sowohl im Benutzer- als auch im Kernelmodus zugänglich zu machen.
* **`__TEXT`**: Enthält **ausführbaren** **Code** mit **Lese-** und **Ausführungsberechtigungen** (nicht schreibbar)**.** Gemeinsame Abschnitte dieses Segments:
* `__text`: Kompilierter Binärcode* `__const`: Konstante Daten (nur lesbar)* `__[c/u/os_log]string`: C-, Unicode- oder os-Log-Zeichenfolgenkonstanten* `__stubs` und `__stubs_helper`: Werden während des dynamischen Bibliotheksladevorgangs verwendet* `__unwind_info`: Stack-Unwind-Daten.* Beachten Sie, dass all diese Inhalte signiert sind, aber auch als ausführbar markiert sind (was mehr Möglichkeiten für die Ausnutzung von Abschnitten schafft, die diese Berechtigung nicht unbedingt benötigen, wie z. B. für spezielle Zeichenfolgenabschnitte).
* **`__DATA`**: Enthält Daten, die **lesbar** und **schreibbar** sind (nicht ausführbar)**.*** `__got:` Global Offset Table* `__nl_symbol_ptr`: Nicht träge (bei Laden binden) Symbolzeiger* `__la_symbol_ptr`: Träge (bei Verwendung binden) Symbolzeiger* `__const`: Sollte schreibgeschützte Daten sein (ist es aber nicht wirklich)* `__cfstring`: CoreFoundation-Zeichenfolgen* `__data`: Globale Variablen (die initialisiert wurden)* `__bss`: Statische Variablen (die nicht initialisiert wurden)* `__objc_*` (\_\_objc\_classlist, \_\_objc\_protolist usw.): Informationen, die vom Objective-C-Laufzeitumgebung verwendet werden
* **`__DATA_CONST`**: \_\_DATA.\_\_const ist nicht garantiert konstant zu sein (Schreibberechtigungen), ebenso wie andere Zeiger und die GOT. Dieser Abschnitt macht `__const`, einige Initialisierer und die GOT-Tabelle (nach der Auflösung) mit `mprotect` **schreibgeschützt**.
* **`__LINKEDIT`**: Enthält Informationen für den Linker (dyld) wie Symbol-, Zeichenfolgen- und Relokationstabelleneinträge. Es ist ein generischer Container für Inhalte, die weder in `__TEXT` noch in `__DATA` sind, und sein Inhalt wird in anderen Ladebefehlen beschrieben.
* dyld-Informationen: Rebase, Nicht-träge/träge/schwache Bindungsoperationen und Exportinformationen* Funktionsstarts: Tabelle der Startadressen von Funktionen* Daten im Code: Dateninseln in \_\_text* Symboltabelle: Symbole im Binärfile* Indirekte Symboltabelle: Zeiger/Stub-Symbole* Zeichentabelle* Codesignatur* **`__OBJC`**: Enthält Informationen, die von der Objective-C-Laufzeitumgebung verwendet werden. Diese Informationen können auch im \_\_DATA-Segment in verschiedenen \_\_objc\_\*-Abschnitten gefunden werden.
* **`__RESTRICT`**: Ein Segment ohne Inhalt mit einem einzigen Abschnitt namens **`__restrict`** (ebenfalls leer), der sicherstellt, dass beim Ausführen des Binärfiles die DYLD-Umgebungsvariablen ignoriert werden.
Wie im Code zu sehen war, **unterstützen Segmente auch Flags** (obwohl sie nicht sehr häufig verwendet werden):* `SG_HIGHVM`: Nur Core (nicht verwendet)* `SG_FVMLIB`: Nicht verwendet* `SG_NORELOC`: Segment hat keine Relokation* `SG_PROTECTED_VERSION_1`: Verschlüsselung. Wird beispielsweise vom Finder verwendet, um den Text im `__TEXT`-Segment zu verschlüsseln.
### **`LC_UNIXTHREAD/LC_MAIN`****`LC_MAIN`** enthält den Einstiegspunkt im **entryoff-Attribut**. Zur Ladezeit **addiert** **dyld** einfach diesen Wert zur (im Speicher befindlichen) **Basis des Binärfiles** und **springt** dann zu dieser Anweisung, um die Ausführung des Codes des Binärfiles zu starten.
**`LC_UNIXTHREAD`** enthält die Werte, die die Register haben müssen, wenn der Hauptthread gestartet wird. Dies wurde bereits veraltet, aber **`dyld`** verwendet es immer noch. Es ist möglich, die Werte der Register, die durch dies festgelegt sind, mit anzusehen:
Enthält Informationen zur Codesignatur der Mach-O-Datei. Es enthält nur einen Offset, der auf den Signatur-Blob zeigt. Dies befindet sich normalerweise am Ende der Datei.
Sie können jedoch einige Informationen zu diesem Abschnitt in diesem Blog-Beitrag und in diesem Gist finden.
LC_ENCRYPTION_INFO[_64]
Unterstützung für die binäre Verschlüsselung. Wenn es einem Angreifer jedoch gelingt, den Prozess zu kompromittieren, kann er den Speicher unverschlüsselt auslesen.
LC_LOAD_DYLINKER
Enthält den Pfad zum dynamischen Linker-Programm, das gemeinsam genutzte Bibliotheken in den Adressraum des Prozesses abbildet. Der Wert ist immer auf /usr/lib/dyld gesetzt. Es ist wichtig zu beachten, dass in macOS das Dylib-Mapping im Benutzermodus und nicht im Kernelmodus erfolgt.
LC_IDENT
Veraltet, aber wenn so konfiguriert, dass Dumps bei einem Absturz erstellt werden, wird ein Mach-O-Core-Dump erstellt und die Kernelversion im LC_IDENT-Befehl festgelegt.
LC_UUID
Zufällige UUID. Es ist direkt für nichts nützlich, aber XNU speichert es zusammen mit dem Rest der Prozessinformationen im Cache. Es kann in Absturzberichten verwendet werden.
LC_DYLD_ENVIRONMENT
Ermöglicht das Angeben von Umgebungsvariablen für den dyld, bevor der Prozess ausgeführt wird. Dies kann sehr gefährlich sein, da es ermöglichen kann, beliebigen Code im Prozess auszuführen. Daher wird dieser Ladungsbefehl nur in dyld-Builds mit #define SUPPORT_LC_DYLD_ENVIRONMENT verwendet und beschränkt die Verarbeitung weiterhin nur auf Variablen im Format DYLD_..._PATH, die Ladepfade angeben.
LC_LOAD_DYLIB
Dieser Ladungsbefehl beschreibt eine dynamische Bibliotheksabhängigkeit, die den Loader (dyld) anweist, diese Bibliothek zu laden und zu verknüpfen. Es gibt einen LC_LOAD_DYLIB-Ladungsbefehl für jede Bibliothek, die die Mach-O-Binärdatei benötigt.
Dieser Ladungsbefehl ist eine Struktur vom Typ dylib_command (die eine Struktur dylib enthält, die die tatsächliche abhängige dynamische Bibliothek beschreibt):
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*/
};
Sie könnten diese Informationen auch über die Befehlszeile mit erhalten:
Einige potenziell mit Malware verbundene Bibliotheken sind:
DiskArbitration: Überwachung von USB-Laufwerken
AVFoundation: Aufnahme von Audio und Video
CoreWLAN: WLAN-Scans.
Ein Mach-O-Binärfile kann einen oder mehrere Konstruktoren enthalten, die vor der in LC_MAIN angegebenen Adresse ausgeführt werden.
Die Offsets aller Konstruktoren werden im Abschnitt __mod_init_func des Segments __DATA_CONST gespeichert.
Mach-O-Daten
Im Kern der Datei befindet sich der Datenbereich, der aus mehreren Segmenten besteht, wie im Bereich der Ladungsbefehle definiert. In jedem Segment können verschiedene Datensektionen untergebracht sein, wobei jede Sektion Code oder Daten spezifisch für einen Typ enthält.
Die Daten sind im Wesentlichen der Teil, der alle Informationen enthält, die von den Ladungsbefehlen LC_SEGMENTS_64 geladen werden.
Dazu gehören:
Funktionstabelle: Die Informationen über die Programmfunktionen enthält.
Symboltabelle: Enthält Informationen über die externen Funktionen, die vom Binärfile verwendet werden.
Es könnte auch interne Funktionen, Variablennamen und mehr enthalten.
Um dies zu überprüfen, könnten Sie das Mach-O View Tool verwenden:
Oder über die Befehlszeile:
size-m/bin/ls
Objektive-C Gemeinsame Abschnitte
Im __TEXT Segment (r-x):
__objc_classname: Klassennamen (Zeichenketten)
__objc_methname: Methodennamen (Zeichenketten)
__objc_methtype: Methodentypen (Zeichenketten)
Im __DATA Segment (rw-):
__objc_classlist: Zeiger auf alle Objektive-C-Klassen
__objc_nlclslist: Zeiger auf nicht-lazy Objektive-C-Klassen
__objc_catlist: Zeiger auf Kategorien
__objc_nlcatlist: Zeiger auf nicht-lazy Kategorien