MIG was created to simplify the process of Mach IPC code creation. It basically generates the needed code for server and client to communicate with a given definition. Even if the generated code is ugly, a developer will just need to import it and his code will be much simpler than before.
Example
Create a definition file, in this case with a very simple function:
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);
Now use mig to generate the server and client code that will be able to comunicate within each other to call the Subtract function:
Several new files will be created in the current directory.
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;
Based on the previous struct the function myipc_server_routine will get the message ID and return the proper function to call:
In this example we have only defined 1 function in the definitions, but if we would have defined more functions, they would have been inside the array of SERVERPREFmyipc_subsystem and the first one would have been assigned to the ID 500, the second one to the ID 501...
Actually it's possible to identify this relation in the struct subsystem_to_name_map_myipc from myipcServer.h:
Finally, another important function to make the server work will be myipc_server, which is the one that will actually call the function related to the received id:
Check the previously highlighted lines accessing the function to call by ID.
In the following is the code to create a simple server and client where the client can call the functions Subtract from the server:
// 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 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);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);}
Binary Analysis
As many binaries now use MIG to expose mach ports, it's interesting to know how to identify that MIG was used and the functions that MIG executes with each message ID.
jtool2 can parse MIG information from a Mach-O binary indicating the message ID and identifying the function to execute:
jtool2-d__DATA.__constmyipc_server|grepMIG
It was previously mentioned that the function that will take care of calling the correct function depending on the received message ID was myipc_server. However, you usually won't have the symbols of the binary (no functions names), so it's interesting to check how it looks like decompiled as it will always be very similar (the code of this function is independent from the functions exposed):
int_myipc_server(int arg0,int arg1) { var_10 = arg0; var_18 = arg1;// Initial instructions to find the proper function ponters*(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);// Call to sign_extend_64 that can help to identifyf this function// This stores in rax the pointer to the call that needs to be called// Check the used of the address 0x100004040 (functions addresses array)// 0x1f4 = 500 (the strating ID) rax =*(sign_extend_64(rax -0x1f4)*0x28+0x100004040); var_20 = rax;// If - else, the if returns false, while the else call the correct function and returns trueif (rax ==0x0) {*(var_18 +0x18) =**_NDR_record;*(int32_t*)(var_18 +0x20) =0xfffffffffffffed1; var_4 =0x0; }else {// Calculated address that calls the proper function with 2 arguments (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;}
This is the same function decompiled in a difefrent Hopper free version:
int_myipc_server(int arg0,int arg1) { r31 = r31 -0x40; saved_fp = r29; stack[-8] = r30; var_10 = arg0; var_18 = arg1;// Initial instructions to find the proper function ponters*(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 (the strating ID) 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; } }// Same if else as in the previous version// Check the used of the address 0x100004040 (functions addresses array)if ((r8 &0x1) ==0x0) {*(var_18 +0x18) =**0x100004000;*(int32_t*)(var_18 +0x20) =0xfffffed1; var_4 =0x0; }else {// Call to the calculated address where the function should be (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;}
Actually if you go to the function 0x100004000 you will find the array of routine_descriptor structs. The first element of the struct is the address where the function is implemented, and the struct takes 0x28 bytes, so each 0x28 bytes (starting from byte 0) you can get 8 bytes and that will be the address of the function that will be called: