hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-ipc-inter-process-communication/macos-mig-mach-interface-generator.md

15 KiB

macOS MIG - Gerador de Interface Mach

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:

MIG foi criado para simplificar o processo de criação de código Mach IPC. Basicamente, ele gera o código necessário para o servidor e o cliente se comunicarem com uma definição fornecida. Mesmo que o código gerado seja feio, um desenvolvedor só precisará importá-lo e seu código será muito mais simples do que antes.

Exemplo

Crie um arquivo de definição, neste caso com uma função muito simples:

{% code title="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);

{% endcode %}

Agora use o mig para gerar o código do servidor e do cliente que serão capazes de se comunicar entre si para chamar a função Subtract:

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

Vários novos arquivos serão criados no diretório atual.

Nos arquivos myipcServer.c e myipcServer.h você pode encontrar a declaração e definição da struct SERVERPREFmyipc_subsystem, que basicamente define a função a ser chamada com base no ID da mensagem recebida (indicamos um número inicial de 500):

{% tabs %} {% tab title="myipcServer.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)},
}
};

{% endtab %}

{% tab title="myipcServer.h" %}

/* 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;

Com base na estrutura anterior, a função myipc_server_routine receberá o ID da mensagem e retornará a função apropriada a ser chamada:

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

Neste exemplo, apenas definimos 1 função nas definições, mas se tivéssemos definido mais funções, elas estariam dentro do array de SERVERPREFmyipc_subsystem e a primeira teria sido atribuída ao ID 500, a segunda ao ID 501...

Na verdade, é possível identificar essa relação na struct subsystem_to_name_map_myipc do arquivo myipcServer.h:

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

Finalmente, outra função importante para fazer o servidor funcionar será myipc_server, que é a que realmente chama a função relacionada ao ID recebido:

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;
/* Tamanho mínimo: a rotina() irá atualizá-lo se for 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 as linhas anteriormente destacadas acessando a função a ser chamada por ID.

A seguir está o código para criar um servidor e um cliente simples onde o cliente pode chamar as funções Subtrair do servidor:

{% tabs %} {% tab title="myipc_server.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);
}

{% endtab %}

{% tab title="myipc_client.c" %}

macOS MIG - Mach Interface Generator

O macOS MIG (Mach Interface Generator) é uma ferramenta usada para gerar código C para comunicação entre processos em sistemas baseados em Mach. Ele é amplamente utilizado para comunicação entre processos em sistemas macOS e iOS.

O MIG gera funções de chamada de procedimento remoto (RPC) que permitem que um processo chame funções em outro processo. Isso pode ser abusado por um atacante para escalar privilégios ou realizar outras atividades maliciosas.

Para proteger contra abusos de IPC, é importante restringir as permissões de comunicação entre processos e garantir que apenas processos confjsonáveis tenham acesso aos serviços expostos via MIG. Além disso, é essencial validar todas as entradas e saídas para prevenir possíveis vulnerabilidades de segurança.

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

int main() {
    mach_port_t server_port;
    kern_return_t kr;

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

    myipc_function_1(server_port, 1234);

    return 0;
}

Neste exemplo, o cliente se conecta a um serviço chamado "com.example.myipc" e chama a função myipc_function_1 passando o server_port e um argumento inteiro.

Certifique-se de revisar e auditar cuidadosamente o uso do MIG em seus aplicativos para evitar possíveis abusos de IPC.

{% endtab %}

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

Análise Binária

Como muitos binários agora usam MIG para expor portas mach, é interessante saber como identificar que o MIG foi usado e as funções que o MIG executa com cada ID de mensagem.

jtool2 pode analisar informações do MIG de um binário Mach-O indicando o ID da mensagem e identificando a função a ser executada:

jtool2 -d __DATA.__const myipc_server | grep MIG

Foi mencionado anteriormente que a função que cuidará de chamar a função correta dependendo do ID da mensagem recebida era myipc_server. No entanto, geralmente você não terá os símbolos do binário (nomes de funções), então é interessante ver como ela se parece decompilada, pois sempre será muito semelhante (o código desta função é independente das funções expostas):

{% tabs %} {% tab title="myipc_server decompiled 1" %}

int _myipc_server(int arg0, int arg1) {
var_10 = arg0;
var_18 = arg1;
// Instruções iniciais para encontrar os ponteiros de função apropriados
*(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);
// Chamada para sign_extend_64 que pode ajudar a identificar esta função
// Isso armazena em rax o ponteiro para a chamada que precisa ser feita
// Verifique o uso do endereço 0x100004040 (array de endereços de funções)
// 0x1f4 = 500 (o ID de início)
            rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
            var_20 = rax;
// Se - senão, o se retorna falso, enquanto o senão chama a função correta e retorna verdadeiro
            if (rax == 0x0) {
                    *(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
else {
// Endereço calculado que chama a função apropriada com 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;
}

{% endtab %}

{% tab title="myipc_server decompiled 2" %} Esta é a mesma função decompilada em uma versão gratuita diferente do Hopper:

int _myipc_server(int arg0, int arg1) {
r31 = r31 - 0x40;
saved_fp = r29;
stack[-8] = r30;
var_10 = arg0;
var_18 = arg1;
// Instruções iniciais para encontrar os ponteiros de função apropriados
*(int32_t *)var_18 = *(int32_t *)var_10 & 0x1f | 0x0;
*(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;
r8 = *(int32_t *)(var_10 + 0x14);
r8 = r8 - 0x1f4;
if (r8 > 0x0) {
if (CPU_FLAGS & G) {
r8 = 0x1;
}
}
if ((r8 & 0x1) == 0x0) {
r8 = *(int32_t *)(var_10 + 0x14);
r8 = r8 - 0x1f4;
if (r8 < 0x0) {
if (CPU_FLAGS & L) {
r8 = 0x1;
}
}
if ((r8 & 0x1) == 0x0) {
r8 = *(int32_t *)(var_10 + 0x14);
// 0x1f4 = 500 (o ID de início)
                    r8 = r8 - 0x1f4;
                    asm { smaddl     x8, w8, w9, x10 };
r8 = *(r8 + 0x8);
var_20 = r8;
r8 = r8 - 0x0;
if (r8 != 0x0) {
if (CPU_FLAGS & NE) {
r8 = 0x1;
}
}
// Mesmo se else que na versão anterior
// Verifique o uso do endereço 0x100004040 (array de endereços de funções)
                    if ((r8 & 0x1) == 0x0) {
                            *(var_18 + 0x18) = **0x100004000;
                            *(int32_t *)(var_18 + 0x20) = 0xfffffed1;
var_4 = 0x0;
}
else {
// Chamada para o endereço calculado onde a função deve estar
                            (var_20)(var_10, var_18);
                            var_4 = 0x1;
}
}
else {
*(var_18 + 0x18) = **0x100004000;
*(int32_t *)(var_18 + 0x20) = 0xfffffed1;
var_4 = 0x0;
}
}
else {
*(var_18 + 0x18) = **0x100004000;
*(int32_t *)(var_18 + 0x20) = 0xfffffed1;
var_4 = 0x0;
}
r0 = var_4;
return r0;
}

{% endtab %} {% endtabs %}

Na verdade, se você for para a função 0x100004000, encontrará o array de structs routine_descriptor. O primeiro elemento da struct é o endereço onde a função é implementada, e a struct ocupa 0x28 bytes, então a cada 0x28 bytes (começando do byte 0) você pode obter 8 bytes e esse será o endereço da função que será chamada:

Esses dados podem ser extraídos usando este script do Hopper.

Aprenda hacking na AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks: