macOS IPC - Inter Process Communication

Unterstützen Sie HackTricks

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 verfügt über eine IPC-Tabelle, in der die Mach-Ports des Prozesses zu finden sind. 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 im IPC-Table 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 nur ein Empfangsrecht für jeden Port im gesamten System geben kann (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 und dann das Verschwinden ermöglicht.

  • 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 aus 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 zu haben, um eine Mach-Nachricht zu senden. Wie wird also die erste Kommunikation hergestellt?

Dafür ist der Bootstrap-Server (launchd in Mac) beteiligt, da jeder ein SEND-Recht zum Bootstrap-Server erhalten kann, ist es möglich, ihn um ein Recht zu bitten, um eine Nachricht an einen anderen Prozess zu senden:

  1. Task A erstellt einen neuen Port, erhält das Empfangsrecht darüber.

  2. Task A, als Inhaber des Empfangsrechts, erzeugt ein SEND-Recht für den Port.

  3. 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.

  1. Task A sendet eine bootstrap_register-Nachricht an den Bootstrap-Server, um den gegebenen Port mit einem Namen wie com.apple.taska zu verknüpfen.

  2. 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.

  1. Mit diesem SEND-Recht ist Task B in der Lage, eine Nachricht an Task A zu senden.

  2. 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 imitieren könnte, indem er fälschlicherweise einen Autorisierungsdienstnamen beansprucht und dann jede Anfrage genehmigt.

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 Imitation 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:

typedef struct {
mach_msg_bits_t               msgh_bits;
mach_msg_size_t               msgh_size;
mach_port_t                   msgh_remote_port;
mach_port_t                   msgh_local_port;
mach_port_name_t              msgh_voucher_port;
mach_msg_id_t                 msgh_id;
} mach_msg_header_t;

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):

#define MACH_MSG_TYPE_MOVE_RECEIVE      16      /* Must hold receive right */
#define MACH_MSG_TYPE_MOVE_SEND         17      /* Must hold send right(s) */
#define MACH_MSG_TYPE_MOVE_SEND_ONCE    18      /* Must hold sendonce right */
#define MACH_MSG_TYPE_COPY_SEND         19      /* Must hold send right(s) */
#define MACH_MSG_TYPE_MAKE_SEND         20      /* Must hold receive right */
#define MACH_MSG_TYPE_MAKE_SEND_ONCE    21      /* Must hold receive right */
#define MACH_MSG_TYPE_COPY_RECEIVE      22      /* NOT VALID */
#define MACH_MSG_TYPE_DISPOSE_RECEIVE   24      /* must hold receive right */
#define MACH_MSG_TYPE_DISPOSE_SEND      25      /* must hold send right(s) */
#define MACH_MSG_TYPE_DISPOSE_SEND_ONCE 26      /* must hold sendonce right */

Zum Beispiel kann MACH_MSG_TYPE_MAKE_SEND_ONCE verwendet werden, um anzuzeigen, dass ein Send-once-Recht für diesen Port abgeleitet und ü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, an den 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 erklärt, um die bidirektionale Kommunikation zu ermöglichen.

Die anderen Felder des Nachrichtenkopfs sind:

  • msgh_size: die Größe des gesamten Pakets.

  • msgh_remote_port: der Port, über den 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 für einen einzelnen Empfänger und mehrere Sender 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 einzelner 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 nur 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 zum Übergeben sind in mach/message.h definiert:

#define MACH_MSG_PORT_DESCRIPTOR                0
#define MACH_MSG_OOL_DESCRIPTOR                 1
#define MACH_MSG_OOL_PORTS_DESCRIPTOR           2
#define MACH_MSG_OOL_VOLATILE_DESCRIPTOR        3
#define MACH_MSG_GUARDED_PORT_DESCRIPTOR        4

#pragma pack(push, 4)

typedef struct{
natural_t                     pad1;
mach_msg_size_t               pad2;
unsigned int                  pad3 : 24;
mach_msg_descriptor_type_t    type : 8;
} mach_msg_type_descriptor_t;

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-Ganzzahl)

  • mach_port_names: Portnamen von einem Ziel abrufen

  • mach_port_type: Rechte einer Aufgabe über einen Namen abrufen

  • mach_port_rename: Benennt einen Port um (wie dup2 für FDs)

  • mach_port_allocate: Einen neuen EMPFANGENEN, PORT_SET oder DEAD_NAME zuweisen

  • mach_port_insert_right: Erstellt ein neues Recht in einem Port, in dem Sie EMPFANGEN haben

  • mach_port_...

  • mach_msg | mach_msg_overwrite: Funktionen zum Senden und Empfangen von Mach-Nachrichten. Die Überschreibversion ermöglicht die Angabe eines anderen Puffers für den Nachrichtenempfang (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 verwenden wird.

__WATCHOS_PROHIBITED __TVOS_PROHIBITED
extern mach_msg_return_t        mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);

Hole die Werte aus den Registern:

reg read $x0 $x1 $x2 $x3 $x4 $x5 $x6
x0 = 0x0000000124e04ce8 ;mach_msg_header_t (*msg)
x1 = 0x0000000003114207 ;mach_msg_option_t (option)
x2 = 0x0000000000000388 ;mach_msg_size_t (send_size)
x3 = 0x0000000000000388 ;mach_msg_size_t (rcv_size)
x4 = 0x0000000000001f03 ;mach_port_name_t (rcv_name)
x5 = 0x0000000000000000 ;mach_msg_timeout_t (timeout)
x6 = 0x0000000000000000 ;mach_port_name_t (notify)

Überprüfen Sie den Nachrichtenkopf und prüfen Sie das erste Argument:

(lldb) x/6w $x0
0x124e04ce8: 0x00131513 0x00000388 0x00000807 0x00001f03
0x124e04cf8: 0x00000b07 0x40000322

; 0x00131513 -> mach_msg_bits_t (msgh_bits) = 0x13 (MACH_MSG_TYPE_COPY_SEND) in local | 0x1500 (MACH_MSG_TYPE_MAKE_SEND_ONCE) in remote | 0x130000 (MACH_MSG_TYPE_COPY_SEND) in voucher
; 0x00000388 -> mach_msg_size_t (msgh_size)
; 0x00000807 -> mach_port_t (msgh_remote_port)
; 0x00001f03 -> mach_port_t (msgh_local_port)
; 0x00000b07 -> mach_port_name_t (msgh_voucher_port)
; 0x40000322 -> mach_msg_id_t (msgh_id)

Diese Art von mach_msg_bits_t ist sehr verbreitet, um eine Antwort zu ermöglichen.

Ports auflisten

lsmp -p <pid>

sudo lsmp -p 1
Process (1) : launchd
name      ipc-object    rights     flags   boost  reqs  recv  send sonce oref  qlimit  msgcount  context            identifier  type
---------   ----------  ----------  -------- -----  ---- ----- ----- ----- ----  ------  --------  ------------------ ----------- ------------
0x00000203  0x181c4e1d  send        --------        ---            2                                                  0x00000000  TASK-CONTROL SELF (1) launchd
0x00000303  0x183f1f8d  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x00000403  0x183eb9dd  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x0000051b  0x1840cf3d  send        --------        ---            2        ->        6         0  0x0000000000000000 0x00011817  (380) WindowServer
0x00000603  0x183f698d  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x0000070b  0x175915fd  recv,send   ---GS---     0  ---      1     2         Y        5         0  0x0000000000000000
0x00000803  0x1758794d  send        --------        ---            1                                                  0x00000000  CLOCK
0x0000091b  0x192c71fd  send        --------        D--            1        ->        1         0  0x0000000000000000 0x00028da7  (418) runningboardd
0x00000a6b  0x1d4a18cd  send        --------        ---            2        ->       16         0  0x0000000000000000 0x00006a03  (92247) Dock
0x00000b03  0x175a5d4d  send        --------        ---            2        ->       16         0  0x0000000000000000 0x00001803  (310) logd
[...]
0x000016a7  0x192c743d  recv,send   --TGSI--     0  ---      1     1         Y       16         0  0x0000000000000000
+     send        --------        ---            1         <-                                       0x00002d03  (81948) seserviced
+     send        --------        ---            1         <-                                       0x00002603  (74295) passd
[...]

Der Name ist der Standardname des Ports (ü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 dem Recht zum Senden den Besitzer desselben identifizieren (Portname + PID). Beachten Sie auch die Verwendung von +, um andere Aufgaben zu kennzeichnen, 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):

procesp 1 ports

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.

// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc receiver.c -o receiver

#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>

int main() {

// Create a new port.
mach_port_t port;
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (kr != KERN_SUCCESS) {
printf("mach_port_allocate() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_allocate() created port right name %d\n", port);


// Give us a send right to this port, in addition to the receive right.
kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
printf("mach_port_insert_right() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_insert_right() inserted a send right\n");


// Send the send right to the bootstrap server, so that it can be looked up by other processes.
kr = bootstrap_register(bootstrap_port, "org.darlinghq.example", port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_register() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_register()'ed our port\n");


// Wait for a message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
mach_msg_trailer_t trailer;
} message;

kr = mach_msg(
&message.header,  // Same as (mach_msg_header_t *) &message.
MACH_RCV_MSG,     // Options. We're receiving a message.
0,                // Size of the message being sent, if sending.
sizeof(message),  // Size of the buffer for receiving.
port,             // The port to receive a message on.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL    // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Got a message\n");

message.some_text[9] = 0;
printf("Text: %s, number: %d\n", message.some_text, message.some_number);
}

Das folgende Beispiel zeigt, wie ein Prozess eine Nachricht an einen anderen Prozess senden kann. In diesem Fall wird die msgsnd-Funktion verwendet, um eine Nachricht an die Message Queue mit der ID 1234 zu senden. Die Nachricht besteht aus einem Text und einem Typ. Der Empfängerprozess kann dann die Nachricht von der Queue abrufen und den Inhalt lesen.

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msg_buffer {
    long msg_type;
    char msg_text[100];
} message;

int main() {
    key_t key;
    int msg_id;

    key = ftok("/tmp", 65);
    msg_id = msgget(key, 0666 | IPC_CREAT);

    message.msg_type = 1;
    strcpy(message.msg_text, "Hello, this is a message!");

    msgsnd(msg_id, &message, sizeof(message), 0);

    printf("Message sent: %s\n", message.msg_text);

    return 0;
}
// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc sender.c -o sender

#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>

int main() {

// Lookup the receiver port using the bootstrap server.
mach_port_t port;
kern_return_t kr = bootstrap_look_up(bootstrap_port, "org.darlinghq.example", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_look_up() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_look_up() returned port right name %d\n", port);


// Construct our message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
} message;

message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
message.header.msgh_remote_port = port;
message.header.msgh_local_port = MACH_PORT_NULL;

strncpy(message.some_text, "Hello", sizeof(message.some_text));
message.some_number = 35;

// Send the message.
kr = mach_msg(
&message.header,  // Same as (mach_msg_header_t *) &message.
MACH_SEND_MSG,    // Options. We're sending a message.
sizeof(message),  // Size of the message being sent.
0,                // Size of the buffer for receiving.
MACH_PORT_NULL,   // A port to receive a message on, if receiving.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL    // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Sent a message\n");
}

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 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 erhalten 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 er HOST_KEXTD_PORT übernahm (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 von Systemdaemons im Besitz 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 abrufen

  • host_info: Hostinformationen abrufen

  • host_virtual_physical_table_info: Virtuelle/Physische Seitentabelle (erfordert MACH_VMDEBUG)

  • host_statistics: Hoststatistiken abrufen

  • mach_memory_info: Kernel-Speicherlayout abrufen

  • Host-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ämlich com.apple.private.kext*, die nur Apple-Binärdateien erhalten.

  • Weitere Routinen, die aufgerufen werden können, sind:

  • host_get_boot_info: machine_boot_info() abrufen

  • host_priv_statistics: Privilegierte Statistiken abrufen

  • vm_allocate_cpm: Kontinuierlichen physischen Speicher zuweisen

  • host_processors: Senderecht an Hostprozessoren

  • mach_vm_wire: Speicher resident machen

  • Da 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:

procexp all ports | grep "HSP"

Aufgaben Spezielle Ports

Diese Ports sind für bekannte Dienste reserviert. Es ist möglich, sie durch Aufruf von task_[get/set]_special_port zu erhalten/einzustellen. Sie können in task_special_ports.h gefunden werden:

typedef	int	task_special_port_t;

#define TASK_KERNEL_PORT	1	/* Represents task to the outside
world.*/
#define TASK_HOST_PORT		2	/* The host (priv) port for task.  */
#define TASK_BOOTSTRAP_PORT	4	/* Bootstrap environment for task. */
#define TASK_WIRED_LEDGER_PORT	5	/* Wired resource ledger for task. */
#define TASK_PAGED_LEDGER_PORT	6	/* Paged resource ledger for task. */

Von hier:

  • TASK_KERNEL_PORT[task-self send right]: Der Port, der zur Steuerung dieser Aufgabe verwendet wird. Wird verwendet, um Nachrichten zu senden, die die Aufgabe betreffen. Dies ist der Port, der von mach_task_self (siehe Task Ports unten) zurückgegeben wird.

  • TASK_BOOTSTRAP_PORT[bootstrap send right]: Der Bootstrap-Port der Aufgabe. Wird verwendet, um Nachrichten zur Rückgabe anderer Systemdienst-Ports zu senden.

  • TASK_HOST_NAME_PORT[host-self send right]: Der Port, der zur Anforderung von Informationen des enthaltenden Hosts verwendet wird. Dies ist der Port, der von mach_host_self zurückgegeben wird.

  • TASK_WIRED_LEDGER_PORT[ledger send right]: Der Port, der die Quelle benennt, aus der diese Aufgabe ihren verdrahteten Kernel-Speicher bezieht.

  • TASK_PAGED_LEDGER_PORT[ledger send right]: Der Port, der die Quelle benennt, aus der diese Aufgabe ihren standardmäßig verwalteten Speicher bezieht.

Task Ports

Ursprünglich hatte Mach keine "Prozesse", sondern "Aufgaben", die eher als Container von Threads betrachtet wurden. Als Mach mit BSD fusioniert wurde, wurde jede Aufgabe mit einem BSD-Prozess korreliert. Daher hat jeder BSD-Prozess die Details, die er benötigt, um ein Prozess zu sein, und jede Mach-Aufgabe hat auch ihre inneren Abläufe (mit Ausnahme der nicht vorhandenen PID 0, die die kernel_task ist).

Es gibt zwei sehr interessante Funktionen in Bezug darauf:

  • task_for_pid(Ziel-Aufgabenport, pid, &Aufgabenport_von_pid): Erhalten Sie ein SEND-Recht für den Aufgabenport der Aufgabe, die mit der durch die pid angegebenen Aufgabe korreliert ist, und geben Sie es an den angegebenen Ziel-Aufgabenport weiter (der normalerweise die aufrufende Aufgabe ist, die mach_task_self() verwendet hat, aber auch ein SEND-Port über eine andere Aufgabe sein könnte).

  • pid_for_task(Aufgabe, &pid): Bei Erhalt eines SEND-Rechts für eine Aufgabe wird ermittelt, mit welcher PID diese Aufgabe verbunden ist.

Um Aktionen innerhalb der Aufgabe auszuführen, benötigte die Aufgabe ein SEND-Recht für sich selbst, indem sie mach_task_self() aufrief (was die task_self_trap (28) verwendet). Mit dieser Berechtigung kann eine Aufgabe mehrere Aktionen ausführen, wie z.B.:

  • task_threads: SEND-Recht über alle Aufgabenports der Threads der Aufgabe erhalten

  • task_info: Informationen über eine Aufgabe erhalten

  • task_suspend/resume: Eine Aufgabe aussetzen oder fortsetzen

  • task_[get/set]_special_port

  • thread_create: Einen Thread erstellen

  • task_[get/set]_state: Aufgabenstatus steuern

  • und mehr finden Sie in mach/task.h

Beachten Sie, dass mit einem SEND-Recht über einen Aufgabenport einer anderen Aufgabe es möglich ist, solche Aktionen über eine andere Aufgabe auszuführen.

Darüber hinaus ist der Aufgabenport auch der vm_map-Port, der es ermöglicht, Speicher innerhalb einer Aufgabe zu lesen und zu manipulieren mit Funktionen wie vm_read() und vm_write(). Dies bedeutet im Grunde genommen, dass eine Aufgabe mit SEND-Rechten über den Aufgabenport einer anderen Aufgabe in der Lage sein wird, Code in diese Aufgabe einzuspeisen.

Denken Sie daran, dass, weil der Kernel auch eine Aufgabe ist, wenn es jemandem gelingt, SEND-Berechtigungen über den kernel_task zu erhalten, wird er in der Lage sein, den Kernel dazu zu bringen, beliebigen Code auszuführen (Jailbreaks).

  • Rufen Sie mach_task_self() auf, um den Namen für diesen Port für die aufrufende Aufgabe zu erhalten. Dieser Port wird nur beim exec() vererbt; eine neue Aufgabe, die mit fork() erstellt wurde, erhält einen neuen Aufgabenport (als Sonderfall erhält eine Aufgabe auch nach exec() in einer suid-Binärdatei einen neuen Aufgabenport). Der einzige Weg, eine Aufgabe zu erstellen und ihren Port zu erhalten, besteht darin, den "Port-Tausch-Tanz" während eines fork() durchzuführen.

  • Dies sind die Einschränkungen für den Zugriff auf den Port (aus macos_task_policy aus der Binärdatei AppleMobileFileIntegrity):

  • Wenn die App die com.apple.security.get-task-allow-Berechtigung hat, können Prozesse des gleichen Benutzers auf den Aufgabenport 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 Aufgabenport für jeden Prozess aufrufen, außer dem Kernel. In älteren Versionen wurde dies task_for_pid-allow genannt. Dies wird nur Apple-Anwendungen gewährt.

  • Root kann auf die Aufgabenports von Anwendungen zugreifen, die nicht mit einer gehärteten Laufzeitumgebung kompiliert wurden (und nicht von Apple stammen).

Der Aufgabenname-Port: Eine nicht privilegierte Version des Aufgabenports. Er verweist auf die Aufgabe, erlaubt jedoch nicht deren Steuerung. Das Einzige, was darüber verfügbar zu sein scheint, ist task_info().

Thread-Ports

Threads haben auch zugehörige Ports, die von der aufrufenden Aufgabe mit task_threads und vom Prozessor mit processor_set_threads sichtbar sind. Ein SEND-Recht für den Thread-Port ermöglicht die Verwendung der Funktionen des thread_act-Subsystem, wie z.B.:

  • thread_terminate

  • thread_[get/set]_state

  • act_[get/set]_state

  • thread_[suspend/resume]

  • thread_info

  • ...

Jeder Thread kann diesen Port durch Aufruf von mach_thread_sef erhalten.

Shellcode-Injektion in Thread über Aufgabenport

Sie können einen Shellcode von hier abrufen:

Introduction to ARM64v8
// clang -framework Foundation mysleep.m -o mysleep
// codesign --entitlements entitlements.plist -s - mysleep

#import <Foundation/Foundation.h>

double performMathOperations() {
double result = 0;
for (int i = 0; i < 10000; i++) {
result += sqrt(i) * tan(i) - cos(i);
}
return result;
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Process ID: %d", [[NSProcessInfo processInfo]
processIdentifier]);
while (true) {
[NSThread sleepForTimeInterval:5];

performMathOperations();  // Silent action

[NSThread sleepForTimeInterval:5];
}
}
return 0;
}

macOS IPC (Inter-Process Communication)

macOS IPC Mechanisms

macOS provides several mechanisms for inter-process communication (IPC), including:

  • Mach Messages: Low-level messaging system used by the kernel and other system services.

  • XPC Services: High-level API for creating inter-process communication for macOS applications.

  • Distributed Objects: Apple's legacy IPC mechanism, now deprecated in favor of XPC Services.

IPC Abuse for Privilege Escalation

Attackers can abuse IPC mechanisms to escalate privileges on macOS systems. Common techniques include:

  • Impersonating XPC Services: Creating malicious XPC services to impersonate legitimate services and gain elevated privileges.

  • Intercepting Mach Messages: Monitoring and intercepting Mach messages to manipulate inter-process communication and gain unauthorized access.

  • Exploiting Distributed Objects: Leveraging vulnerabilities in legacy Distributed Objects to escalate privileges.

Mitigation Strategies

To protect against IPC abuse, consider the following mitigation strategies:

  • Use Code Signing: Ensure all IPC services are properly code-signed to prevent the execution of unsigned or malicious code.

  • Implement Sandboxing: Utilize sandboxing to restrict the capabilities of IPC services and prevent unauthorized actions.

  • Monitor IPC Activity: Monitor IPC activity on macOS systems for suspicious behavior or unauthorized communication.

  • Update Regularly: Keep macOS systems and applications up to date to patch known vulnerabilities in IPC mechanisms.

By understanding macOS IPC mechanisms and implementing proper security measures, you can reduce the risk of privilege escalation through inter-process communication.

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>