macOS Universal binaries & Mach-O Format
Last updated
Last updated
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
I binari di Mac OS sono solitamente compilati come universal binaries. Un universal binary può supportare più architetture nello stesso file.
Questi binari seguono la struttura Mach-O che è fondamentalmente composta da:
Header
Load Commands
Data
Cerca il file con: mdfind fat.h | grep -i mach-o | grep -E "fat.h$"
L'header ha i byte magic seguiti dal numero di architetture che il file contiene (nfat_arch
) e ogni architettura avrà una struttura fat_arch
.
Controllalo con:
o utilizzando lo strumento Mach-O View:
Come potresti pensare, di solito un universal binary compilato per 2 architetture raddoppia la dimensione di uno compilato per solo 1 arch.
L'header contiene informazioni di base sul file, come i byte magic per identificarlo come un file Mach-O e informazioni sull'architettura target. Puoi trovarlo in: mdfind loader.h | grep -i mach-o | grep -E "loader.h$"
Ci sono diversi tipi di file, puoi trovarli definiti nel codice sorgente ad esempio qui. I più importanti sono:
MH_OBJECT
: File oggetto relocabile (prodotti intermedi della compilazione, non ancora eseguibili).
MH_EXECUTE
: File eseguibili.
MH_FVMLIB
: File di libreria VM fissa.
MH_CORE
: Dump di codice
MH_PRELOAD
: File eseguibile pre-caricato (non più supportato in XNU)
MH_DYLIB
: Librerie dinamiche
MH_DYLINKER
: Linker dinamico
MH_BUNDLE
: "File plugin". Generati utilizzando -bundle in gcc e caricati esplicitamente da NSBundle
o dlopen
.
MH_DYSM
: File companion .dSym
(file con simboli per il debug).
MH_KEXT_BUNDLE
: Estensioni del kernel.
Or usando Mach-O View:
Il codice sorgente definisce anche diversi flag utili per il caricamento delle librerie:
MH_NOUNDEFS
: Nessun riferimento non definito (completamente collegato)
MH_DYLDLINK
: Collegamento Dyld
MH_PREBOUND
: Riferimenti dinamici precollegati.
MH_SPLIT_SEGS
: File divide segmenti r/o e r/w.
MH_WEAK_DEFINES
: Il binario ha simboli debolmente definiti
MH_BINDS_TO_WEAK
: Il binario utilizza simboli deboli
MH_ALLOW_STACK_EXECUTION
: Rendi lo stack eseguibile
MH_NO_REEXPORTED_DYLIBS
: Libreria non comandi LC_REEXPORT
MH_PIE
: Eseguibile indipendente dalla posizione
MH_HAS_TLV_DESCRIPTORS
: C'è una sezione con variabili locali al thread
MH_NO_HEAP_EXECUTION
: Nessuna esecuzione per heap/pagine dati
MH_HAS_OBJC
: Il binario ha sezioni oBject-C
MH_SIM_SUPPORT
: Supporto per simulatori
MH_DYLIB_IN_CACHE
: Utilizzato su dylibs/frameworks nella cache delle librerie condivise.
Il layout del file in memoria è specificato qui, dettagliando la posizione della tabella dei simboli, il contesto del thread principale all'inizio dell'esecuzione e le librerie condivise richieste. Vengono fornite istruzioni al caricatore dinamico (dyld) sul processo di caricamento del binario in memoria.
Utilizza la struttura load_command, definita nel menzionato loader.h
:
Ci sono circa 50 diversi tipi di comandi di caricamento che il sistema gestisce in modo diverso. I più comuni sono: LC_SEGMENT_64
, LC_LOAD_DYLINKER
, LC_MAIN
, LC_LOAD_DYLIB
e LC_CODE_SIGNATURE
.
Fondamentalmente, questo tipo di comando di caricamento definisce come caricare il __TEXT (codice eseguibile) e il __DATA (dati per il processo) segmenti secondo gli offset indicati nella sezione Dati quando il binario viene eseguito.
Questi comandi definiscono segmenti che sono mappati nello spazio di memoria virtuale di un processo quando viene eseguito.
Ci sono diversi tipi di segmenti, come il __TEXT segmento, che contiene il codice eseguibile di un programma, e il __DATA segmento, che contiene dati utilizzati dal processo. Questi segmenti si trovano nella sezione dati del file Mach-O.
Ogni segmento può essere ulteriormente diviso in più sezioni. La struttura del comando di caricamento contiene informazioni su queste sezioni all'interno del rispettivo segmento.
Nell'intestazione prima trovi l'intestazione del segmento:
Esempio di intestazione del segmento:
Questa intestazione definisce il numero di sezioni i cui intestazioni appaiono dopo di essa:
Esempio di intestazione di sezione:
Se aggiungi l'offset di sezione (0x37DC) + l'offset dove inizia l'arch, in questo caso 0x18000
--> 0x37DC + 0x18000 = 0x1B7DC
È anche possibile ottenere informazioni sugli header dalla riga di comando con:
Common segments loaded by this cmd:
__PAGEZERO
: Istruisce il kernel a mappare l'indirizzo zero in modo che non possa essere letto, scritto o eseguito. Le variabili maxprot e minprot nella struttura sono impostate a zero per indicare che non ci sono diritti di lettura-scrittura-esecuzione su questa pagina.
Questa allocazione è importante per mitigare le vulnerabilità di dereferenziazione di puntatori NULL. Questo perché XNU applica una rigida pagina zero che garantisce che la prima pagina (solo la prima) della memoria sia inaccessibile (eccetto in i386). Un binario potrebbe soddisfare questi requisiti creando un piccolo __PAGEZERO (utilizzando -pagezero_size
) per coprire i primi 4k e avere il resto della memoria a 32 bit accessibile sia in modalità utente che in modalità kernel.
__TEXT
: Contiene codice eseguibile con permessi di lettura e esecuzione (non scrivibile). Sezioni comuni di questo segmento:
__text
: Codice binario compilato
__const
: Dati costanti (solo lettura)
__[c/u/os_log]string
: Costanti di stringa C, Unicode o os logs
__stubs
e __stubs_helper
: Coinvolti durante il processo di caricamento della libreria dinamica
__unwind_info
: Dati di unwind dello stack.
Nota che tutto questo contenuto è firmato ma anche contrassegnato come eseguibile (creando più opzioni per lo sfruttamento di sezioni che non necessitano necessariamente di questo privilegio, come le sezioni dedicate alle stringhe).
__DATA
: Contiene dati che sono leggibili e scrivibili (non eseguibili).
__got:
Tabella degli offset globali
__nl_symbol_ptr
: Puntatore simbolo non pigro (binding al caricamento)
__la_symbol_ptr
: Puntatore simbolo pigro (binding all'uso)
__const
: Dovrebbe essere dati di sola lettura (non realmente)
__cfstring
: Stringhe CoreFoundation
__data
: Variabili globali (che sono state inizializzate)
__bss
: Variabili statiche (che non sono state inizializzate)
__objc_*
(__objc_classlist, __objc_protolist, ecc): Informazioni utilizzate dal runtime Objective-C
__DATA_CONST
: __DATA.__const non è garantito che sia costante (permessi di scrittura), né lo sono altri puntatori e la GOT. Questa sezione rende __const
, alcuni inizializzatori e la tabella GOT (una volta risolta) solo lettura utilizzando mprotect
.
__LINKEDIT
: Contiene informazioni per il linker (dyld) come, simboli, stringhe e voci della tabella di rilocazione. È un contenitore generico per contenuti che non sono né in __TEXT
né in __DATA
e il suo contenuto è descritto in altri comandi di caricamento.
informazioni dyld: Rebase, opcodes di binding non pigro/pigro/debole e informazioni di esportazione
Inizio delle funzioni: Tabella degli indirizzi di inizio delle funzioni
Dati nel codice: Isole di dati in __text
Tabella dei simboli: Simboli nel binario
Tabella dei simboli indiretti: Simboli puntatore/stub
Tabella delle stringhe
Firma del codice
__OBJC
: Contiene informazioni utilizzate dal runtime Objective-C. Anche se queste informazioni potrebbero essere trovate anche nel segmento __DATA, all'interno di varie sezioni __objc_*.
__RESTRICT
: Un segmento senza contenuto con una singola sezione chiamata __restrict
(anch'essa vuota) che garantisce che quando si esegue il binario, ignorerà le variabili ambientali DYLD.
Come era possibile vedere nel codice, i segmenti supportano anche flag (anche se non sono molto utilizzati):
SG_HIGHVM
: Solo core (non utilizzato)
SG_FVMLIB
: Non utilizzato
SG_NORELOC
: Il segmento non ha rilocazione
SG_PROTECTED_VERSION_1
: Crittografia. Utilizzato ad esempio da Finder per crittografare il segmento di testo __TEXT
.
LC_UNIXTHREAD/LC_MAIN
LC_MAIN
contiene il punto di ingresso nell'attributo entryoff. Al momento del caricamento, dyld semplicemente aggiunge questo valore alla base del binario (in memoria), poi salta a questa istruzione per avviare l'esecuzione del codice del binario.
LC_UNIXTHREAD
contiene i valori che il registro deve avere quando si avvia il thread principale. Questo era già deprecato ma dyld
lo utilizza ancora. È possibile vedere i valori dei registri impostati da questo con:
LC_CODE_SIGNATURE
Contiene informazioni sulla firma del codice del file Macho-O. Contiene solo un offset che punta al blob della firma. Questo si trova tipicamente alla fine del file. Tuttavia, puoi trovare alcune informazioni su questa sezione in questo post del blog e in questo gist.
LC_ENCRYPTION_INFO[_64]
Supporto per la crittografia binaria. Tuttavia, ovviamente, se un attaccante riesce a compromettere il processo, sarà in grado di dumpare la memoria non crittografata.
LC_LOAD_DYLINKER
Contiene il percorso all'eseguibile del linker dinamico che mappa le librerie condivise nello spazio degli indirizzi del processo. Il valore è sempre impostato su /usr/lib/dyld
. È importante notare che in macOS, il mapping delle dylib avviene in modalità utente, non in modalità kernel.
LC_IDENT
Obsoleto, ma quando configurato per generare dump in caso di panico, viene creato un core dump Mach-O e la versione del kernel è impostata nel comando LC_IDENT
.
LC_UUID
UUID casuale. È utile per qualsiasi cosa direttamente, ma XNU lo memorizza nella cache con il resto delle informazioni sul processo. Può essere utilizzato nei rapporti di crash.
LC_DYLD_ENVIRONMENT
Consente di indicare le variabili di ambiente al dyld prima che il processo venga eseguito. Questo può essere molto pericoloso poiché può consentire di eseguire codice arbitrario all'interno del processo, quindi questo comando di caricamento è utilizzato solo in dyld costruito con #define SUPPORT_LC_DYLD_ENVIRONMENT
e restringe ulteriormente l'elaborazione solo alle variabili della forma DYLD_..._PATH
specificando i percorsi di caricamento.
LC_LOAD_DYLIB
Questo comando di caricamento descrive una dipendenza di libreria dinamica che istruisce il loader (dyld) a caricare e collegare la suddetta libreria. C'è un comando di caricamento LC_LOAD_DYLIB
per ogni libreria di cui il binario Mach-O ha bisogno.
Questo comando di caricamento è una struttura di tipo dylib_command
(che contiene una struct dylib, che descrive la libreria dinamica dipendente effettiva):
Puoi anche ottenere queste informazioni dalla riga di comando con:
Alcune librerie potenzialmente correlate al malware sono:
DiskArbitration: Monitoraggio delle unità USB
AVFoundation: Cattura audio e video
CoreWLAN: Scansioni Wifi.
Un binario Mach-O può contenere uno o più costruttori, che verranno eseguiti prima dell'indirizzo specificato in LC_MAIN. Gli offset di qualsiasi costruttore sono contenuti nella sezione __mod_init_func del segmento __DATA_CONST.
Al centro del file si trova la regione dati, che è composta da diversi segmenti come definiti nella regione dei comandi di caricamento. Una varietà di sezioni dati può essere ospitata all'interno di ciascun segmento, con ciascuna sezione che contiene codice o dati specifici per un tipo.
I dati sono fondamentalmente la parte che contiene tutte le informazioni che vengono caricate dai comandi di caricamento LC_SEGMENTS_64
Questo include:
Tabella delle funzioni: Che contiene informazioni sulle funzioni del programma.
Tabella dei simboli: Che contiene informazioni sulle funzioni esterne utilizzate dal binario
Potrebbe anche contenere nomi di funzioni interne, variabili e altro ancora.
Per controllarlo, puoi utilizzare lo strumento Mach-O View:
O dalla cli:
Nel segmento __TEXT
(r-x):
__objc_classname
: Nomi delle classi (stringhe)
__objc_methname
: Nomi dei metodi (stringhe)
__objc_methtype
: Tipi dei metodi (stringhe)
Nel segmento __DATA
(rw-):
__objc_classlist
: Puntatori a tutte le classi Objective-C
__objc_nlclslist
: Puntatori a classi Objective-C Non-Lazy
__objc_catlist
: Puntatore a Categorie
__objc_nlcatlist
: Puntatore a Categorie Non-Lazy
__objc_protolist
: Elenco dei protocolli
__objc_const
: Dati costanti
__objc_imageinfo
, __objc_selrefs
, objc__protorefs
...
_swift_typeref
, _swift3_capture
, _swift3_assocty
, _swift3_types, _swift3_proto
, _swift3_fieldmd
, _swift3_builtin
, _swift3_reflstr
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)