macOS IPC - Inter Process Communication
Mach-Nachrichten über Ports
Grundlegende Informationen
Mach verwendet Tasks als die kleinste Einheit zum Teilen von Ressourcen, und jeder Task kann mehrere Threads enthalten. Diese Tasks und Threads sind 1:1 auf POSIX-Prozesse und Threads abgebildet.
Die Kommunikation zwischen Tasks erfolgt über die Mach Inter-Process Communication (IPC), wobei einseitige Kommunikationskanäle genutzt werden. Nachrichten werden zwischen Ports übertragen, die eine Art von Nachrichtenwarteschlangen darstellen, die vom Kernel verwaltet werden.
Ein Port ist das Grundelement der Mach-IPC. Es kann verwendet werden, um Nachrichten zu senden und zu empfangen.
Jeder Prozess hat eine IPC-Tabelle, in der es möglich ist, die Mach-Ports des Prozesses zu finden. Der Name eines Mach-Ports ist tatsächlich eine Nummer (ein Zeiger auf das Kernelobjekt).
Ein Prozess kann auch einen Portnamen mit bestimmten Rechten an einen anderen Task senden, und der Kernel wird diesen Eintrag in der IPC-Tabelle des anderen Tasks erscheinen lassen.
Portrechte
Portrechte, die definieren, welche Operationen ein Task ausführen kann, sind entscheidend für diese Kommunikation. Die möglichen Portrechte sind (Definitionen von hier):
Empfangsrecht, das das Empfangen von an den Port gesendeten Nachrichten ermöglicht. Mach-Ports sind MPSC (multiple-producer, single-consumer) Warteschlangen, was bedeutet, dass es im gesamten System möglicherweise nur ein Empfangsrecht für jeden Port gibt (im Gegensatz zu Pipes, bei denen mehrere Prozesse alle Dateideskriptoren zum Lesenende einer Pipe halten können).
Ein Task mit dem Empfangsrecht kann Nachrichten empfangen und Senderechte erstellen, die es ihm ermöglichen, Nachrichten zu senden. Ursprünglich hatte nur der eigene Task das Empfangsrecht über seinen Port.
Wenn der Besitzer des Empfangsrechts stirbt oder es beendet, wird das Senderecht nutzlos (toter Name).
Senderecht, das das Senden von Nachrichten an den Port ermöglicht.
Das Senderecht kann geklont werden, sodass ein Task, der ein Senderecht besitzt, das Recht klonen und es einem dritten Task gewähren kann.
Beachten Sie, dass Portrechte auch durch Mac-Nachrichten übergeben werden können.
Einmal-Senderecht, das das Senden einer Nachricht an den Port ermöglicht und dann verschwindet.
Dieses Recht kann nicht geklont werden, aber es kann verschoben werden.
Portset-Recht, das ein Portset anstelle eines einzelnen Ports kennzeichnet. Das Dequeuing einer Nachricht aus einem Portset dequeues eine Nachricht von einem der enthaltenen Ports. Portsets können verwendet werden, um gleichzeitig auf mehreren Ports zu lauschen, ähnlich wie
select
/poll
/epoll
/kqueue
in Unix.Toter Name, der kein tatsächliches Portrecht ist, sondern nur ein Platzhalter. Wenn ein Port zerstört wird, werden alle bestehenden Portrechte für den Port zu toten Namen.
Tasks können SEND-Rechte an andere übertragen, sodass sie Nachrichten zurückschicken können. SEND-Rechte können auch geklont werden, sodass ein Task das Recht duplizieren und einem dritten Task geben kann. Dies ermöglicht in Verbindung mit einem Zwischenprozess, der als Bootstrap-Server bekannt ist, eine effektive Kommunikation zwischen Tasks.
Datei-Ports
Datei-Ports ermöglichen es, Dateideskriptoren in Mac-Ports zu kapseln (unter Verwendung von Mach-Portrechten). Es ist möglich, einen fileport
aus einem gegebenen FD mit fileport_makeport
zu erstellen und einen FD aus einem fileport
mit fileport_makefd
zu erstellen.
Aufbau einer Kommunikation
Wie bereits erwähnt, ist es möglich, Rechte über Mach-Nachrichten zu senden, jedoch kann man kein Recht senden, ohne bereits ein Recht zum Senden einer Mach-Nachricht zu haben. Wie wird also die erste Kommunikation hergestellt?
Dafür ist der Bootstrap-Server (launchd auf dem Mac) beteiligt, da jeder ein SEND-Recht zum Bootstrap-Server erhalten kann, ist es möglich, ihn um ein Recht zu bitten, eine Nachricht an einen anderen Prozess zu senden:
Task A erstellt einen neuen Port und erhält das Empfangsrecht darüber.
Task A, als Inhaber des Empfangsrechts, erzeugt ein SEND-Recht für den Port.
Task A stellt eine Verbindung mit dem Bootstrap-Server her und sendet ihm das SEND-Recht für den Port, das es zu Beginn generiert hat.
Denken Sie daran, dass jeder ein SEND-Recht zum Bootstrap-Server erhalten kann.
Task A sendet eine
bootstrap_register
-Nachricht an den Bootstrap-Server, um den gegebenen Port mit einem Namen wiecom.apple.taska
zu verknüpfen.Task B interagiert mit dem Bootstrap-Server, um eine Bootstrap-Suche nach dem Dienstnamen (
bootstrap_lookup
) auszuführen. Damit der Bootstrap-Server antworten kann, sendet Task B ihm ein SEND-Recht zu einem zuvor erstellten Port innerhalb der Suchnachricht. Wenn die Suche erfolgreich ist, dupliziert der Server das SEND-Recht, das von Task A erhalten wurde, und überträgt es an Task B.
Denken Sie daran, dass jeder ein SEND-Recht zum Bootstrap-Server erhalten kann.
Mit diesem SEND-Recht ist Task B in der Lage, eine Nachricht an Task A zu senden.
Für eine bidirektionale Kommunikation erstellt Task B normalerweise einen neuen Port mit einem Empfangsrecht und einem SEND-Recht und gibt das SEND-Recht an Task A weiter, damit es Nachrichten an TASK B senden kann (bidirektionale Kommunikation).
Der Bootstrap-Server kann den vom Task beanspruchten Dienstnamen nicht authentifizieren. Dies bedeutet, dass ein Task potenziell jeden Systemtask impersonieren könnte, beispielsweise einen Autorisierungsdienstnamen falsch zu beanspruchen und dann jede Anfrage zu genehmigen.
Apple speichert die Namen der systembereitgestellten Dienste in sicheren Konfigurationsdateien, die sich in SIP-geschützten Verzeichnissen befinden: /System/Library/LaunchDaemons
und /System/Library/LaunchAgents
. Neben jedem Dienstnamen wird auch die zugehörige Binärdatei gespeichert. Der Bootstrap-Server erstellt und hält ein Empfangsrecht für jeden dieser Dienstenamen.
Für diese vordefinierten Dienste unterscheidet sich der Suchprozess leicht. Wenn ein Dienstname gesucht wird, startet launchd den Dienst dynamisch. Der neue Ablauf ist wie folgt:
Task B initiiert eine Bootstrap-Suche nach einem Dienstnamen.
launchd überprüft, ob der Task ausgeführt wird, und wenn nicht, startet er ihn.
Task A (der Dienst) führt ein Bootstrap-Check-in (
bootstrap_check_in()
) durch. Hier erstellt der Bootstrap-Server ein SEND-Recht, behält es und überträgt das Empfangsrecht an Task A.launchd dupliziert das SEND-Recht und sendet es an Task B.
Task B erstellt einen neuen Port mit einem Empfangsrecht und einem SEND-Recht und gibt das SEND-Recht an Task A (den Dienst) weiter, damit er Nachrichten an TASK B senden kann (bidirektionale Kommunikation).
Dieser Prozess gilt jedoch nur für vordefinierte Systemaufgaben. Nichtsystemaufgaben funktionieren weiterhin wie ursprünglich beschrieben, was potenziell eine Impersonation ermöglichen könnte.
Daher sollte launchd niemals abstürzen, da sonst das gesamte System abstürzt.
Eine Mach-Nachricht
Hier finden Sie weitere Informationen
Die mach_msg
-Funktion, im Wesentlichen ein Systemaufruf, wird zum Senden und Empfangen von Mach-Nachrichten verwendet. Die Funktion erfordert, dass die Nachricht als erstes Argument gesendet wird. Diese Nachricht muss mit einer mach_msg_header_t
-Struktur beginnen, gefolgt vom eigentlichen Nachrichteninhalt. Die Struktur ist wie folgt definiert:
Prozesse, die ein Empfangsrecht besitzen, können Nachrichten über einen Mach-Port empfangen. Umgekehrt erhalten die Sender ein Senderecht oder ein Einmal-Senderecht. Das Einmal-Senderecht dient ausschließlich zum Senden einer einzelnen Nachricht, nach der es ungültig wird.
Das anfängliche Feld msgh_bits
ist eine Bitmap:
Das erste Bit (am signifikantesten) wird verwendet, um anzuzeigen, dass eine Nachricht komplex ist (mehr dazu unten).
Das 3. und 4. Bit werden vom Kernel verwendet.
Die 5 am wenigsten signifikanten Bits des 2. Bytes können für Gutschein verwendet werden: ein weiterer Typ von Port zum Senden von Schlüssel/Wert-Kombinationen.
Die 5 am wenigsten signifikanten Bits des 3. Bytes können für lokalen Port verwendet werden.
Die 5 am wenigsten signifikanten Bits des 4. Bytes können für entfernten Port verwendet werden.
Die Typen, die im Gutschein, lokalen und entfernten Ports angegeben werden können, sind (aus mach/message.h):
Zum Beispiel kann MACH_MSG_TYPE_MAKE_SEND_ONCE
verwendet werden, um anzuzeigen, dass ein Send-once-Recht abgeleitet und für diesen Port übertragen werden soll. Es kann auch MACH_PORT_NULL
angegeben werden, um zu verhindern, dass der Empfänger antworten kann.
Um eine einfache bidirektionale Kommunikation zu erreichen, kann ein Prozess einen Mach-Port im Mach-Nachrichtenkopf namens Antwort-Port (msgh_local_port
) angeben, wohin der Empfänger der Nachricht eine Antwort auf diese Nachricht senden kann.
Beachten Sie, dass diese Art der bidirektionalen Kommunikation in XPC-Nachrichten verwendet wird, die eine Antwort erwarten (xpc_connection_send_message_with_reply
und xpc_connection_send_message_with_reply_sync
). Aber normalerweise werden verschiedene Ports erstellt, wie zuvor erläutert, um die bidirektionale Kommunikation herzustellen.
Die anderen Felder des Nachrichtenkopfs sind:
msgh_size
: die Größe des gesamten Pakets.msgh_remote_port
: der Port, auf dem diese Nachricht gesendet wird.msgh_voucher_port
: Mach-Gutscheine.msgh_id
: die ID dieser Nachricht, die vom Empfänger interpretiert wird.
Beachten Sie, dass Mach-Nachrichten über einen Mach-Port
gesendet werden, der ein Kommunikationskanal mit einem einzelnen Empfänger und mehreren Sendern ist, der in den Mach-Kernel integriert ist. Mehrere Prozesse können Nachrichten an einen Mach-Port senden, aber zu jedem Zeitpunkt kann nur ein einziger Prozess daraus lesen.
Nachrichten werden dann durch den mach_msg_header_t
-Kopf gefolgt vom Körper und vom Trailer (falls vorhanden) gebildet und können die Berechtigung erteilen, darauf zu antworten. In diesen Fällen muss der Kernel die Nachricht einfach von einer Aufgabe an die andere weiterleiten.
Ein Trailer ist eine vom Kernel zur Nachricht hinzugefügte Information (kann nicht vom Benutzer festgelegt werden), die bei der Nachrichtenempfang mit den Flags MACH_RCV_TRAILER_<trailer_opt>
angefordert werden kann (es gibt verschiedene Informationen, die angefordert werden können).
Komplexe Nachrichten
Es gibt jedoch auch andere komplexere Nachrichten, wie diejenigen, die zusätzliche Portrechte übergeben oder Speicher teilen, bei denen der Kernel auch diese Objekte an den Empfänger senden muss. In diesen Fällen wird das höchstwertige Bit des Kopfs msgh_bits
gesetzt.
Die möglichen Deskriptoren, die übergeben werden können, sind in mach/message.h
definiert:
In 32-Bit sind alle Deskriptoren 12B groß und der Deskriptortyp befindet sich im 11. Deskriptor. In 64-Bit variieren die Größen.
Der Kernel kopiert die Deskriptoren von einer Aufgabe zur anderen, erstellt jedoch zuerst eine Kopie im Kernel-Speicher. Diese Technik, bekannt als "Feng Shui", wurde in mehreren Exploits missbraucht, um den Kernel dazu zu bringen, Daten in seinem Speicher zu kopieren, sodass ein Prozess Deskriptoren an sich selbst senden kann. Dann kann der Prozess die Nachrichten empfangen (der Kernel wird sie freigeben).
Es ist auch möglich, Portrechte an einen anfälligen Prozess zu senden, und die Portrechte werden einfach im Prozess erscheinen (auch wenn er sie nicht verarbeitet).
Mac Ports APIs
Beachten Sie, dass Ports dem Aufgaben-Namespace zugeordnet sind. Um einen Port zu erstellen oder nach einem Port zu suchen, wird auch der Aufgaben-Namespace abgefragt (mehr in mach/mach_port.h
):
mach_port_allocate
|mach_port_construct
: Erstellt einen Port.mach_port_allocate
kann auch ein Port-Set erstellen: Empfangsrecht über eine Gruppe von Ports. Immer wenn eine Nachricht empfangen wird, wird der Port angezeigt, von dem sie stammt.mach_port_allocate_name
: Ändert den Namen des Ports (standardmäßig 32-Bit-Integer)mach_port_names
: Portnamen von einem Ziel abrufenmach_port_type
: Rechte einer Aufgabe über einen Namen abrufenmach_port_rename
: Benennt einen Port um (wie dup2 für FDs)mach_port_allocate
: Einen neuen EMPFANGENEN, PORT_SET oder DEAD_NAME zuweisenmach_port_insert_right
: Erstellt ein neues Recht in einem Port, in dem Sie EMPFANGEN habenmach_port_...
mach_msg
|mach_msg_overwrite
: Funktionen zum Senden und Empfangen von Mach-Nachrichten. Die Überschreibversion ermöglicht es, einen anderen Puffer für den Nachrichtenempfang anzugeben (die andere Version wird ihn einfach wiederverwenden).
Debug mach_msg
Da die Funktionen mach_msg
und mach_msg_overwrite
diejenigen sind, die zum Senden und Empfangen von Nachrichten verwendet werden, würde das Setzen eines Haltepunkts auf sie ermöglichen, die gesendeten und empfangenen Nachrichten zu inspizieren.
Starten Sie beispielsweise das Debuggen einer beliebigen Anwendung, die Sie debuggen können, da sie libSystem.B
laden wird, die diese Funktion verwendet.
Um die Argumente von mach_msg
zu erhalten, überprüfen Sie die Register. Dies sind die Argumente (aus mach/message.h):
Hole die Werte aus den Registern:
Überprüfen Sie den Nachrichtenkopf und prüfen Sie das erste Argument:
Diese Art von mach_msg_bits_t
ist sehr verbreitet, um eine Antwort zu ermöglichen.
Ports auflisten
Der Name ist der Standardname, der dem Port zugewiesen wird (überprüfen Sie, wie er in den ersten 3 Bytes ansteigt). Das ipc-object
ist der verschleierte eindeutige Bezeichner des Ports.
Beachten Sie auch, wie die Ports mit nur send
Rechten den Besitzer davon identifizieren (Portname + PID).
Beachten Sie auch die Verwendung von +
zur Kennzeichnung anderer Tasks, die mit demselben Port verbunden sind.
Es ist auch möglich, procesxp zu verwenden, um auch die registrierten Dienstnamen anzuzeigen (bei deaktiviertem SIP aufgrund der Notwendigkeit von com.apple.system-task-port
):
Du kannst dieses Tool in iOS installieren, indem du es von http://newosxbook.com/tools/binpack64-256.tar.gz herunterlädst.
Codebeispiel
Beachte, wie der Sender einen Port zuweist, ein Senderecht für den Namen org.darlinghq.example
erstellt und es an den Bootstrap-Server sendet, während der Sender nach dem Senderecht dieses Namens fragte und es verwendete, um eine Nachricht zu senden.
Privilegierte Ports
Es gibt einige spezielle Ports, die es ermöglichen, bestimmte sensible Aktionen auszuführen oder auf bestimmte sensible Daten zuzugreifen, falls Aufgaben über die SEND-Berechtigungen für sie verfügen. Dies macht diese Ports aus der Perspektive eines Angreifers sehr interessant, nicht nur wegen der Fähigkeiten, sondern auch, weil es möglich ist, SEND-Berechtigungen über Aufgaben hinweg zu teilen.
Host-Spezialports
Diese Ports werden durch eine Nummer repräsentiert.
SEND-Rechte können durch den Aufruf von host_get_special_port
und RECEIVE-Rechte durch den Aufruf von host_set_special_port
erlangt werden. Beide Aufrufe erfordern jedoch den host_priv
-Port, auf den nur der Root zugreifen kann. Darüber hinaus konnte der Root in der Vergangenheit host_set_special_port
aufrufen und willkürlich übernehmen, was es beispielsweise ermöglichte, Code-Signaturen zu umgehen, indem HOST_KEXTD_PORT
übernommen wurde (SIP verhindert dies jetzt).
Diese sind in 2 Gruppen unterteilt: Die ersten 7 Ports gehören dem Kernel, wobei die 1 HOST_PORT
, die 2 HOST_PRIV_PORT
, die 3 HOST_IO_MASTER_PORT
und die 7 HOST_MAX_SPECIAL_KERNEL_PORT
sind.
Diejenigen, die mit der Nummer 8 beginnen, sind Systemdaemons zugeordnet und können in host_special_ports.h
deklariert gefunden werden.
Host-Port: Wenn ein Prozess SEND-Privilegien über diesen Port hat, kann er Informationen über das System abrufen, indem er seine Routinen aufruft, wie z.B.:
host_processor_info
: Prozessorinformationen abrufenhost_info
: Hostinformationen abrufenhost_virtual_physical_table_info
: Virtuelle/Physische Seitentabelle (erfordert MACH_VMDEBUG)host_statistics
: Hoststatistiken abrufenmach_memory_info
: Kernel-Speicherlayout abrufenHost-Priv-Port: Ein Prozess mit SEND-Recht über diesen Port kann privilegierte Aktionen ausführen, wie das Anzeigen von Bootdaten oder das Versuchen, eine Kernelerweiterung zu laden. Der Prozess muss Root sein, um diese Berechtigung zu erhalten.
Darüber hinaus sind für den Aufruf der
kext_request
-API weitere Berechtigungen erforderlich, nämlichcom.apple.private.kext*
, die nur Apple-Binärdateien erhalten.Weitere Routinen, die aufgerufen werden können, sind:
host_get_boot_info
:machine_boot_info()
abrufenhost_priv_statistics
: Privilegierte Statistiken abrufenvm_allocate_cpm
: Kontinuierlichen physischen Speicher zuweisenhost_processors
: Senderecht an Hostprozessorenmach_vm_wire
: Speicher resident machenDa Root auf diese Berechtigung zugreifen kann, könnte er
host_set_[special/exception]_port[s]
aufrufen, um Host-Spezial- oder Ausnahmeports zu übernehmen.
Es ist möglich, alle Host-Spezialports anzuzeigen, indem man ausführt:
Aufgaben Ports
Ursprünglich hatte Mach keine "Prozesse", sondern "Tasks", die eher als Container von Threads betrachtet wurden. Als Mach mit BSD fusioniert wurde, wurde jeder Task mit einem BSD-Prozess korreliert. Daher hat jeder BSD-Prozess die erforderlichen Details, um ein Prozess zu sein, und jeder Mach-Task hat ebenfalls seine internen Abläufe (mit Ausnahme der nicht vorhandenen PID 0, die der kernel_task
ist).
Es gibt zwei sehr interessante Funktionen in Bezug darauf:
task_for_pid(target_task_port, pid, &task_port_of_pid)
: Erhalten Sie ein SEND-Recht für den Task-Port des Tasks, der mit der angegebenenpid
verbunden ist, und übergeben Sie es an den angegebenentarget_task_port
(der normalerweise der Aufrufer-Task ist, dermach_task_self()
verwendet hat, aber auch ein SEND-Port über einen anderen Task sein könnte).pid_for_task(task, &pid)
: Bei Vorliegen eines SEND-Rechts für einen Task finden Sie heraus, mit welcher PID dieser Task verbunden ist.
Um Aktionen innerhalb des Tasks auszuführen, benötigte der Task ein SEND
-Recht für sich selbst, indem er mach_task_self()
aufrief (was den task_self_trap
(28) verwendet). Mit dieser Berechtigung kann ein Task verschiedene Aktionen ausführen, wie z.B.:
task_threads
: SEND-Recht über alle Task-Ports der Threads des Tasks erhaltentask_info
: Informationen über einen Task erhaltentask_suspend/resume
: Einen Task anhalten oder fortsetzentask_[get/set]_special_port
thread_create
: Einen Thread erstellentask_[get/set]_state
: Task-Status steuernund mehr in mach/task.h zu finden sind
Beachten Sie, dass mit einem SEND-Recht über einen Task-Port eines anderen Tasks Aktionen über einen anderen Task ausgeführt werden können.
Darüber hinaus ist der Task-Port auch der vm_map
-Port, der es ermöglicht, den Speicher innerhalb eines Tasks mit Funktionen wie vm_read()
und vm_write()
zu lesen und zu manipulieren. Dies bedeutet im Grunde genommen, dass ein Task mit SEND-Rechten über den Task-Port eines anderen Tasks in der Lage sein wird, Code in diesen Task einzuspeisen.
Denken Sie daran, dass, weil der Kernel auch ein Task ist, wenn es jemandem gelingt, SEND-Berechtigungen über den kernel_task
zu erhalten, er den Kernel dazu bringen kann, beliebigen Code auszuführen (Jailbreaks).
Rufen Sie
mach_task_self()
auf, um den Namen für diesen Port für den Aufrufer-Task zu erhalten. Dieser Port wird nur beimexec()
vererbt; ein neuer Task, der mitfork()
erstellt wurde, erhält einen neuen Task-Port (als Sonderfall erhält ein Task auch nachexec()
in einem suid-Binary einen neuen Task-Port). Der einzige Weg, einen Task zu erstellen und seinen Port zu erhalten, besteht darin, den "Port-Tausch-Tanz" während einesfork()
durchzuführen.Dies sind die Einschränkungen für den Zugriff auf den Port (aus
macos_task_policy
aus dem BinärAppleMobileFileIntegrity
):Wenn die App die
com.apple.security.get-task-allow
-Berechtigung hat, können Prozesse desselben Benutzers auf den Task-Port zugreifen (üblicherweise von Xcode für Debugging hinzugefügt). Der Notarisierungsprozess wird dies nicht für Produktversionen zulassen.Apps mit der
com.apple.system-task-ports
-Berechtigung können den Task-Port für jeden Prozess außer dem Kernel erhalten. In älteren Versionen wurde diestask_for_pid-allow
genannt. Dies wird nur Apple-Anwendungen gewährt.Root kann auf Task-Ports von Anwendungen zugreifen, die nicht mit einer gehärteten Laufzeitumgebung kompiliert wurden (und nicht von Apple stammen).
Der Task-Name-Port: Eine unprivilegierte Version des Task-Ports. Er verweist auf den Task, erlaubt jedoch nicht dessen Steuerung. Das Einzige, was darüber verfügbar zu sein scheint, ist task_info()
.
Shellcode-Injektion in Thread über Task-Port
Sie können ein Shellcode von hier abrufen:
pageIntroduction to ARM64v8Kompilieren Sie das vorherige Programm und fügen Sie die Berechtigungen hinzu, um in der Lage zu sein, Code mit demselben Benutzer einzuspritzen (ansonsten müssen Sie sudo verwenden).
Last updated