macOS IPC - Inter Process Communication
Last updated
Last updated
Impara e pratica Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Mach utilizza compiti come la più piccola unità per condividere risorse, e ogni compito può contenere più thread. Questi compiti e thread sono mappati 1:1 a processi e thread POSIX.
La comunicazione tra compiti avviene tramite la Comunicazione Inter-Processo Mach (IPC), utilizzando canali di comunicazione unidirezionali. I messaggi vengono trasferiti tra porte, che fungono da code di messaggi gestite dal kernel.
Una porta è l'elemento base dell'IPC Mach. Può essere utilizzata per inviare messaggi e riceverli.
Ogni processo ha una tabella IPC, in cui è possibile trovare le porte mach del processo. Il nome di una porta mach è in realtà un numero (un puntatore all'oggetto kernel).
Un processo può anche inviare un nome di porta con alcuni diritti a un compito diverso e il kernel farà apparire questa voce nella tabella IPC dell'altro compito.
I diritti di porta, che definiscono quali operazioni un compito può eseguire, sono fondamentali per questa comunicazione. I possibili diritti di porta sono (definizioni da qui):
Diritto di ricezione, che consente di ricevere messaggi inviati alla porta. Le porte Mach sono code MPSC (produttore multiplo, consumatore singolo), il che significa che può esserci solo un diritto di ricezione per ogni porta nell'intero sistema (a differenza delle pipe, dove più processi possono detenere descrittori di file per l'estremità di lettura di una pipe).
Un compito con il Diritto di Ricezione può ricevere messaggi e creare diritti di Invio, consentendogli di inviare messaggi. Inizialmente solo il proprio compito ha il Diritto di Ricezione sulla sua porta.
Se il proprietario del Diritto di Ricezione muore o lo uccide, il diritto di invio diventa inutile (nome morto).
Diritto di invio, che consente di inviare messaggi alla porta.
Il Diritto di invio può essere clonato in modo che un compito che possiede un Diritto di invio possa clonare il diritto e concederlo a un terzo compito.
Nota che i diritti di porta possono anche essere trasmessi tramite messaggi Mac.
Diritto di invio una sola volta, che consente di inviare un messaggio alla porta e poi scompare.
Questo diritto non può essere clonato, ma può essere spostato.
Diritto di insieme di porte, che denota un insieme di porte piuttosto che una singola porta. Dequeuing un messaggio da un insieme di porte estrae un messaggio da una delle porte che contiene. Gli insiemi di porte possono essere utilizzati per ascoltare su più porte simultaneamente, molto simile a select
/poll
/epoll
/kqueue
in Unix.
Nome morto, che non è un vero diritto di porta, ma solo un segnaposto. Quando una porta viene distrutta, tutti i diritti di porta esistenti per la porta si trasformano in nomi morti.
I compiti possono trasferire diritti di INVIO ad altri, consentendo loro di inviare messaggi di ritorno. I diritti di INVIO possono anche essere clonati, quindi un compito può duplicare e dare il diritto a un terzo compito. Questo, combinato con un processo intermedio noto come bootstrap server, consente una comunicazione efficace tra i compiti.
Le porte di file consentono di racchiudere descrittori di file in porte Mac (utilizzando diritti di porta Mach). È possibile creare un fileport
da un FD dato utilizzando fileport_makeport
e creare un FD da un fileport utilizzando fileport_makefd
.
Come accennato in precedenza, è possibile inviare diritti utilizzando messaggi Mach, tuttavia, non puoi inviare un diritto senza avere già un diritto per inviare un messaggio Mach. Quindi, come viene stabilita la prima comunicazione?
Per questo, il bootstrap server (launchd in mac) è coinvolto, poiché chiunque può ottenere un diritto di INVIO al bootstrap server, è possibile chiedergli un diritto per inviare un messaggio a un altro processo:
Il compito A crea una nuova porta, ottenendo il DIRITTO DI RICEZIONE su di essa.
Il compito A, essendo il detentore del Diritto di Ricezione, genera un Diritto di INVIO per la porta.
Il compito A stabilisce una connessione con il bootstrap server, e gli invia il Diritto di INVIO per la porta che ha generato all'inizio.
Ricorda che chiunque può ottenere un Diritto di INVIO al bootstrap server.
Il compito A invia un messaggio bootstrap_register
al bootstrap server per associare la porta data a un nome come com.apple.taska
Il compito B interagisce con il bootstrap server per eseguire un bootstrap lookup per il nome del servizio (bootstrap_lookup
). Affinché il bootstrap server possa rispondere, il compito B gli invierà un Diritto di INVIO a una porta che ha precedentemente creato all'interno del messaggio di lookup. Se il lookup ha successo, il server duplica il Diritto di INVIO ricevuto dal Compito A e lo trasmette al Compito B.
Ricorda che chiunque può ottenere un Diritto di INVIO al bootstrap server.
Con questo Diritto di INVIO, il Compito B è in grado di inviare un messaggio al Compito A.
Per una comunicazione bidirezionale, di solito il compito B genera una nuova porta con un DIRITTO DI RICEZIONE e un DIRITTO DI INVIO, e dà il DIRITTO DI INVIO al Compito A in modo che possa inviare messaggi al COMPITO B (comunicazione bidirezionale).
Il bootstrap server non può autenticare il nome del servizio rivendicato da un compito. Questo significa che un compito potrebbe potenzialmente impersonare qualsiasi compito di sistema, come rivendicare falsamente un nome di servizio di autorizzazione e poi approvare ogni richiesta.
Poi, Apple memorizza i nomi dei servizi forniti dal sistema in file di configurazione sicuri, situati in directory protette da SIP: /System/Library/LaunchDaemons
e /System/Library/LaunchAgents
. Accanto a ciascun nome di servizio, è anche memorizzato il binario associato. Il bootstrap server creerà e manterrà un DIRITTO DI RICEZIONE per ciascuno di questi nomi di servizio.
Per questi servizi predefiniti, il processo di lookup differisce leggermente. Quando un nome di servizio viene cercato, launchd avvia il servizio dinamicamente. Il nuovo flusso di lavoro è il seguente:
Il compito B avvia un lookup bootstrap per un nome di servizio.
launchd controlla se il compito è in esecuzione e, se non lo è, lo avvia.
Il compito A (il servizio) esegue un bootstrap check-in (bootstrap_check_in()
). Qui, il bootstrap server crea un Diritto di INVIO, lo trattiene e trasferisce il Diritto di RICEZIONE al Compito A.
launchd duplica il Diritto di INVIO e lo invia al Compito B.
Il compito B genera una nuova porta con un DIRITTO DI RICEZIONE e un DIRITTO DI INVIO, e dà il DIRITTO DI INVIO al Compito A (il svc) in modo che possa inviare messaggi al COMPITO B (comunicazione bidirezionale).
Tuttavia, questo processo si applica solo ai compiti di sistema predefiniti. I compiti non di sistema operano ancora come descritto originariamente, il che potrebbe potenzialmente consentire l'impersonificazione.
Pertanto, launchd non dovrebbe mai bloccarsi o l'intero sistema si bloccherà.
Trova ulteriori informazioni qui
La funzione mach_msg
, essenzialmente una chiamata di sistema, è utilizzata per inviare e ricevere messaggi Mach. La funzione richiede che il messaggio da inviare sia l'argomento iniziale. Questo messaggio deve iniziare con una struttura mach_msg_header_t
, seguita dal contenuto del messaggio effettivo. La struttura è definita come segue:
I process che possiedono un diritto di ricezione possono ricevere messaggi su una porta Mach. Al contrario, ai mittenti viene concesso un diritto di invio o un diritto di invio-una-volta. Il diritto di invio-una-volta è esclusivamente per l'invio di un singolo messaggio, dopo di che diventa non valido.
Il campo iniziale msgh_bits
è un bitmap:
Il primo bit (il più significativo) è utilizzato per indicare che un messaggio è complesso (ne parleremo più avanti)
Il 3° e il 4° sono utilizzati dal kernel
I 5 bit meno significativi del 2° byte possono essere utilizzati per voucher: un altro tipo di porta per inviare combinazioni chiave/valore.
I 5 bit meno significativi del 3° byte possono essere utilizzati per porta locale
I 5 bit meno significativi del 4° byte possono essere utilizzati per porta remota
I tipi che possono essere specificati nel voucher, nelle porte locali e remote sono (da mach/message.h):
Per esempio, MACH_MSG_TYPE_MAKE_SEND_ONCE
può essere utilizzato per indicare che un diritto di invio una sola volta dovrebbe essere derivato e trasferito per questo porto. Può anche essere specificato MACH_PORT_NULL
per impedire al destinatario di poter rispondere.
Per ottenere una facile comunicazione bidirezionale, un processo può specificare un porto mach nell'intestazione del messaggio mach chiamato porto di risposta (msgh_local_port
) dove il ricevitore del messaggio può inviare una risposta a questo messaggio.
Nota che questo tipo di comunicazione bidirezionale è utilizzato nei messaggi XPC che si aspettano una risposta (xpc_connection_send_message_with_reply
e xpc_connection_send_message_with_reply_sync
). Ma di solito vengono creati porti diversi come spiegato in precedenza per creare la comunicazione bidirezionale.
Gli altri campi dell'intestazione del messaggio sono:
msgh_size
: la dimensione dell'intero pacchetto.
msgh_remote_port
: il porto su cui questo messaggio è inviato.
msgh_voucher_port
: voucher mach.
msgh_id
: l'ID di questo messaggio, che è interpretato dal ricevitore.
Nota che i messaggi mach vengono inviati su un mach port
, che è un canale di comunicazione a singolo ricevitore, multiplo mittente integrato nel kernel mach. Più processi possono inviare messaggi a un porto mach, ma in qualsiasi momento solo un singolo processo può leggere da esso.
I messaggi sono quindi formati dall'intestazione mach_msg_header_t
seguita dal corpo e dal trailer (se presente) e possono concedere il permesso di rispondere. In questi casi, il kernel deve solo passare il messaggio da un'attività all'altra.
Un trailer è informazione aggiunta al messaggio dal kernel (non può essere impostata dall'utente) che può essere richiesta nella ricezione del messaggio con i flag MACH_RCV_TRAILER_<trailer_opt>
(ci sono diverse informazioni che possono essere richieste).
Tuttavia, ci sono altri messaggi più complessi, come quelli che passano diritti di porto aggiuntivi o condividono memoria, dove il kernel deve anche inviare questi oggetti al destinatario. In questi casi, il bit più significativo dell'intestazione msgh_bits
è impostato.
I possibili descrittori da passare sono definiti in mach/message.h
:
In 32 bit, tutti i descrittori sono 12B e il tipo di descrittore si trova nell'undicesimo. In 64 bit, le dimensioni variano.
Il kernel copierà i descrittori da un task all'altro ma prima creando una copia nella memoria del kernel. Questa tecnica, nota come "Feng Shui", è stata abusata in diversi exploit per far sì che il kernel copi i dati nella sua memoria, facendo in modo che un processo invii descrittori a se stesso. Poi il processo può ricevere i messaggi (il kernel li libererà).
È anche possibile inviare diritti di porta a un processo vulnerabile, e i diritti di porta appariranno semplicemente nel processo (anche se non li sta gestendo).
Nota che le porte sono associate allo spazio dei nomi del task, quindi per creare o cercare una porta, viene interrogato anche lo spazio dei nomi del task (maggiori informazioni in mach/mach_port.h
):
mach_port_allocate
| mach_port_construct
: Crea una porta.
mach_port_allocate
può anche creare un port set: diritto di ricezione su un gruppo di porte. Ogni volta che viene ricevuto un messaggio, viene indicata la porta da cui proviene.
mach_port_allocate_name
: Cambia il nome della porta (per impostazione predefinita intero 32 bit)
mach_port_names
: Ottieni i nomi delle porte da un target
mach_port_type
: Ottieni i diritti di un task su un nome
mach_port_rename
: Rinomina una porta (come dup2 per i FD)
mach_port_allocate
: Alloca un nuovo RECEIVE, PORT_SET o DEAD_NAME
mach_port_insert_right
: Crea un nuovo diritto in una porta dove hai RECEIVE
mach_port_...
mach_msg
| mach_msg_overwrite
: Funzioni utilizzate per inviare e ricevere messaggi mach. La versione overwrite consente di specificare un buffer diverso per la ricezione del messaggio (l'altra versione riutilizzerà semplicemente quello).
Poiché le funzioni mach_msg
e mach_msg_overwrite
sono quelle utilizzate per inviare e ricevere messaggi, impostare un breakpoint su di esse consentirebbe di ispezionare i messaggi inviati e ricevuti.
Ad esempio, inizia a fare debug di qualsiasi applicazione che puoi debuggare poiché caricherà libSystem.B
che utilizzerà questa funzione.
Per ottenere gli argomenti di mach_msg
controlla i registri. Questi sono gli argomenti (da mach/message.h):
Ottieni i valori dai registri:
Ispeziona l'intestazione del messaggio controllando il primo argomento:
Quel tipo di mach_msg_bits_t
è molto comune per consentire una risposta.
Il nome è il nome predefinito assegnato alla porta (controlla come sta aumentando nei primi 3 byte). L'ipc-object
è l'identificatore unico offuscato della porta.
Nota anche come le porte con solo diritto di send
stanno identificando il proprietario (nome della porta + pid).
Nota anche l'uso di +
per indicare altri compiti collegati alla stessa porta.
È anche possibile utilizzare procesxp per vedere anche i nomi dei servizi registrati (con SIP disabilitato a causa della necessità di com.apple.system-task-port
):
Puoi installare questo strumento su iOS scaricandolo da http://newosxbook.com/tools/binpack64-256.tar.gz
Nota come il mittente alloca una porta, crea un diritto di invio per il nome org.darlinghq.example
e lo invia al server di bootstrap mentre il mittente richiede il diritto di invio di quel nome e lo utilizza per inviare un messaggio.
Ci sono alcune porte speciali che consentono di eseguire determinate azioni sensibili o accedere a determinati dati sensibili nel caso in cui un'attività abbia i permessi SEND su di esse. Questo rende queste porte molto interessanti dal punto di vista di un attaccante non solo per le capacità, ma anche perché è possibile condividere i permessi SEND tra le attività.
Queste porte sono rappresentate da un numero.
I diritti SEND possono essere ottenuti chiamando host_get_special_port
e i diritti RECEIVE chiamando host_set_special_port
. Tuttavia, entrambe le chiamate richiedono la porta host_priv
a cui può accedere solo l'utente root. Inoltre, in passato, l'utente root era in grado di chiamare host_set_special_port
e di dirottare arbitrariamente, il che consentiva, ad esempio, di bypassare le firme del codice dirottando HOST_KEXTD_PORT
(SIP ora previene questo).
Queste sono divise in 2 gruppi: Le prime 7 porte sono di proprietà del kernel, essendo la 1 HOST_PORT
, la 2 HOST_PRIV_PORT
, la 3 HOST_IO_MASTER_PORT
e la 7 è HOST_MAX_SPECIAL_KERNEL_PORT
.
Quelle che iniziano dal numero 8 sono di proprietà dei demoni di sistema e possono essere trovate dichiarate in host_special_ports.h
.
Porte host: Se un processo ha il privilegio SEND su questa porta, può ottenere informazioni sul sistema chiamando le sue routine come:
host_processor_info
: Ottieni informazioni sul processore
host_info
: Ottieni informazioni sull'host
host_virtual_physical_table_info
: Tabella delle pagine Virtuali/Fisiche (richiede MACH_VMDEBUG)
host_statistics
: Ottieni statistiche dell'host
mach_memory_info
: Ottieni layout della memoria del kernel
Porte Privilege host: Un processo con diritto SEND su questa porta può eseguire azioni privilegiate come mostrare dati di avvio o tentare di caricare un'estensione del kernel. Il processo deve essere root per ottenere questo permesso.
Inoltre, per chiamare l'API kext_request
è necessario avere altri diritti com.apple.private.kext*
che sono concessi solo ai binari Apple.
Altre routine che possono essere chiamate sono:
host_get_boot_info
: Ottieni machine_boot_info()
host_priv_statistics
: Ottieni statistiche privilegiate
vm_allocate_cpm
: Alloca Memoria Fisica Contigua
host_processors
: Diritto di invio ai processori host
mach_vm_wire
: Rendi la memoria residente
Poiché root può accedere a questo permesso, potrebbe chiamare host_set_[special/exception]_port[s]
per dirottare porte speciali o di eccezione host.
È possibile vedere tutte le porte speciali host eseguendo:
Questi sono porte riservate per servizi ben noti. È possibile ottenerle/impostarle chiamando task_[get/set]_special_port
. Possono essere trovate in task_special_ports.h
:
Da qui:
TASK_KERNEL_PORT[diritto di invio task-self]: La porta utilizzata per controllare questo task. Usata per inviare messaggi che influenzano il task. Questa è la porta restituita da mach_task_self (vedi Task Ports qui sotto).
TASK_BOOTSTRAP_PORT[diritto di invio bootstrap]: La porta di bootstrap del task. Usata per inviare messaggi che richiedono il ritorno di altre porte di servizio di sistema.
TASK_HOST_NAME_PORT[diritto di invio host-self]: La porta utilizzata per richiedere informazioni sull'host contenitore. Questa è la porta restituita da mach_host_self.
TASK_WIRED_LEDGER_PORT[diritto di invio ledger]: La porta che nomina la sorgente da cui questo task attinge la sua memoria kernel wired.
TASK_PAGED_LEDGER_PORT[diritto di invio ledger]: La porta che nomina la sorgente da cui questo task attinge la sua memoria gestita di default.
Originariamente Mach non aveva "processi", aveva "task" che erano considerati più come un contenitore di thread. Quando Mach è stato fuso con BSD ogni task era correlato a un processo BSD. Pertanto, ogni processo BSD ha i dettagli necessari per essere un processo e ogni task Mach ha anche il suo funzionamento interno (eccetto per il pid 0 inesistente che è il kernel_task
).
Ci sono due funzioni molto interessanti relative a questo:
task_for_pid(target_task_port, pid, &task_port_of_pid)
: Ottieni un diritto di INVIO per la porta del task relativo a quello specificato dal pid
e assegnalo alla target_task_port
indicata (che di solito è il task chiamante che ha usato mach_task_self()
, ma potrebbe essere una porta di INVIO su un task diverso).
pid_for_task(task, &pid)
: Dato un diritto di INVIO a un task, trova a quale PID è correlato questo task.
Per eseguire azioni all'interno del task, il task aveva bisogno di un diritto di SEND
su se stesso chiamando mach_task_self()
(che utilizza il task_self_trap
(28)). Con questo permesso un task può eseguire diverse azioni come:
task_threads
: Ottieni diritto di INVIO su tutte le porte dei task dei thread del task
task_info
: Ottieni informazioni su un task
task_suspend/resume
: Sospendi o riprendi un task
task_[get/set]_special_port
thread_create
: Crea un thread
task_[get/set]_state
: Controlla lo stato del task
e altro può essere trovato in mach/task.h
Nota che con un diritto di INVIO su una porta task di un task diverso, è possibile eseguire tali azioni su un task diverso.
Inoltre, la task_port è anche la vm_map
porta che consente di leggere e manipolare la memoria all'interno di un task con funzioni come vm_read()
e vm_write()
. Questo significa fondamentalmente che un task con diritti di INVIO sulla task_port di un task diverso sarà in grado di iniettare codice in quel task.
Ricorda che poiché il kernel è anche un task, se qualcuno riesce a ottenere un diritto di INVIO sul kernel_task
, sarà in grado di far eseguire al kernel qualsiasi cosa (jailbreak).
Chiama mach_task_self()
per ottenere il nome per questa porta per il task chiamante. Questa porta è solo ereditata attraverso exec()
; un nuovo task creato con fork()
ottiene una nuova porta task (come caso speciale, un task ottiene anche una nuova porta task dopo exec()
in un binario suid). L'unico modo per generare un task e ottenere la sua porta è eseguire il "port swap dance" mentre si esegue un fork()
.
Queste sono le restrizioni per accedere alla porta (da macos_task_policy
dal binario AppleMobileFileIntegrity
):
Se l'app ha il diritto com.apple.security.get-task-allow
i processi dello stesso utente possono accedere alla porta del task (comunemente aggiunto da Xcode per il debug). Il processo di notarizzazione non lo consentirà per le versioni di produzione.
Le app con il diritto com.apple.system-task-ports
possono ottenere la porta del task per qualsiasi processo, tranne il kernel. Nelle versioni precedenti era chiamato task_for_pid-allow
. Questo è concesso solo alle applicazioni Apple.
L'utente root può accedere alle porte dei task delle applicazioni non compilate con un runtime indurito (e non da Apple).
La porta del nome del task: Una versione non privilegiata della porta del task. Riferisce al task, ma non consente di controllarlo. L'unica cosa che sembra essere disponibile attraverso di essa è task_info()
.
I thread hanno anche porte associate, che sono visibili dal task che chiama task_threads
e dal processore con processor_set_threads
. Un diritto di INVIO alla porta del thread consente di utilizzare le funzioni del sottosistema thread_act
, come:
thread_terminate
thread_[get/set]_state
act_[get/set]_state
thread_[suspend/resume]
thread_info
...
Qualsiasi thread può ottenere questa porta chiamando mach_thread_sef
.
Puoi prendere un shellcode da:
Introduction to ARM64v8Compila il programma precedente e aggiungi le autorizzazioni per poter iniettare codice con lo stesso utente (altrimenti dovrai usare sudo).