macOS MIG - Mach Interface Generator
Reading time: 11 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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Grundinformationen
MIG wurde entwickelt, um den Prozess der Mach IPC Codeerstellung zu vereinfachen. Es generiert den benötigten Code für Server und Client, um mit einer gegebenen Definition zu kommunizieren. Auch wenn der generierte Code unansehnlich ist, muss ein Entwickler ihn nur importieren, und sein Code wird viel einfacher sein als zuvor.
Die Definition wird in der Interface Definition Language (IDL) mit der Erweiterung .defs
angegeben.
Diese Definitionen haben 5 Abschnitte:
- Subsystemdeklaration: Das Schlüsselwort subsystem wird verwendet, um den Namen und die ID anzugeben. Es ist auch möglich, es als
KernelServer
zu kennzeichnen, wenn der Server im Kernel ausgeführt werden soll. - Inklusionen und Importe: MIG verwendet den C-Präprozessor, sodass es in der Lage ist, Importe zu verwenden. Darüber hinaus ist es möglich,
uimport
undsimport
für benutzer- oder servergenerierten Code zu verwenden. - Typdeklarationen: Es ist möglich, Datentypen zu definieren, obwohl normalerweise
mach_types.defs
undstd_types.defs
importiert werden. Für benutzerdefinierte Typen kann eine bestimmte Syntax verwendet werden: - [i
n/out]tran
: Funktion, die von einer eingehenden oder zu einer ausgehenden Nachricht übersetzt werden muss c[user/server]type
: Zuordnung zu einem anderen C-Typ.destructor
: Rufen Sie diese Funktion auf, wenn der Typ freigegeben wird.- Operationen: Dies sind die Definitionen der RPC-Methoden. Es gibt 5 verschiedene Typen:
routine
: Erwartet eine Antwortsimpleroutine
: Erwartet keine Antwortprocedure
: Erwartet eine Antwortsimpleprocedure
: Erwartet keine Antwortfunction
: Erwartet eine Antwort
Beispiel
Erstellen Sie eine Definitionsdatei, in diesem Fall mit einer sehr einfachen Funktion:
subsystem myipc 500; // Arbitrary name and id
userprefix USERPREF; // Prefix for created functions in the client
serverprefix SERVERPREF; // Prefix for created functions in the server
#include <mach/mach_types.defs>
#include <mach/std_types.defs>
simpleroutine Subtract(
server_port : mach_port_t;
n1 : uint32_t;
n2 : uint32_t);
Beachten Sie, dass das erste Argument der Port ist, an den gebunden werden soll, und MIG wird automatisch den Antwortport verwalten (es sei denn, mig_get_reply_port()
wird im Client-Code aufgerufen). Darüber hinaus wird die ID der Operationen sequentiell beginnend mit der angegebenen Subsystem-ID sein (wenn eine Operation veraltet ist, wird sie gelöscht und skip
wird verwendet, um ihre ID weiterhin zu verwenden).
Verwenden Sie nun MIG, um den Server- und Client-Code zu generieren, der in der Lage ist, miteinander zu kommunizieren, um die Subtract-Funktion aufzurufen:
mig -header myipcUser.h -sheader myipcServer.h myipc.defs
Mehrere neue Dateien werden im aktuellen Verzeichnis erstellt.
tip
Sie können ein komplexeres Beispiel in Ihrem System mit: mdfind mach_port.defs
finden.
Und Sie können es aus demselben Ordner wie die Datei mit: mig -DLIBSYSCALL_INTERFACE mach_ports.defs
kompilieren.
In den Dateien myipcServer.c
und myipcServer.h
finden Sie die Deklaration und Definition der Struktur SERVERPREFmyipc_subsystem
, die im Grunde die Funktion definiert, die basierend auf der empfangenen Nachrichten-ID aufgerufen werden soll (wir haben eine Startnummer von 500 angegeben):
/* Description of this subsystem, for use in direct RPC */
const struct SERVERPREFmyipc_subsystem SERVERPREFmyipc_subsystem = {
myipc_server_routine,
500, // start ID
501, // end ID
(mach_msg_size_t)sizeof(union __ReplyUnion__SERVERPREFmyipc_subsystem),
(vm_address_t)0,
{
{ (mig_impl_routine_t) 0,
// Function to call
(mig_stub_routine_t) _XSubtract, 3, 0, (routine_arg_descriptor_t)0, (mach_msg_size_t)sizeof(__Reply__Subtract_t)},
}
};
Basierend auf der vorherigen Struktur wird die Funktion myipc_server_routine
die Nachrichten-ID abrufen und die entsprechende Funktion zurückgeben, die aufgerufen werden soll:
mig_external mig_routine_t myipc_server_routine
(mach_msg_header_t *InHeadP)
{
int msgh_id;
msgh_id = InHeadP->msgh_id - 500;
if ((msgh_id > 0) || (msgh_id < 0))
return 0;
return SERVERPREFmyipc_subsystem.routine[msgh_id].stub_routine;
}
In diesem Beispiel haben wir nur 1 Funktion in den Definitionen definiert, aber wenn wir mehr Funktionen definiert hätten, wären sie im Array von SERVERPREFmyipc_subsystem
enthalten gewesen, und die erste wäre der ID 500 zugewiesen worden, die zweite der ID 501...
Wenn die Funktion eine Antwort senden sollte, würde die Funktion mig_internal kern_return_t __MIG_check__Reply__<name>
ebenfalls existieren.
Tatsächlich ist es möglich, diese Beziehung in der Struktur subsystem_to_name_map_myipc
aus myipcServer.h
(**subsystem*to_name_map*\***
** in anderen Dateien) zu identifizieren:
#ifndef subsystem_to_name_map_myipc
#define subsystem_to_name_map_myipc \
{ "Subtract", 500 }
#endif
Schließlich wird eine weitere wichtige Funktion, um den Server zum Laufen zu bringen, myipc_server
sein, die tatsächlich die Funktion aufruft, die mit der empfangenen ID verbunden ist:
mig_external boolean_t myipc_server
(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP)
{
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
mig_routine_t routine;
OutHeadP->msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REPLY(InHeadP->msgh_bits), 0);
OutHeadP->msgh_remote_port = InHeadP->msgh_reply_port;
/* Minimale Größe: routine() wird aktualisiert, wenn sie unterschiedlich ist */
OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t);
OutHeadP->msgh_local_port = MACH_PORT_NULL;
OutHeadP->msgh_id = InHeadP->msgh_id + 100;
OutHeadP->msgh_reserved = 0;
if ((InHeadP->msgh_id > 500) || (InHeadP->msgh_id < 500) ||
((routine = SERVERPREFmyipc_subsystem.routine[InHeadP->msgh_id - 500].stub_routine) == 0)) {
((mig_reply_error_t *)OutHeadP)->NDR = NDR_record;
((mig_reply_error_t *)OutHeadP)->RetCode = MIG_BAD_ID;
return FALSE;
}
(*routine) (InHeadP, OutHeadP);
return TRUE;
}
Überprüfen Sie die zuvor hervorgehobenen Zeilen, die auf die Funktion zugreifen, die nach ID aufgerufen werden soll.
Der folgende Code erstellt einen einfachen Server und Client, bei dem der Client die Funktionen Subtract vom Server aufrufen kann:
// gcc myipc_server.c myipcServer.c -o myipc_server
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "myipcServer.h"
kern_return_t SERVERPREFSubtract(mach_port_t server_port, uint32_t n1, uint32_t n2)
{
printf("Received: %d - %d = %d\n", n1, n2, n1 - n2);
return KERN_SUCCESS;
}
int main() {
mach_port_t port;
kern_return_t kr;
// Register the mach service
kr = bootstrap_check_in(bootstrap_port, "xyz.hacktricks.mig", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_check_in() failed with code 0x%x\n", kr);
return 1;
}
// myipc_server is the function that handles incoming messages (check previous exlpanation)
mach_msg_server(myipc_server, sizeof(union __RequestUnion__SERVERPREFmyipc_subsystem), port, MACH_MSG_TIMEOUT_NONE);
}
Der NDR_record
Der NDR_record wird von libsystem_kernel.dylib
exportiert und ist eine Struktur, die es MIG ermöglicht, Daten so zu transformieren, dass sie systemunabhängig sind, da MIG ursprünglich für die Verwendung zwischen verschiedenen Systemen gedacht war (und nicht nur auf derselben Maschine).
Dies ist interessant, da das Vorhandensein von _NDR_record
in einer Binärdatei als Abhängigkeit (jtool2 -S <binary> | grep NDR
oder nm
) bedeutet, dass die Binärdatei ein MIG-Client oder -Server ist.
Darüber hinaus haben MIG-Server die Dispatch-Tabelle in __DATA.__const
(oder in __CONST.__constdata
im macOS-Kernel und __DATA_CONST.__const
in anderen *OS-Kernen). Dies kann mit jtool2
ausgegeben werden.
Und MIG-Clients verwenden den __NDR_record
, um mit __mach_msg
an die Server zu senden.
Binäranalyse
jtool
Da viele Binärdateien jetzt MIG verwenden, um Mach-Ports bereitzustellen, ist es interessant zu wissen, wie man identifizieren kann, dass MIG verwendet wurde und die Funktionen, die MIG mit jeder Nachrichten-ID ausführt.
jtool2 kann MIG-Informationen aus einer Mach-O-Binärdatei parsen, die die Nachrichten-ID angibt und die auszuführende Funktion identifiziert:
jtool2 -d __DATA.__const myipc_server | grep MIG
Darüber hinaus sind MIG-Funktionen nur Wrapper der tatsächlichen Funktion, die aufgerufen wird, was bedeutet, dass Sie durch das Abrufen ihrer Disassemblierung und das Durchsuchen nach BL möglicherweise die tatsächlich aufgerufene Funktion finden können:
jtool2 -d __DATA.__const myipc_server | grep BL
Assembly
Es wurde zuvor erwähnt, dass die Funktion, die sich um das Aufrufen der richtigen Funktion je nach empfangenem Nachrichten-ID kümmert, myipc_server
war. Allerdings hat man normalerweise nicht die Symbole der Binärdatei (keine Funktionsnamen), daher ist es interessant zu überprüfen, wie es dekompiliert aussieht, da der Code dieser Funktion immer sehr ähnlich sein wird (der Code dieser Funktion ist unabhängig von den exponierten Funktionen):
int _myipc_server(int arg0, int arg1) {
var_10 = arg0;
var_18 = arg1;
// Anfangsinstruktionen, um die richtigen Funktionszeiger zu finden
*(int32_t *)var_18 = *(int32_t *)var_10 & 0x1f;
*(int32_t *)(var_18 + 0x8) = *(int32_t *)(var_10 + 0x8);
*(int32_t *)(var_18 + 0x4) = 0x24;
*(int32_t *)(var_18 + 0xc) = 0x0;
*(int32_t *)(var_18 + 0x14) = *(int32_t *)(var_10 + 0x14) + 0x64;
*(int32_t *)(var_18 + 0x10) = 0x0;
if (*(int32_t *)(var_10 + 0x14) <= 0x1f4 && *(int32_t *)(var_10 + 0x14) >= 0x1f4) {
rax = *(int32_t *)(var_10 + 0x14);
// Aufruf von sign_extend_64, der helfen kann, diese Funktion zu identifizieren
// Dies speichert in rax den Zeiger auf den Aufruf, der aufgerufen werden muss
// Überprüfen der Verwendung der Adresse 0x100004040 (Funktionsadressenarray)
// 0x1f4 = 500 (die Start-ID)
rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
var_20 = rax;
// Wenn - sonst, die if gibt false zurück, während else die richtige Funktion aufruft und true zurückgibt
if (rax == 0x0) {
*(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
else {
// Berechnete Adresse, die die richtige Funktion mit 2 Argumenten aufruft
(var_20)(var_10, var_18);
var_4 = 0x1;
}
}
else {
*(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
rax = var_4;
return rax;
}
Tatsächlich, wenn Sie zur Funktion 0x100004000
gehen, finden Sie das Array von routine_descriptor
Strukturen. Das erste Element der Struktur ist die Adresse, an der die Funktion implementiert ist, und die Struktur benötigt 0x28 Bytes, sodass Sie alle 0x28 Bytes (beginnend bei Byte 0) 8 Bytes erhalten können, und das wird die Adresse der Funktion sein, die aufgerufen wird:
Diese Daten können mit diesem Hopper-Skript extrahiert werden.
Debug
Der von MIG generierte Code ruft auch kernel_debug
auf, um Protokolle über Operationen beim Eintritt und Austritt zu generieren. Es ist möglich, sie mit trace
oder kdv
zu überprüfen: kdv all | grep MIG
References
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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.