macOS MIG - Mach Interface Generator

支持HackTricks

基本信息

MIG被创建用于简化Mach IPC代码的生成过程。它基本上为服务器和客户端之间的通信生成所需的代码。即使生成的代码看起来很丑陋,开发人员只需导入它,他的代码将比以前简单得多。

定义是使用接口定义语言(IDL)使用.defs扩展名指定的。

这些定义有5个部分:

  • 子系统声明:关键字子系统用于指示名称ID。还可以将其标记为**KernelServer**,如果服务器应在内核中运行。

  • 包含和导入:MIG使用C预处理器,因此可以使用导入。此外,可以使用uimportsimport用于用户或服务器生成的代码。

  • 类型声明:可以定义数据类型,尽管通常会导入mach_types.defsstd_types.defs。对于自定义类型,可以使用一些语法:

    • [i`n/out]tran:需要从传入消息翻译的函数或传出消息翻译的函数

    • c[user/server]type:映射到另一个C类型。

    • destructor:在释放类型时调用此函数。

  • 操作:这些是RPC方法的定义。有5种不同类型:

    • routine:期望回复

    • simpleroutine:不期望回复

    • procedure:期望回复

    • simpleprocedure:不期望回复

    • function:期望回复

示例

创建一个定义文件,这里是一个非常简单的函数:

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将自动处理回复端口(除非在客户端代码中调用mig_get_reply_port())。此外,操作的ID将是连续的,从指定的子系统ID开始(因此,如果某个操作已被弃用,则会被删除,并且使用skip来仍然使用其ID)。

现在使用MIG生成服务器和客户端代码,这些代码将能够相互通信以调用Subtract函数:

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

当前目录中将创建几个新文件。

您可以在系统中找到一个更复杂的示例:mdfind mach_port.defs 您可以从与文件相同的文件夹中编译它:mig -DLIBSYSCALL_INTERFACE mach_ports.defs

在文件**myipcServer.cmyipcServer.h中,您可以找到结构体SERVERPREFmyipc_subsystem**的声明和定义,该结构体基本上定义了根据接收的消息ID调用的函数(我们指定了起始编号为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)},
}
};

macOS MIG (Mach Interface Generator)

macOS MIG (Mach Interface Generator) 是一种用于生成客户端-服务器通信接口的工具。它允许开发人员定义一组消息,这些消息用于在进程之间进行通信。MIG 工具会根据这些消息生成客户端和服务器之间通信所需的代码。

在 macOS 中,MIG 工具通常用于生成与内核和驱动程序之间的通信接口。这些接口定义了内核服务的调用方式,允许用户空间进程请求内核执行特定操作。

通过理解和利用 MIG 生成的接口,黑客可以尝试进行特权升级或执行其他恶意操作。因此,在进行 macOS 安全加固时,需要特别注意保护与 MIG 相关的进程和通信接口,以防止黑客利用这些接口进行攻击。

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

根据前面的结构,函数**myipc_server_routine将获取消息ID**并返回要调用的适当函数:

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

在这个示例中,我们只在定义中定义了一个函数,但如果我们定义了更多函数,它们将位于**SERVERPREFmyipc_subsystem**的数组内,第一个函数将被分配给ID 500,第二个函数将被分配给ID 501...

如果预期函数将发送一个回复,那么函数mig_internal kern_return_t __MIG_check__Reply__<name>也会存在。

实际上可以在**myipcServer.h中的结构体subsystem_to_name_map_myipc(在其他文件中为subsystem_to_name_map_*****)中识别这种关系:

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

最后,使服务器工作的另一个重要函数将是**myipc_server,这个函数实际上会调用**与接收到的id相关的函数:

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;
/* Minimal size: routine() will update it if different */
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;
}

检查前面突出显示的行,访问要按ID调用的函数。

以下是创建一个简单服务器客户端的代码,其中客户端可以调用服务器的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_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);
}

在macOS中,MIG(Mach接口生成器)是一种用于处理进程间通信(IPC)的强大工具。通过MIG,可以定义用于在Mach消息传递系统中进行IPC的接口。攻击者可以利用MIG生成的代码中的漏洞来实现特权升级和进程滥用。

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

NDR_record

NDR_record由libsystem_kernel.dylib导出,它是一个结构体,允许MIG转换数据,使其对使用它的系统不可知,因为MIG被设计用于在不同系统之间使用(而不仅仅是在同一台机器上)。

这很有趣,因为如果在二进制文件中发现_NDR_record作为一个依赖项(jtool2 -S <binary> | grep NDRnm),这意味着该二进制文件是一个MIG客户端或服务器。

此外,MIG服务器__DATA.__const(或macOS内核中的__CONST.__constdata和其他*OS内核中的__DATA_CONST.__const)中具有分发表。可以使用**jtool2**来转储这些内容。

MIG客户端将使用__NDR_record通过__mach_msg发送给服务器。

二进制分析

jtool

由于许多二进制文件现在使用MIG来公开mach端口,了解如何识别MIG的使用以及MIG在每个消息ID上执行的函数是很有趣的。

jtool2可以从Mach-O二进制文件中解析MIG信息,指示消息ID并识别要执行的函数:

jtool2 -d __DATA.__const myipc_server | grep MIG

此外,MIG 函数只是被调用的实际函数的包装器,这意味着通过获取其反汇编并使用 BL 进行过滤,您可能会找到实际被调用的函数:

jtool2 -d __DATA.__const myipc_server | grep BL

汇编

先前提到负责根据接收的消息ID调用正确函数的函数是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脚本提取。

调试

MIG 生成的代码还调用 kernel_debug 来生成关于进入和退出操作的日志。可以使用 tracekdv 来检查它们:kdv all | grep MIG

参考资料

Last updated