MIG був створений для спрощення процесу створення коду Mach IPC. Він в основному генерує необхідний код для зв'язку сервера та клієнта відповідно до заданого визначення. Навіть якщо згенерований код виглядає неохайно, розробнику просто потрібно буде імпортувати його, і його код стане набагато простішим, ніж раніше.
Визначення вказується в Мові Визначення Інтерфейсу (IDL) з використанням розширення .defs.
Ці визначення мають 5 секцій:
Оголошення підсистеми: Ключове слово subsystem використовується для вказівки імені та ідентифікатора. Також можливо позначити його як KernelServer, якщо сервер повинен працювати в ядрі.
Включення та імпорти: MIG використовує C-препроцесор, тому він може використовувати імпорти. Більше того, можливо використовувати uimport та simport для коду, згенерованого користувачем або сервером.
Оголошення типів: Можливо визначити типи даних, хоча зазвичай він імпортує mach_types.defs та std_types.defs. Для власних типів можна використовувати деякий синтаксис:
[in/out]tran: Функція, яка повинна бути переведена з вхідного або на вихідне повідомлення
c[user/server]type: Відображення на інший тип C.
destructor: Викликати цю функцію, коли тип звільняється.
Операції: Це визначення методів RPC. Є 5 різних типів:
routine: Очікує відповідь
simpleroutine: Не очікує відповіді
procedure: Очікує відповідь
simpleprocedure: Не очікує відповіді
function: Очікує відповідь
Example
Створіть файл визначення, в цьому випадку з дуже простою функцією:
myipc.defs
subsystem myipc 500; // Arbitrary name and iduserprefix USERPREF; // Prefix for created functions in the clientserverprefix SERVERPREF; // Prefix for created functions in the server#include<mach/mach_types.defs>#include<mach/std_types.defs>simpleroutineSubtract(server_port : mach_port_t;n1 : uint32_t;n2 : uint32_t);
Зверніть увагу, що перший аргумент - це порт для прив'язки і MIG автоматично обробить порт відповіді (якщо не викликати mig_get_reply_port() у коді клієнта). Більше того, ID операцій буде послідовним, починаючи з вказаного ID підсистеми (тому, якщо операція застаріла, вона видаляється, а skip використовується для продовження використання її ID).
Тепер використовуйте MIG для генерації коду сервера та клієнта, які зможуть спілкуватися один з одним для виклику функції Subtract:
Several new files will be created in the current directory.
Ви можете знайти більш складний приклад у вашій системі за допомогою: mdfind mach_port.defs
І ви можете скомпілювати його з тієї ж папки, що й файл за допомогою: mig -DLIBSYSCALL_INTERFACE mach_ports.defs
In the files myipcServer.c and myipcServer.h you can find the declaration and definition of the struct SERVERPREFmyipc_subsystem, which basically defines the function to call based on the received message ID (we indicated a starting number of 500):
/* Description of this subsystem, for use in direct RPC */conststruct SERVERPREFmyipc_subsystem SERVERPREFmyipc_subsystem = {myipc_server_routine,500, // start ID501, // 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)},}};
/* Description of this subsystem, for use in direct RPC */externconststruct 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 */unsignedint maxsize; /* Max msg size */vm_address_t reserved; /* Reserved */struct routine_descriptor /* Array of routine descriptors */routine[1];} SERVERPREFmyipc_subsystem;
Виходячи з попередньої структури, функція myipc_server_routine отримає ідентифікатор повідомлення та поверне відповідну функцію для виклику:
В цьому прикладі ми визначили лише 1 функцію в визначеннях, але якби ми визначили більше функцій, вони були б всередині масиву SERVERPREFmyipc_subsystem, а перша була б призначена ID 500, друга - ID 501...
Якщо функція повинна була надіслати reply, функція mig_internal kern_return_t __MIG_check__Reply__<name> також існувала б.
Насправді, цю залежність можна ідентифікувати в структурі subsystem_to_name_map_myipc з myipcServer.h (subsystem_to_name_map_*** в інших файлах):
Перевірте раніше виділені рядки, що отримують доступ до функції для виклику за ідентифікатором.
Наступний код створює простий сервер і клієнт, де клієнт може викликати функції Subtract з сервера:
// 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_tSERVERPREFSubtract(mach_port_t server_port,uint32_t n1,uint32_t n2){printf("Received: %d - %d = %d\n", n1, n2, n1 - n2);return KERN_SUCCESS;}intmain() {mach_port_t port;kern_return_t kr;// Register the mach servicekr =bootstrap_check_in(bootstrap_port,"xyz.hacktricks.mig",&port);if (kr != KERN_SUCCESS) {printf("bootstrap_check_in() failed with code 0x%x\n", kr);return1;}// 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"intmain() {// 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);return1;}printf("Port right name %d\n", port);USERPREFSubtract(port,40,2);}
NDR_record
NDR_record експортується з libsystem_kernel.dylib, і це структура, яка дозволяє MIG перетворювати дані так, щоб вони були незалежними від системи, оскільки MIG задумувався для використання між різними системами (а не лише на одній машині).
Це цікаво, оскільки якщо _NDR_record знайдено в бінарному файлі як залежність (jtool2 -S <binary> | grep NDR або nm), це означає, що бінарний файл є клієнтом або сервером MIG.
Більше того, MIG сервери мають таблицю розподілу в __DATA.__const (або в __CONST.__constdata в ядрі macOS і __DATA_CONST.__const в інших ядрах *OS). Це можна вивантажити за допомогою jtool2.
А MIG клієнти використовуватимуть __NDR_record, щоб надсилати з __mach_msg до серверів.
Аналіз бінарних файлів
jtool
Оскільки багато бінарних файлів зараз використовують MIG для відкриття mach портів, цікаво знати, як виявити, що використовувався MIG і функції, які виконує MIG з кожним ідентифікатором повідомлення.
jtool2 може аналізувати інформацію MIG з Mach-O бінарного файлу, вказуючи ідентифікатор повідомлення та ідентифікуючи функцію для виконання:
jtool2-d__DATA.__constmyipc_server|grepMIG
Крім того, функції MIG є просто обгортками для фактичної функції, яка викликається, що означає, що отримавши її дизасемблювання та виконавши grep для BL, ви можете знайти фактичну функцію, яка викликається:
jtool2-d__DATA.__constmyipc_server|grepBL
Assembly
Було раніше згадано, що функція, яка буде відповідати за виклик правильного функції в залежності від отриманого ідентифікатора повідомлення, була 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 викликає правильну функцію і повертає trueif (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;}
Це та сама функція, декомпільована в іншій безкоштовній версії Hopper:
Насправді, якщо ви перейдете до функції 0x100004000, ви знайдете масив структур routine_descriptor. Перший елемент структури - це адреса, де функція реалізована, а структура займає 0x28 байт, тому кожні 0x28 байт (починаючи з байта 0) ви можете отримати 8 байт, і це буде адреса функції, яка буде викликана:
Код, згенерований MIG, також викликає kernel_debug, щоб генерувати журнали про операції на вході та виході. Можна перевірити їх, використовуючи trace або kdv: kdv all | grep MIG