macOS MIG - Mach Interface Generator

Ondersteun HackTricks

Basiese Inligting

MIG is geskep om die proses van Mach IPC-kode-skepping te vereenvoudig. Dit genereer basies die benodigde kode vir die bediener en klient om met 'n gegewe definisie te kommunikeer. Selfs as die gegenereerde kode lelik is, sal 'n ontwikkelaar dit net hoef in te voer en sy kode sal baie eenvoudiger wees as voorheen.

Die definisie word gespesifiseer in die Interface Definisie Taal (IDL) deur die gebruik van die .defs-uitbreiding.

Hierdie definisies het 5 afdelings:

  • Onderstelselverklaring: Die sleutelwoord onderstelsel word gebruik om die naam en die id aan te dui. Dit is ook moontlik om dit as KernelServer te merk as die bediener in die kernel moet loop.

  • Insluitings en invoere: MIG gebruik die C-preprosessor, sodat dit invoere kan gebruik. Daarbenewens is dit moontlik om uimport en simport vir gebruiker- of bedieners gegenereerde kode te gebruik.

  • Tipeverklarings: Dit is moontlik om datatipes te definieer, alhoewel dit gewoonlik mach_types.defs en std_types.defs sal invoer. Vir aangepaste tipes kan 'n paar sintaksis gebruik word:

  • [in/out]tran: Funksie wat van 'n inkomende of na 'n uitgaande boodskap vertaal moet word

  • c[user/server]type: Koppeling na 'n ander C-tipe.

  • destructor: Roep hierdie funksie aan wanneer die tipe vrygestel word.

  • Operasies: Dit is die definisies van die RPC-metodes. Daar is 5 verskillende tipes:

  • routine: Verwag antwoord

  • simpleroutine: Verwag nie antwoord nie

  • procedure: Verwag antwoord

  • simpleprocedure: Verwag nie antwoord nie

  • function: Verwag antwoord

Voorbeeld

Skep 'n definisie-lêer, in hierdie geval met 'n baie eenvoudige funksie:

myipc.defs
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);

Merk op dat die eerste argument die poort is om te bind en MIG sal outomaties die antwoordpoort hanteer (tensy mig_get_reply_port() geroep word in die kliëntkode). Verder sal die ID van die operasies opeenvolgend wees beginnende met die aangeduide subsisteem ID (so as 'n operasie verouderd is, word dit verwyder en skip word gebruik om steeds sy ID te gebruik).

Gebruik nou MIG om die bediener- en kliëntkode te genereer wat in staat sal wees om binne mekaar te kommunikeer om die Aftrek-funksie te roep:

mig -header myipcUser.h -sheader myipcServer.h myipc.defs

Verskeie nuwe lêers sal geskep word in die huidige gids.

Jy kan 'n meer komplekse voorbeeld in jou stelsel vind met: mdfind mach_port.defs En jy kan dit van dieselfde gids as die lêer kompileer met: mig -DLIBSYSCALL_INTERFACE mach_ports.defs

In die lêers myipcServer.c en myipcServer.h kan jy die deklarasie en definisie van die struktuur SERVERPREFmyipc_subsystem vind, wat basies die funksie definieer om te roep gebaseer op die ontvangsboodskap-ID (ons het 'n beginnommer van 500 aangedui):

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

macOS MIG (Mach Interface Generator)

MIG is a tool used to define inter-process communication (IPC) for macOS. It generates client-server communication code based on the interfaces defined in a .defs file. This allows processes to communicate with each other using messages.

Example:

#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "myipcServer.h"

kern_return_t myipc_server(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP);

In this example, myipc_server is the function that will be called when a message is received by the server. The server processes the message and sends a response back to the client.

MIG simplifies the process of defining IPC interfaces and handling messages between processes in macOS.

/* Description of this subsystem, for use in direct RPC */
extern const struct SERVERPREFmyipc_subsystem {
mig_server_routine_t	server;	/* Server routine */
mach_msg_id_t	start;	/* Min routine number */
mach_msg_id_t	end;	/* Max routine number + 1 */
unsigned int	maxsize;	/* Max msg size */
vm_address_t	reserved;	/* Reserved */
struct routine_descriptor	/* Array of routine descriptors */
routine[1];
} SERVERPREFmyipc_subsystem;

Gebaseer op die vorige struktuur sal die funksie myipc_server_routine die boodskap ID kry en die korrekte funksie teruggee om te roep:

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 hierdie voorbeeld het ons slegs 1 funksie in die definisies gedefinieer, maar as ons meer funksies sou definieer, sou hulle binne die array van SERVERPREFmyipc_subsystem gewees het en die eerste een sou aan die ID 500 toegewys gewees het, die tweede een aan die ID 501...

As die funksie verwag het om 'n antwoord te stuur, sou die funksie mig_internal kern_return_t __MIG_check__Reply__<name> ook bestaan.

Eintlik is dit moontlik om hierdie verhouding in die struktuur subsystem_to_name_map_myipc van myipcServer.h (subsystem_to_name_map_*** in ander lêers) te identifiseer:

#ifndef subsystem_to_name_map_myipc
#define subsystem_to_name_map_myipc \
{ "Subtract", 500 }
#endif

Uiteindelik, nog 'n belangrike funksie om die bediener te laat werk, sal myipc_server wees, wat die een is wat werklik die funksie oproep wat verband hou met die ontvangste id:

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;
/* Minimal size: routine() will update it if different */
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;
}

Kontroleer die voorheen uitgeligte lyne deur toegang tot die funksie om te roep volgens ID.

Die volgende is die kode om 'n eenvoudige bediener en kliënt te skep waar die kliënt die funksies van die bediener kan oproep:

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

Afrikaans Translation:

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

int main() {
    kern_return_t kr;
    mach_port_t server_port;
    myipc_msg_t msg;

    kr = bootstrap_look_up(bootstrap_port, "com.example.myipc_server", &server_port);
    if (kr != KERN_SUCCESS) {
        printf("Failed to look up server port: %s\n", mach_error_string(kr));
        return 1;
    }

    msg.hdr.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
    msg.hdr.msgh_size = sizeof(msg);
    msg.hdr.msgh_remote_port = server_port;
    msg.hdr.msgh_local_port = MACH_PORT_NULL;
    msg.hdr.msgh_id = 0;

    msg.body.msgh_descriptor_count = 0;

    kr = mach_msg(&msg.hdr, MACH_SEND_MSG, msg.hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
    if (kr != KERN_SUCCESS) {
        printf("Failed to send message: %s\n", mach_error_string(kr));
        return 1;
    }

    return 0;
}
// gcc myipc_client.c myipcUser.c -o myipc_client

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "myipcUser.h"

int main() {

// Lookup the receiver port using the bootstrap server.
mach_port_t port;
kern_return_t kr = bootstrap_look_up(bootstrap_port, "xyz.hacktricks.mig", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_look_up() failed with code 0x%x\n", kr);
return 1;
}
printf("Port right name %d\n", port);
USERPREFSubtract(port, 40, 2);
}

Die NDR_record

Die NDR_record word uitgevoer deur libsystem_kernel.dylib, en dit is 'n struktuur wat MIG toelaat om data te transformeer sodat dit onverskillig is van die stelsel waarvoor dit gebruik word aangesien MIG gedink was om tussen verskillende stelsels gebruik te word (en nie net op dieselfde masjien nie).

Dit is interessant omdat as _NDR_record gevind word in 'n binêre lêer as 'n afhanklikheid (jtool2 -S <binary> | grep NDR of nm), beteken dit dat die binêre lêer 'n MIG-kliënt of -bediener is.

Verder het MIG-bedieners die verspreidingstabel in __DATA.__const (of in __CONST.__constdata in macOS-kernel en __DATA_CONST.__const in ander *OS-kernelle). Dit kan gedump word met jtool2.

En MIG-kliënte sal die __NDR_record gebruik om met __mach_msg na die bedieners te stuur.

Binêre Analise

jtool

Aangesien baie binêre lêers nou MIG gebruik om mach-poorte bloot te stel, is dit interessant om te weet hoe om te identifiseer dat MIG gebruik is en die funksies wat MIG uitvoer met elke boodskap-ID.

jtool2 kan MIG-inligting van 'n Mach-O binêre lêer ontleding wat die boodskap-ID aandui en die funksie identifiseer om uit te voer:

jtool2 -d __DATA.__const myipc_server | grep MIG

Verder is MIG-funksies net omhulsels van die werklike funksie wat opgeroep word, wat beteken dat deur sy disassemblage te kry en te soek vir BL, jy moontlik die werklike funksie wat opgeroep word, kan vind:

jtool2 -d __DATA.__const myipc_server | grep BL

Monteer

Dit is voorheen genoem dat die funksie wat sal sorg vir die oproep van die korrekte funksie afhangende van die ontvangste boodskap-ID myipc_server was. Gewoonlik sal jy egter nie die simbole van die binêre lêer hê nie (geen funksienames nie), dus is dit interessant om te kyk hoe dit gedeaktiveer lyk aangesien dit altyd baie soortgelyk sal wees (die kode van hierdie funksie is onafhanklik van die blootgestelde funksies):

int _myipc_server(int arg0, int arg1) {
var_10 = arg0;
var_18 = arg1;
// Aanvanklike instruksies om die regte funksiepunte te vind
*(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);
// Oproep na sign_extend_64 wat kan help om hierdie funksie te identifiseer
// Dit stoor in rax die wyser na die oproep wat gemaak moet word
// Kyk na die gebruik van die adres 0x100004040 (funksie-adresse-reeks)
// 0x1f4 = 500 (die begin-ID)
            rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
            var_20 = rax;
// If - else, die if gee vals terug, terwyl die else die regte funksie oproep en waar teruggee
            if (rax == 0x0) {
                    *(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
else {
// Gekalibreerde adres wat die regte funksie met 2 argumente oproep
                    (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;
}

Eintlik, as jy na die funksie 0x100004000 gaan, sal jy die reeks van routine_descriptor strukture vind. Die eerste element van die struktuur is die adres waar die funksie geïmplementeer is, en die struktuur neem 0x28 byte, dus elke 0x28 byte (beginnend vanaf byte 0) kan jy 8 byte kry en dit sal die adres van die funksie wees wat geroep sal word:

Hierdie data kan onttrek word deur hierdie Hopper-skrip te gebruik.

Foutopsporing

Die kode wat deur MIG gegenereer word, roep ook kernel_debug aan om logboeke oor operasies by in- en uitgang te genereer. Dit is moontlik om hulle te kontroleer met behulp van trace of kdv: kdv all | grep MIG

Verwysings

Ondersteun HackTricks

Last updated