macOS Universal binaries & Mach-O Format
Last updated
Last updated
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Di solito i binari di Mac OS sono compilati come universal binaries. Un universal binary può supportare più architetture nello stesso file.
Questi binari seguono la struttura Mach-O che è composta principalmente da:
Header
Comandi di Caricamento
Dati
Cerca il file con: mdfind fat.h | grep -i mach-o | grep -E "fat.h$"
L'intestazione ha i byte magic seguiti dal numero di architetture contenute nel file (nfat_arch
) e ogni architettura avrà una struttura fat_arch
.
Controlla con:
o usando lo strumento Mach-O View:
Come potresti pensare, di solito un binary universale compilato per 2 architetture raddoppia la dimensione di uno compilato per una sola architettura.
L'intestazione contiene informazioni di base sul file, come i byte magici per identificarlo come file Mach-O e informazioni sull'architettura di destinazione. 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 precaricato (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 .dSym
compagno (file con simboli per il debug).
MH_KEXT_BUNDLE
: Estensioni del kernel.
Oppure utilizzando 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 definiti deboli
MH_BINDS_TO_WEAK
: Il binario utilizza simboli deboli
MH_ALLOW_STACK_EXECUTION
: Rendere lo stack eseguibile
MH_NO_REEXPORTED_DYLIBS
: Libreria senza comandi LC_REEXPORT
MH_PIE
: Esecuzione indipendente dalla posizione
MH_HAS_TLV_DESCRIPTORS
: C'è una sezione con variabili locali al thread
MH_NO_HEAP_EXECUTION
: Nessuna esecuzione per pagine heap/dati
MH_HAS_OBJC
: Il binario ha sezioni Objective-C
MH_SIM_SUPPORT
: Supporto del simulatore
MH_DYLIB_IN_CACHE
: Usato su dylib/framework nella cache delle librerie condivise.
La struttura del file in memoria è specificata qui, dettagliando la posizione della tabella dei simboli, il contesto del thread principale all'avvio dell'esecuzione e le librerie condivise richieste. Sono fornite istruzioni al caricatore dinamico (dyld) sul processo di caricamento del binario in memoria.
Viene utilizzata 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 in base agli offset indicati nella sezione dei Dati quando il binario viene eseguito.
Questi comandi definiscono segmenti che vengono mappati nello spazio di memoria virtuale di un processo quando viene eseguito.
Ci sono diversi tipi di segmenti, come il segmento __TEXT, che contiene il codice eseguibile di un programma, e il segmento __DATA, che contiene dati utilizzati dal processo. Questi segmenti sono situati nella sezione dei 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 trovi prima l'intestazione del segmento:
Esempio di intestazione del segmento:
Questa intestazione definisce il numero di sezioni le cui intestazioni appaiono dopo di essa:
Esempio di intestazione di sezione:
Se si aggiunge l'offset della sezione (0x37DC) + l'offset in cui inizia l'architettura, in questo caso 0x18000
--> 0x37DC + 0x18000 = 0x1B7DC
È anche possibile ottenere le informazioni sull'intestazione dalla riga di comando con:
I segmenti comuni caricati da questo 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 su zero per indicare che non ci sono diritti di lettura-scrittura-esecuzione su questa pagina.
Questa allocazione è importante per mitigare le vulnerabilità di dereferenziazione del puntatore NULL. Questo perché XNU impone una pagina zero rigida 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 (usando -pagezero_size
) per coprire i primi 4k e rendere il resto della memoria a 32 bit accessibile sia in modalità utente che kernel.
__TEXT
: Contiene codice eseguibile con permessi di lettura ed esecuzione (non scrivibile). Sezioni comuni di questo segmento:
__text
: Codice binario compilato
__const
: Dati costanti (solo lettura)
__[c/u/os_log]string
: Costanti di stringhe 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.
Si noti che tutto questo contenuto è firmato ma anche contrassegnato come eseguibile (creando più opzioni per lo sfruttamento di sezioni che non necessariamente richiedono 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 al simbolo non lazy (bind al caricamento)
__la_symbol_ptr
: Puntatore al simbolo lazy (bind all'uso)
__const
: Dovrebbe essere dati di sola lettura (ma non lo è realmente)
__cfstring
: Stringhe di CoreFoundation
__data
: Variabili globali (che sono state inizializzate)
__bss
: Variabili statiche (che non sono state inizializzate)
__objc_*
(__objc_classlist, __objc_protolist, ecc): Informazioni utilizzate dall'Objective-C runtime
__DATA_CONST
: __DATA.__const non è garantito essere costante (permessi di scrittura), così come gli 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, opcode di binding non-lazy/lazy/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 di puntatore/stub
Tabella delle stringhe
Firma del codice
__OBJC
: Contiene informazioni utilizzate dall'Objective-C runtime. Anche se queste informazioni potrebbero essere trovate nel segmento __DATA, all'interno di varie sezioni in __objc_*.
__RESTRICT
: Un segmento senza contenuto con una singola sezione chiamata __restrict
(anche vuota) che garantisce che quando si esegue il binario, verranno ignorate le variabili ambientali DYLD.
Come è stato possibile vedere nel codice, i segmenti supportano anche dei flag (anche se non vengono utilizzati molto):
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 (in memoria) base del binario, quindi salta a questa istruzione per avviare l'esecuzione del codice binario.
LC_UNIXTHREAD
contiene i valori che i registri devono avere all'avvio del thread principale. Questo è 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 Mach-O. Contiene solo un offset che punta al blocco della firma. Di solito si trova alla fine del file. Tuttavia, è possibile trovare ulteriori informazioni su questa sezione in questo post sul blog e in questo gists.
LC_ENCRYPTION_INFO[_64]
Supporto per la crittografia binaria. Tuttavia, naturalmente, se un attaccante riesce a compromettere il processo, sarà in grado di scaricare la memoria non crittografata.
LC_LOAD_DYLINKER
Contiene il percorso dell'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 dump core Mach-O e la versione del kernel è impostata nel comando LC_IDENT
.
LC_UUID
UUID casuale. È utile per niente direttamente ma XNU lo memorizza con il resto delle informazioni sul processo. Può essere utilizzato nei report di crash.
LC_DYLD_ENVIRONMENT
Permette di indicare le variabili d'ambiente al dyld prima che il processo venga eseguito. Questo può essere molto pericoloso in quanto consente di eseguire codice arbitrario all'interno del processo, quindi questo comando di caricamento viene utilizzato solo in dyld compilati con #define SUPPORT_LC_DYLD_ENVIRONMENT
e limita 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 da libreria dinamica che istruisce il caricatore (dyld) a caricare e collegare tale libreria. C'è un comando di caricamento LC_LOAD_DYLIB
per ogni libreria richiesta dal binario Mach-O.
Questo comando di caricamento è una struttura di tipo dylib_command
(che contiene una struttura dylib, descrivendo la libreria dinamica dipendente effettiva):
È possibile ottenere queste informazioni anche da riga di comando con:
Alcune potenziali librerie 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 eventuali costruttori sono contenuti nella sezione __mod_init_func del segmento __DATA_CONST.
Al centro del file si trova la regione dei dati, composta da diversi segmenti come definito nella regione dei comandi di caricamento. Una varietà di sezioni dati può essere contenuta in ciascun segmento, con ciascuna sezione che contiene codice o dati specifici per un tipo.
I dati sono essenzialmente la parte che contiene tutte le informazioni caricate dai comandi di caricamento LC_SEGMENTS_64
Ciò include:
Tabella delle funzioni: Che contiene informazioni sulle funzioni del programma.
Tabella dei simboli: Che contiene informazioni sulle funzioni esterne utilizzate dal binario
Potrebbe contenere anche funzioni interne, nomi di variabili e altro.
Per controllarlo, potresti utilizzare lo strumento Mach-O View:
O tramite la riga di comando:
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 alle classi Objective-C non lazy
__objc_catlist
: Puntatore alle Categorie
__objc_nlcatlist
: Puntatore alle 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