macOS MIG - Mach Interface Generator
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Basic Information
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.
The definition is specified in Interface Definition Language (IDL) using the .defs extension.
These definitions have 5 sections:
Subsystem declaration: The keyword subsystem is used to indicate the name and the id. It's also possible to mark it as
KernelServerif the server should run in the kernel.Inclusions and imports: MIG uses the C-prepocessor, so it's able to use imports. Moreover, it's possible to use
uimportandsimportfor user or server generated code.Type declarations: It's possible to define data types although usually it will import
mach_types.defsandstd_types.defs. For custom ones some syntax can be used:[i
n/out]tran: Function that needs to be trasnlated from an incoming or to an outgoing messagec[user/server]type: Mapping to another C type.destructor: Call this function when the type is released.
Operations: These are the definitions of the RPC methods. There are 5 different types:
routine: Expects replysimpleroutine: Doesn't expect replyprocedure: Expects replysimpleprocedure: Doesn't expect replyfunction: Expects reply
Example
Create a definition file, in this case with a very simple function:
Note that the first argument is the port to bind and MIG will automatically handle the reply port (unless calling mig_get_reply_port() in the client code). Moreover, the ID of the operations will be sequential starting by the indicated subsystem ID (so if an operation is deprecated it's deleted and skip is used to still use its ID).
Now use MIG to generate the server and client code that will be able to communicate within each other to call the Subtract function:
Several new files will be created in the current directory.
You can find a more complex example in your system with: mdfind mach_port.defs
And you can compile it from the same folder as the file with: 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):
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...
If the function was expected to send a reply the function mig_internal kern_return_t __MIG_check__Reply__<name> would also exist.
Actually it's possible to identify this relation in the struct subsystem_to_name_map_myipc from myipcServer.h (subsystem_to_name_map_*** in other files):
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.
The following is the code to create a simple server and client where the client can call the functions Subtract from the server:
The NDR_record
The NDR_record is exported by libsystem_kernel.dylib, and it's a struct that allows MIG to transform data so it's agnostic of the system it's being used as MIG was thought to be used between different systems (and not only in the same machine).
This is interesting because if _NDR_record is found in a binary as a dependency (jtool2 -S <binary> | grep NDR or nm), it means that the binary is a MIG client or Server.
Moreover MIG servers have the dispatch table in __DATA.__const (or in __CONST.__constdata in macOS kernel and __DATA_CONST.__const in other *OS kernels). This can be dumped with jtool2.
And MIG clients will use the __NDR_record to send with __mach_msg to the servers.
Binary Analysis
jtool
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:
Moreover, MIG functions are just wrappers of the actual function that gets called, which means taht getting its dissasembly and grepping for BL you might be able to find the acatual function being called:
Assembly
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):
This is the same function decompiled in a difefrent Hopper free version:
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:


This data can be extracted using this Hopper script.
Debug
The code generated by MIG also calles kernel_debug to generate logs about operations on entry and exit. It's possible to check them using trace or kdv: kdv all | grep MIG
References
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Last updated