macOS MIG - Mach Interface Generator

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Informaci贸n B谩sica

MIG fue creado para simplificar el proceso de creaci贸n de c贸digo Mach IPC. B谩sicamente genera el c贸digo necesario para que el servidor y el cliente se comuniquen con una definici贸n dada. Aunque el c贸digo generado es feo, un desarrollador solo necesitar谩 importarlo y su c贸digo ser谩 mucho m谩s simple que antes.

La definici贸n se especifica en el Lenguaje de Definici贸n de Interfaces (IDL) utilizando la extensi贸n .defs.

Estas definiciones tienen 5 secciones:

  • Declaraci贸n de subsistema: La palabra clave subsistema se utiliza para indicar el nombre y el id. Tambi茅n es posible marcarlo como KernelServer si el servidor debe ejecutarse en el kernel.
  • Inclusiones e importaciones: MIG utiliza el preprocesador C, por lo que puede usar importaciones. Adem谩s, es posible usar uimport y simport para c贸digo generado por el usuario o el servidor.
  • Declaraciones de tipo: Es posible definir tipos de datos aunque generalmente importar谩 mach_types.defs y std_types.defs. Para los personalizados se puede usar alguna sintaxis:
  • [in/out]tran: Funci贸n que necesita ser traducida de un mensaje entrante o a un mensaje saliente
  • c[user/server]type: Mapeo a otro tipo C.
  • destructor: Llama a esta funci贸n cuando el tipo es liberado.
  • Operaciones: Estas son las definiciones de los m茅todos RPC. Hay 5 tipos diferentes:
  • routine: Espera respuesta
  • simpleroutine: No espera respuesta
  • procedure: Espera respuesta
  • simpleprocedure: No espera respuesta
  • function: Espera respuesta

Ejemplo

Crea un archivo de definici贸n, en este caso con una funci贸n muy simple:

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

Tenga en cuenta que el primer argumento es el puerto a enlazar y MIG manejar谩 autom谩ticamente el puerto de respuesta (a menos que se llame a mig_get_reply_port() en el c贸digo del cliente). Adem谩s, el ID de las operaciones ser谩 secuencial comenzando por el ID del subsistema indicado (as铆 que si una operaci贸n est谩 obsoleta, se elimina y se usa skip para seguir utilizando su ID).

Ahora use MIG para generar el c贸digo del servidor y del cliente que podr谩 comunicarse entre s铆 para llamar a la funci贸n Subtract:

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

Se crear谩n varios archivos nuevos en el directorio actual.

tip

Puedes encontrar un ejemplo m谩s complejo en tu sistema con: mdfind mach_port.defs
Y puedes compilarlo desde la misma carpeta que el archivo con: mig -DLIBSYSCALL_INTERFACE mach_ports.defs

En los archivos myipcServer.c y myipcServer.h puedes encontrar la declaraci贸n y definici贸n de la estructura SERVERPREFmyipc_subsystem, que b谩sicamente define la funci贸n a llamar seg煤n el ID del mensaje recibido (indicamos un n煤mero inicial de 500):

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

Basado en la estructura anterior, la funci贸n myipc_server_routine obtendr谩 el ID del mensaje y devolver谩 la funci贸n adecuada a llamar:

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

En este ejemplo, solo hemos definido 1 funci贸n en las definiciones, pero si hubi茅ramos definido m谩s funciones, estar铆an dentro del array de SERVERPREFmyipc_subsystem y la primera se habr铆a asignado al ID 500, la segunda al ID 501...

Si se esperaba que la funci贸n enviara una respuesta, la funci贸n mig_internal kern_return_t __MIG_check__Reply__<name> tambi茅n existir铆a.

De hecho, es posible identificar esta relaci贸n en la estructura subsystem_to_name_map_myipc de myipcServer.h (**subsystem*to_name_map*\***** en otros archivos):

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

Finalmente, otra funci贸n importante para hacer que el servidor funcione ser谩 myipc_server, que es la que realmente llamar谩 a la funci贸n relacionada con el id recibido:

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;
/* Tama帽o m铆nimo: routine() lo actualizar谩 si es diferente */
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;
}

Verifique las l铆neas resaltadas anteriormente que acceden a la funci贸n para llamar por ID.

El siguiente es el c贸digo para crear un servidor y un cliente simples donde el cliente puede llamar a las funciones Subtract del servidor:

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

El NDR_record

El NDR_record es exportado por libsystem_kernel.dylib, y es una estructura que permite a MIG transformar datos para que sea agn贸stico del sistema en el que se est谩 utilizando, ya que se pens贸 que MIG se usar铆a entre diferentes sistemas (y no solo en la misma m谩quina).

Esto es interesante porque si se encuentra _NDR_record en un binario como una dependencia (jtool2 -S <binary> | grep NDR o nm), significa que el binario es un cliente o servidor MIG.

Adem谩s, los servidores MIG tienen la tabla de despacho en __DATA.__const (o en __CONST.__constdata en el n煤cleo de macOS y __DATA_CONST.__const en otros n煤cleos *OS). Esto se puede volcar con jtool2.

Y los clientes MIG usar谩n el __NDR_record para enviar con __mach_msg a los servidores.

An谩lisis de Binarios

jtool

Como muchos binarios ahora utilizan MIG para exponer puertos mach, es interesante saber c贸mo identificar que se utiliz贸 MIG y las funciones que MIG ejecuta con cada ID de mensaje.

jtool2 puede analizar informaci贸n de MIG de un binario Mach-O indicando el ID de mensaje e identificando la funci贸n a ejecutar:

bash
jtool2 -d __DATA.__const myipc_server | grep MIG

Adem谩s, las funciones MIG son solo envolturas de la funci贸n real que se llama, lo que significa que al obtener su desensamblado y buscar BL, es posible que puedas encontrar la funci贸n real que se est谩 llamando:

bash
jtool2 -d __DATA.__const myipc_server | grep BL

Assembly

Se mencion贸 anteriormente que la funci贸n que se encargar谩 de llamar a la funci贸n correcta dependiendo del ID de mensaje recibido era myipc_server. Sin embargo, generalmente no tendr谩s los s铆mbolos del binario (sin nombres de funciones), por lo que es interesante ver c贸mo se ve decompilado ya que siempre ser谩 muy similar (el c贸digo de esta funci贸n es independiente de las funciones expuestas):

int _myipc_server(int arg0, int arg1) {
var_10 = arg0;
var_18 = arg1;
// Instrucciones iniciales para encontrar los punteros de funci贸n adecuados
*(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);
// Llamada a sign_extend_64 que puede ayudar a identificar esta funci贸n
// Esto almacena en rax el puntero a la llamada que necesita ser llamada
// Ver el uso de la direcci贸n 0x100004040 (array de direcciones de funciones)
// 0x1f4 = 500 (el ID de inicio)
            rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
            var_20 = rax;
// If - else, el if devuelve falso, mientras que el else llama a la funci贸n correcta y devuelve verdadero
            if (rax == 0x0) {
                    *(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
else {
// Direcci贸n calculada que llama a la funci贸n adecuada con 2 argumentos
                    (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;
}

En realidad, si vas a la funci贸n 0x100004000 encontrar谩s el array de routine_descriptor structs. El primer elemento de la struct es la direcci贸n donde se implementa la funci贸n, y la struct ocupa 0x28 bytes, as铆 que cada 0x28 bytes (comenzando desde el byte 0) puedes obtener 8 bytes y esa ser谩 la direcci贸n de la funci贸n que ser谩 llamada:

Estos datos pueden ser extra铆dos usando este script de Hopper.

Debug

El c贸digo generado por MIG tambi茅n llama a kernel_debug para generar registros sobre operaciones de entrada y salida. Es posible revisarlos usando trace o kdv: kdv all | grep MIG

References

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks