macOS IPC - Inter Process Communication
Messaggistica Mach tramite Porte
Informazioni di Base
Mach utilizza task come unità più piccola per la condivisione di risorse, e ogni task può contenere più thread. Questi task e thread sono mappati 1:1 ai processi e ai thread POSIX.
La comunicazione tra i task avviene tramite la Comunicazione tra Processi Mach (IPC), utilizzando canali di comunicazione unidirezionali. I messaggi vengono trasferiti tra le porte, che agiscono come una sorta di code di messaggi gestite dal kernel.
Una porta è l'elemento base dell'IPC di Mach. Può essere utilizzata per inviare messaggi e riceverli.
Ogni processo ha una tabella IPC, dove è possibile trovare le porte mach del processo. Il nome di una porta mach è in realtà un numero (un puntatore all'oggetto del kernel).
Un processo può anche inviare un nome di porta con alcuni diritti a un task diverso e il kernel renderà visibile questa voce nella tabella IPC dell'altro task.
Diritti delle Porte
I diritti delle porte, che definiscono le operazioni che un task può eseguire, sono fondamentali per questa comunicazione. I possibili diritti delle porte sono (definizioni da qui):
Diritto di ricezione, che consente di ricevere messaggi inviati alla porta. Le porte Mach sono code MPSC (multiple-producer, single-consumer), il che significa che può esserci solo un diritto di ricezione per ogni porta in tutto il sistema (a differenza delle pipe, dove più processi possono tutti detenere descrittori di file per l'estremità di lettura di una pipe).
Un task con il diritto di ricezione può ricevere messaggi e creare diritti di invio, consentendogli di inviare messaggi. Originariamente solo il proprio task ha il diritto di ricezione sulla sua porta.
Se il proprietario del diritto di ricezione muore o lo termina, 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 task che possiede un diritto di invio possa clonare il diritto e concederlo a un terzo task.
Nota che i diritti delle porte possono anche essere passati attraverso i messaggi Mac.
Diritto di invio una volta sola, 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 indica un insieme di porte anziché una singola porta. Estrarre 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 contemporaneamente, molto simile a
select
/poll
/epoll
/kqueue
in Unix.Nome morto, che non è un vero e proprio diritto di porta, ma solo un segnaposto. Quando una porta viene distrutta, tutti i diritti di porta esistenti per la porta diventano nomi morti.
I task possono trasferire DIRITTI DI INVIO ad altri, consentendo loro di inviare messaggi indietro. I DIRITTI DI INVIO possono anche essere clonati, quindi un task può duplicare e dare il diritto a un terzo task. Questo, combinato con un processo intermedio noto come il bootstrap server, consente una comunicazione efficace tra i task.
Porte File
Le porte file consentono di incapsulare i descrittori di file in porte Mac (utilizzando i diritti delle porte Mach). È possibile creare un fileport
da un determinato FD utilizzando fileport_makeport
e creare un FD da un fileport utilizzando fileport_makefd
.
Stabilire una comunicazione
Come già accennato, è possibile inviare diritti utilizzando messaggi Mach, tuttavia, non è possibile inviare un diritto senza già avere un diritto per inviare un messaggio Mach. Quindi, come viene stabilita la prima comunicazione?
Per questo, è coinvolto il bootstrap server (launchd in mac), poiché chiunque può ottenere un DIRITTO DI INVIO al bootstrap server, è possibile chiedergli un diritto per inviare un messaggio a un altro processo:
Il Task A crea una nuova porta, ottenendo il DIRITTO DI RICEZIONE su di essa.
Il Task A, essendo il detentore del DIRITTO DI RICEZIONE, genera un DIRITTO DI INVIO per la porta.
Il Task A stabilisce una connessione con il bootstrap server, e gli invia il DIRITTO DI INVIO per la porta generato all'inizio.
Ricorda che chiunque può ottenere un DIRITTO DI INVIO al bootstrap server.
Il Task A invia un messaggio
bootstrap_register
al bootstrap server per associare la porta data a un nome comecom.apple.taska
Il Task B interagisce con il bootstrap server per eseguire una ricerca bootstrap per il nome del servizio (
bootstrap_lookup
). Quindi, affinché il bootstrap server possa rispondere, il task B invierà un DIRITTO DI INVIO a una porta che ha creato precedentemente all'interno del messaggio di ricerca. Se la ricerca ha successo, il server duplica il DIRITTO DI INVIO ricevuto dal Task A e lo trasmette al Task B.
Ricorda che chiunque può ottenere un DIRITTO DI INVIO al bootstrap server.
Con questo DIRITTO DI INVIO, il Task B è in grado di inviare un messaggio al Task A.
Per una comunicazione bidirezionale di solito il task B genera una nuova porta con un DIRITTO DI RICEZIONE e un DIRITTO DI INVIO, e dà il DIRITTO DI INVIO al Task A in modo che possa inviare messaggi a TASK B (comunicazione bidirezionale).
Il bootstrap server non può autenticare il nome del servizio reclamato da un task. Ciò significa che un task potrebbe potenzialmente fingere di essere qualsiasi task di sistema, come ad esempio falsamente reclamare un nome di servizio di autorizzazione e quindi approvare ogni richiesta.
Successivamente, 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 ricerca differisce leggermente. Quando viene cercato un nome di servizio, launchd avvia il servizio dinamicamente. Il nuovo flusso di lavoro è il seguente:
Il Task B avvia una ricerca bootstrap per un nome di servizio.
launchd controlla se il task è in esecuzione e se non lo è, lo avvia.
Il Task A (il servizio) esegue un check-in bootstrap (
bootstrap_check_in()
). Qui, il bootstrap server crea un DIRITTO DI INVIO, lo mantiene e trasferisce il DIRITTO DI RICEZIONE al Task A.launchd duplica il DIRITTO DI INVIO e lo invia al Task B.
Il Task B genera una nuova porta con un DIRITTO DI RICEZIONE e un DIRITTO DI INVIO, e dà il DIRITTO DI INVIO al Task A (il servizio) in modo che possa inviare messaggi a TASK B (comunicazione bidirezionale).
Tuttavia, questo processo si applica solo ai task di sistema predefiniti. I task non di sistema continuano a operare come descritto originariamente, il che potrebbe potenzialmente consentire l'usurpazione.
Pertanto, launchd non dovrebbe mai bloccarsi o l'intero sistema si bloccherà.
Un messaggio Mach
La funzione mach_msg
, essenzialmente una chiamata di sistema, viene utilizzata per inviare e ricevere messaggi Mach. La funzione richiede che il messaggio venga inviato come argomento iniziale. Questo messaggio deve iniziare con una struttura mach_msg_header_t
, seguita dal contenuto effettivo del messaggio. La struttura è definita come segue:
I processi che possiedono un diritto di ricezione possono ricevere messaggi su una porta Mach. Al contrario, i mittenti ottengono un diritto di invio o un diritto di invio una volta sola. Il diritto di invio una volta sola è esclusivamente per l'invio di un singolo messaggio, dopo il quale diventa non valido.
Il campo iniziale msgh_bits
è una mappa di bit:
Il primo bit (più significativo) è utilizzato per indicare che un messaggio è complesso (più dettagli in seguito)
Il 3° e il 4° sono utilizzati dal kernel
I 5 bit meno significativi del 2° byte possono essere utilizzati per il voucher: un altro tipo di porta per inviare combinazioni chiave/valore.
I 5 bit meno significativi del 3° byte possono essere utilizzati per la porta locale
I 5 bit meno significativi del 4° byte possono essere utilizzati per la 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 deve essere derivato e trasferito per questa porta. È anche possibile specificare MACH_PORT_NULL
per impedire al destinatario di poter rispondere.
Per ottenere una comunicazione bidirezionale semplice, un processo può specificare una porta mach nell'intestazione del messaggio mach chiamata porta di risposta (msgh_local_port
) dove il ricevente del messaggio può inviare una risposta a questo messaggio.
Si noti 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
: la porta su cui viene inviato questo messaggio.msgh_voucher_port
: voucher mach.msgh_id
: l'ID di questo messaggio, interpretato dal ricevente.
Si noti che i messaggi mach vengono inviati su una porta mach
, che è un canale di comunicazione singolo destinatario, multiplo mittente integrato nel kernel mach. Più processi possono inviare messaggi a una porta mach, ma in qualsiasi momento solo un singolo processo può leggere da essa.
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 ad esso. In questi casi, il kernel deve solo passare il messaggio da un task all'altro.
Un trailer è un'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).
Messaggi Complessi
Tuttavia, ci sono altri messaggi più complessi, come quelli che passano diritti di porta aggiuntivi o condividono memoria, in cui il kernel deve anche inviare questi oggetti al destinatario. In questi casi, il bit più significativo dell'intestazione msgh_bits
è impostato.
I descrittori possibili da passare sono definiti in mach/message.h
:
In 32 bit, tutti i descrittori sono di 12B e il tipo di descrittore è nel 11°. In 64 bit, le dimensioni variano.
Il kernel copierà i descrittori da un task all'altro ma prima creerà una copia nella memoria del kernel. Questa tecnica, nota come "Feng Shui", è stata abusata in diversi exploit per fare in modo che il kernel copi i dati nella sua memoria facendo sì che un processo invii descrittori a se stesso. Quindi 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).
API delle porte Mac
Nota che le porte sono associate allo spazio dei nomi del task, quindi per creare o cercare una porta, viene anche interrogato lo spazio dei nomi del task (più in mach/mach_port.h
):
mach_port_allocate
|mach_port_construct
: Crea una porta.mach_port_allocate
può anche creare un insieme di porte: diritto di ricezione su un gruppo di porte. Ogni volta che viene ricevuto un messaggio, viene indicata la porta da cui è stato inviato.mach_port_allocate_name
: Cambia il nome della porta (di default un intero a 32 bit)mach_port_names
: Ottieni i nomi delle porte da un targetmach_port_type
: Ottieni i diritti di un task su un nomemach_port_rename
: Rinomina una porta (come dup2 per FD)mach_port_allocate
: Alloca un nuovo RICEVI, PORT_SET o DEAD_NAMEmach_port_insert_right
: Crea un nuovo diritto in una porta dove hai RICEVImach_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 lo riutilizzerà).
Debug di mach_msg
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 eseguire il debug di qualsiasi applicazione che puoi eseguire il debug 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):
Ottenere 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.
Enumerare le porte
Il nome è il nome predefinito assegnato alla porta (controlla come sta aumentando nei primi 3 byte). L'ipc-object
è l'identificatore univoco oscurato della porta.
Nota anche come le porte con solo il diritto di send
stanno identificando il proprietario di essa (nome della porta + pid).
Nota anche l'uso di +
per indicare altri task connessi 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
Esempio di codice
Nota come il mittente alloca una porta, crea un diritto di invio per il nome org.darlinghq.example
e lo invia al server di avvio mentre il mittente ha richiesto il diritto di invio di quel nome e lo ha usato per inviare un messaggio.
Porte privilegiate
Porta host: Se un processo ha il privilegio di Invio su questa porta può ottenere informazioni sul sistema (ad esempio
host_processor_info
).Porta host priv: Un processo con il diritto di Invio su questa porta può eseguire azioni privilegiate come caricare un'estensione del kernel. Il processo deve essere root per ottenere questo permesso.
Inoltre, per chiamare l'API
kext_request
è necessario avere altri diritticom.apple.private.kext*
che vengono concessi solo ai binari Apple.Porta nome attività: Una versione non privilegiata della porta attività. Fa riferimento all'attività, ma non consente di controllarla. L'unica cosa che sembra essere disponibile tramite di essa è
task_info()
.Porta attività (alias porta kernel): Con il permesso di Invio su questa porta è possibile controllare l'attività (leggere/scrivere memoria, creare thread...).
Chiamare
mach_task_self()
per ottenere il nome di questa porta per l'attività chiamante. Questa porta viene ereditata solo attraversoexec()
; una nuova attività creata confork()
ottiene una nuova porta attività (come caso speciale, un'attività ottiene anche una nuova porta attività dopoexec()
in un binario suid). L'unico modo per generare un'attività e ottenere la sua porta è eseguire la "danza dello scambio di porte" durante unfork()
.Queste sono le restrizioni per accedere alla porta (da
macos_task_policy
dal binarioAppleMobileFileIntegrity
):Se l'app ha il diritto
com.apple.security.get-task-allow
i processi dello stesso utente possono accedere alla porta attività (comunemente aggiunto da Xcode per il debug). Il processo di notarizzazione non lo permetterà per i rilasci in produzione.Le app con il diritto
com.apple.system-task-ports
possono ottenere la porta attività per qualsiasi processo, tranne il kernel. Nelle versioni precedenti era chiamatotask_for_pid-allow
. Questo è concesso solo alle applicazioni Apple.Root può accedere alle porte attività delle applicazioni non compilati con un ambiente di esecuzione protetto (e non di Apple).
Iniezione di shellcode nel thread tramite porta attività
È possibile ottenere un shellcode da:
pageIntroduction to ARM64v8Traduzione
Compila il programma precedente e aggiungi i privilegi per poter iniettare codice con lo stesso utente (altrimenti dovrai usare sudo).
Last updated