macOS IPC Study Notes
1. 方式
MIG
XPC
DO
….
然而一切都是在Mach Msg的基础之上的。
2. 一些基础概念
2.1 什么是Port
个人理解就是类似Windows上handle的概念。
用户态经过处理是一个类似socket的整数,内核态(namep)索引到与之对应的消息队列,IPC时通过Port传递数据到消息队列,或者从消息队列取出数据。
2.2.2 port name
2.2.3 (port) right
一个port和对这个port的访问权限,有对应的权限才能做对应的操作,比如recv,接收数据;send,发送数据。
1 | #define MACH_PORT_RIGHT_SEND ((mach_port_right_t) 0) |
2.2 创建流程
示例代码:
1 | mach_port_t p; |
mach_port_allocate
函数定义如下:
1 | kern_return_t |
mach_port_allocate_full
根据不同的right走不同的分配逻辑:
ipc_port_alloc
/ipc_port_alloc_name
两个函数区别只是是否指定了name。
ipc_object_alloc
348行通过一个宏,把port转name的方式获取namep,之后对ipc entry的关键结构进行初始化。
ipc_port_init
初始化port结构
至此,port初始化完成,namep初始化完成,可以根据namep索引到对应的内核中的消息队列。
port与ipc entry的关系,来自Mac OS X Internals:
其中的一些概念:
- 用户态 mach_port_t
port在用户态表示,类似socket的一个整数
内核态 mach_port_name_t
1
typedef natural_t mach_port_name_t;
port在内核态表示
qos
1
Structure used to pass information about port allocation requests.Must be padded to 64-bits total length.
ipc space
1 | Each task has a private IPC spacea namespace for portsthat is represented by the ipc_space structure in the kernel. |
每个task都有自己的唯一一个ipc sapce,
其中 ipc_entry_t is_table; /* an array of entries */
字段是放着所有的ipc entry。
task的结构体实在是太大了,从task的struct ipc_space *itk_space;
字段索引到其对应的ipc space。
- ipc entry
看源码发现,图中的ipc_tree_entry
结构没了:
1 | struct ipc_space { |
- port的user reference计数是啥
一个port的user reference只表示了某个entry在task的space中被多少个地方使用,和entry实际指向哪个port没有关系
2.3 发送MACH MSG
mach msg的结构不再赘述,这部分直接看message.h
头文件里的定义即可,下面着重看发送和接收过程。
其实是一个把mach msg转换成kmsg结构,然后入队(目标消息队列)的操作,目标进程获取就是一个出队的操作。
用户态(client) <--> 内核态 <--> 用户态(server)
收/发都是用mach_msg
,使用options参数区别是收还是发。
2.3.1 流程
1 | mach_msg(...) |
2.3.2 发送
mach_msg_send( mach_msg_header_t *msg, mach_msg_option_t option, mach_msg_size_t send_size, mach_msg_timeout_t send_timeout, mach_msg_priority_t override)
根据消息大小重新分配了内存,并且把消息拷贝进来,并且消息尾部增加了一些字段:
1 | trailer = (mach_msg_max_trailer_t *) ((vm_offset_t)kmsg->ikm_header + send_size); |
之前审服务的时候遇到过,还以为这部分是可控的,造成乌龙。 囧
ipc_kmsg_copyin(kmsg, space, map, override, &option);
此时
kmsg
是新分配的内存,里面放的是要发送的mach msg, space和map都是当前task的space和map,直接获取,这部分有个图,可以看Macos Internals
。- ipc_kmsg_copyin_header(kmsg, space, override, optionp);
拷贝port rights
,成功的话,原本消息中(kmsg)的port name都会被替换成对应的对象的指针。 - ipc_kmsg_copyin_body( kmsg, space, map);
拷贝msg body部分,中间验证了size、desc部分的size,类型等字段。
desc_count < 0x3fff 。
descriptor_size部分,必须是desc*16 == descriptor_size,不满足会为了对齐而调整。
最终完成拷贝,把用户态的mach msg
拷贝到了kmsg中。
- ipc_kmsg_copyin_header(kmsg, space, override, optionp);
ipc_kmsg_send(kmsg, option, send_timeout);
到这里的时候port right拷贝了,消息内容也拷贝了,该直接发送了。把消息发送到dst的消息队列里。
对于发送给内核的消息和非内核的消息分开处理
内核:kmsg = ipc_kobject_server(kmsg, option);
其他:ipc_mqueue_send(&port->ip_messages, kmsg, option, send_timeout);
2.3.3 接收
ipc_mqueue_copyin(space, rcv_name, &mqueue, &object);
Convert a name in a space to a message queue.
根据这个recv_name在space里找到ipc_entry
结构,从而找到其中ipc_object->ipmessage
结构。
mach_msg_rcv_link_special_reply_port(…)
ipc_mqueue_receive(mqueue, option, rcv_size, msg_timeout, THREAD_ABORTSAFE);
Receive a message from a message queue
之前得到了消息队列mqueue
,这个函数就是从这个消息队列中取出消息。ipc_mqueue_receive_on_thread
使用指定thread从消息队列中接收消息。
接受分port set(imq_is_set()
) 和 单个port(imq_is_queue()
),这部分看message queue的结构体也能看出来必须要这么处理。消息队列是一个循环双向链表,取消息的过程就是一个 unlink的过程:
- mach_msg_receive_results
Receive a message, copy out的操作,把之前“解链”的消息拷贝出来。
3. 引用
再谈Mach-IPC
Mac OS X Internals
MOXil
Auditing and Exploiting Apple IPC – Ianbeer
bazad