macOS MIG - Mach Interface Generator

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

MIG був створений для спрощення процесу створення коду Mach IPC. Він в основному генерує необхідний код для сервера та клієнта для спілкування з заданою ​​визначенням. Навіть якщо згенерований код виглядає некрасиво, розробнику просто потрібно буде імпортувати його, і його код буде набагато простішим, ніж раніше.

Приклад

Створіть файл визначення, у цьому випадку з дуже простою функцією:

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

Тепер використовуйте mig для генерації коду сервера та клієнта, які зможуть спілкуватися між собою, щоб викликати функцію Subtract:

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

Кілька нових файлів буде створено в поточному каталозі.

У файлах myipcServer.c та myipcServer.h ви можете знайти декларацію та визначення структури SERVERPREFmyipc_subsystem, яка в основному визначає функцію для виклику на основі отриманого ідентифікатора повідомлення (ми вказали початковий номер 500):

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

На основі попередньої структури функція myipc_server_routine отримає ідентифікатор повідомлення та поверне відповідну функцію для виклику:

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

У цьому прикладі ми визначили лише 1 функцію в визначеннях, але якби ми визначили більше функцій, вони були б у масиві SERVERPREFmyipc_subsystem, і перша була б призначена для ID 500, друга для ID 501...

Фактично, можна ідентифікувати цей зв'язок у структурі subsystem_to_name_map_myipc з myipcServer.h:

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

Нарешті, ще одна важлива функція для роботи сервера буде myipc_server, яка фактично викликає функцію, пов'язану з отриманим ідентифікатором:

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;
/* Мінімальний розмір: routine() оновить його, якщо він відрізняється */
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;
}

Перевірте попередньо підсвічені рядки, отримуючи доступ до функції для виклику за ідентифікатором.

У наступному код для створення простого сервера та клієнта, де клієнт може викликати функції від сервера:

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

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

Аналіз бінарних файлів

Оскільки багато бінарних файлів зараз використовують MIG для виходу на зв'язок з портами mach, цікаво знати, як визначити, що був використаний MIG та функції, які виконує MIG з кожним ідентифікатором повідомлення.

jtool2 може розбирати інформацію MIG з бінарного файлу Mach-O, вказуючи ідентифікатор повідомлення та ідентифікуючи функцію для виконання:

jtool2 -d __DATA.__const myipc_server | grep MIG

Було зазначено, що функція, яка буде відповідати за виклик правильної функції в залежності від отриманого ідентифікатора повідомлення, - це myipc_server. Однак, зазвичай у вас не буде символів бінарного файлу (назв функцій), тому цікаво перевірити, як виглядає декомпільована версія, оскільки вона завжди буде дуже схожа (код цієї функції незалежний від викладених функцій):

int _myipc_server(int arg0, int arg1) {
var_10 = arg0;
var_18 = arg1;
// Початкові інструкції для знаходження відповідних вказівників функцій
*(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);
// Виклик sign_extend_64, який може допомогти ідентифікувати цю функцію
// Це зберігає в rax вказівник на виклик, який потрібно викликати
// Перевірте використання адреси 0x100004040 (масив адрес функцій)
// 0x1f4 = 500 (початковий ID)
            rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
            var_20 = rax;
// Якщо - інакше, якщо if повертає false, тоді else викликає правильну функцію та повертає true
            if (rax == 0x0) {
                    *(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
else {
// Розрахована адреса, яка викликає відповідну функцію з 2 аргументами
                    (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;
}

Фактично, якщо ви перейдете до функції 0x100004000, ви знайдете масив структур routine_descriptor. Перший елемент структури - це адреса, де реалізована функція, і структура займає 0x28 байтів, тому кожні 0x28 байтів (починаючи з байту 0) ви можете отримати 8 байтів, і це буде адреса функції, яка буде викликана:

Ці дані можна витягти за допомогою цього сценарію Hopper.

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Last updated