macOS IPC - Inter Process Communication
Last updated
Last updated
学习并练习AWS Hacking:HackTricks 培训 AWS 红队专家 (ARTE) 学习并练习GCP Hacking: HackTricks 培训 GCP 红队专家 (GRTE)
Mach 使用任务作为共享资源的最小单位,每个任务可以包含多个线程。这些任务和线程与 POSIX 进程和线程是一对一映射的。
任务之间的通信通过 Mach 进程间通信(IPC)进行,利用单向通信通道。消息在端口之间传递,端口类似于由内核管理的消息队列。
端口是 Mach IPC 的基本元素。它可用于发送消息和接收消息。
每个进程都有一个IPC 表,在其中可以找到进程的 mach 端口。mach 端口的名称实际上是一个数字(指向内核对象的指针)。
进程还可以将一个带有一些权限的端口名称发送给另一个任务,内核将使此条目出现在另一个任务的IPC 表中。
端口权限定义了任务可以执行的操作,对于这种通信至关重要。可能的端口权限包括(此处的定义):
接收权限,允许接收发送到端口的消息。Mach 端口是 MPSC(多生产者,单消费者)队列,这意味着整个系统中可能只有一个接收权限与每个端口相关联(与管道不同,在管道的读端可以有多个进程持有文件描述符)。
具有接收权限的任务可以接收消息并创建发送权限,使其能够发送消息。最初,只有拥有任务的接收权限才能拥有其端口的接收权限。
如果拥有接收权限的所有者死亡或终止它,发送权限将变得无效(死命名)。
发送权限,允许向端口发送消息。
发送权限可以克隆,因此拥有发送权限的任务可以克隆权限并将其授予第三个任务。
注意,端口权限也可以通过 Mac 消息传递。
一次性发送权限,允许向端口发送一条消息,然后消失。
此权限无法克隆,但可以移动。
端口集权限,表示一个_端口集_而不是单个端口。从端口集中出列一条消息会从其中一个包含的端口中出列一条消息。端口集可用于同时监听多个端口,类似于 Unix 中的 select
/poll
/epoll
/kqueue
。
死命名,它不是实际的端口权限,而仅是一个占位符。当一个端口被销毁时,所有现有的端口权限将变成死命名。
任务可以将发送权限传输给其他任务,使其能够发送消息回来。发送权限也可以被克隆,因此任务可以复制并将权限授予第三个任务。这与称为引导服务器的中间进程结合使用,允许任务之间进行有效的通信。
文件端口允许在 Mac 端口中封装文件描述符(使用 Mach 端口权限)。可以使用 fileport_makeport
从给定的 FD 创建一个 fileport
,并使用 fileport_makefd
从 fileport
创建一个 FD。
如前所述,可以使用 Mach 消息发送权限,但是,不能在没有发送 Mach 消息的权限的情况下发送权限。那么,如何建立第一次通信呢?
为此,涉及引导服务器(mac 中的 launchd),因为每个人都可以获得发送权限到引导服务器,因此可以要求它为发送消息到另一个进程的权限:
任务 A 创建一个新端口,获得其接收权限。
作为接收权限的持有者,任务 A 为端口生成一个发送权限。
任务 A 与引导服务器建立一个连接,并将其生成的端口的发送权限发送给它。
请记住,任何人都可以获得发送权限到引导服务器。
任务 A 向引导服务器发送一个 bootstrap_register
消息,以将给定的端口与一个名称(如 com.apple.taska
)关联。
任务 B 与引导服务器交互,执行一个引导查找服务名称的操作(bootstrap_lookup
)。因此,引导服务器可以响应,任务 B 将在查找消息中发送一个先前创建的端口的发送权限。如果查找成功,服务器会复制从任务 A 接收的发送权限,并传输给任务 B。
请记住,任何人都可以获得发送权限到引导服务器。
有了这个发送权限,任务 B 就能够向任务 A 发送消息。
对于双向通信,通常任务 B 生成一个具有接收权限和发送权限的新端口,并将发送权限提供给任务 A,以便它可以向任务 B 发送消息(双向通信)。
引导服务器无法对任务声称的服务名称进行身份验证。这意味着一个任务可能潜在地冒充任何系统任务,例如虚假声明授权服务名称,然后批准每个请求。
然后,Apple 将系统提供的服务名称存储在安全配置文件中,位于SIP 保护目录中:/System/Library/LaunchDaemons
和 /System/Library/LaunchAgents
。在每个服务名称旁边,还存储了关联的二进制文件。引导服务器将为这些服务名称创建并持有接收权限。
对于这些预定义服务,查找过程略有不同。当查找服务名称时,launchd 动态启动服务。新的工作流程如下:
任务 B 启动一个引导查找服务名称。
launchd 检查任务是否正在运行,如果没有,则启动它。
任务 A(服务)执行一个引导签到(bootstrap_check_in()
)。在这里,引导服务器创建一个发送权限,保留它,并将接收权限传输给任务 A。
launchd 复制发送权限并将其发送给任务 B。
任务 B 生成一个具有接收权限和发送权限的新端口,并将发送权限提供给任务 A(服务),以便它可以向任务 B 发送消息(双向通信)。
然而,此过程仅适用于预定义的系统任务。非系统任务仍按最初描述的方式运行,这可能导致潜在的冒充。
因此,launchd 不应该崩溃,否则整个系统将崩溃。
mach_msg
函数,本质上是一个系统调用,用于发送和接收 Mach 消息。该函数要求将消息作为初始参数发送。该消息必须以 mach_msg_header_t
结构开头,后跟实际的消息内容。该结构定义如下:
进程拥有 接收权限 可以在 Mach 端口上接收消息。相反,发送者 被授予 发送权限 或 一次性发送权限。一次性发送权限专门用于发送一条消息,之后将变为无效。
初始字段 msgh_bits
是一个位图:
第一个位(最重要的)用于指示消息是否复杂(下文详细介绍)
第 3 和第 4 位由内核使用
第 2 字节的 最不重要的 5 位 可用于 凭证:另一种发送键/值组合的端口类型。
第 3 字节的 最不重要的 5 位 可用于 本地端口
第 4 字节的 最不重要的 5 位 可用于 远程端口
可以在凭证、本地端口和远程端口中指定的类型为(来自 mach/message.h):
例如,MACH_MSG_TYPE_MAKE_SEND_ONCE
可用于指示应为此端口派生并传输一次性发送权。也可以指定 MACH_PORT_NULL
以防止接收方能够回复。
为了实现简单的双向通信,进程可以在名为 reply port(msgh_local_port
)的 mach 消息头中指定一个mach端口,接收方可以向该消息发送回复。
请注意,这种双向通信在期望回复的 XPC 消息中使用(xpc_connection_send_message_with_reply
和 xpc_connection_send_message_with_reply_sync
)。但通常会创建不同的端口,如前面解释的,以创建双向通信。
消息头的其他字段包括:
msgh_size
:整个数据包的大小。
msgh_remote_port
:发送此消息的端口。
msgh_voucher_port
:mach 优惠券。
msgh_id
:此消息的 ID,由接收方解释。
请注意,mach 消息通过 mach 端口
发送,这是内置于 mach 内核中的单接收方、多发送方通信通道。多个进程可以向 mach 端口发送消息,但在任何时刻只有一个进程可以从中读取。
然后,消息由**mach_msg_header_t
头部、主体和尾部**(如果有)组成,并且可以授予回复权限。在这些情况下,内核只需将消息从一个任务传递到另一个任务。
尾部是由内核添加到消息中的信息(用户无法设置),可以在消息接收时使用标志 MACH_RCV_TRAILER_<trailer_opt>
请求(可以请求不同的信息)。
然而,还有其他更复杂的消息,比如传递附加端口权限或共享内存的消息,内核还需要将这些对象发送给接收方。在这种情况下,头部 msgh_bits
的最高位被设置。
可传递的描述符在 mach/message.h
中定义:
请注意,端口与任务命名空间相关联,因此要创建或搜索端口,还需要查询任务命名空间(更多信息请参见 mach/mach_port.h
):
mach_port_allocate
| mach_port_construct
:创建一个端口。
mach_port_allocate
还可以创建一个端口集:接收一组端口的接收权。每当接收到消息时,都会指示消息来自哪个端口。
mach_port_allocate_name
:更改端口的名称(默认为32位整数)。
mach_port_names
:从目标获取端口名称。
mach_port_type
:获取任务对名称的权限。
mach_port_rename
:重命名端口(类似于 FD 的 dup2)。
mach_port_allocate
:分配新的接收、端口集或 DEAD_NAME。
mach_port_insert_right
:在具有接收权的端口中创建新的权限。
mach_port_...
mach_msg
| mach_msg_overwrite
:用于发送和接收 mach 消息的函数。覆盖版本允许指定不同的缓冲区用于消息接收(另一个版本将仅重用它)。
由于函数**mach_msg
和mach_msg_overwrite
**是用于发送和接收消息的函数,设置在它们上的断点将允许检查发送和接收的消息。
例如,开始调试任何可以调试的应用程序,因为它将加载**libSystem.B
,该库将使用此函数**。
要获取**mach_msg
**的参数,请检查寄存器。这些是参数(来自 mach/message.h):
从注册表中获取值:
检查消息头,检查第一个参数:
那种 mach_msg_bits_t
类型非常常见,用于允许回复。
名称 是给定端口的默认名称(检查前3个字节如何增加)。 ipc-object
是端口的混淆唯一标识符。
还要注意,只有**send
权限的端口如何标识其所有者**(端口名称 + pid)。
还要注意使用**+
来指示连接到同一端口的其他任务**。
还可以使用procesxp来查看还有注册的服务名称(由于需要com.apple.system-task-port
,因此已禁用SIP):
您可以从http://newosxbook.com/tools/binpack64-256.tar.gz下载iOS上的工具进行安装。
请注意发送方如何分配一个端口,为名称org.darlinghq.example
创建一个发送权限,并将其发送到引导服务器,同时发送方请求该名称的发送权限并使用它来发送消息。
在这个示例中,我们创建了一个名为`sender`的程序,它将发送消息到接收者进程。首先,我们使用`msgget`函数获取一个消息队列的标识符。然后,我们填充一个包含消息类型和消息数据的结构体,并使用`msgsnd`函数将消息发送到消息队列中。最后,我们使用`msgctl`函数删除消息队列。
有一些特殊端口允许在具有对其发送权限的任务中执行某些敏感操作或访问某些敏感数据。这使得这些端口从攻击者的角度非常有趣,不仅因为其功能,还因为可以在任务之间共享发送权限。
这些端口由一个数字表示。
通过调用**host_get_special_port
获取发送权限,调用host_set_special_port
获取接收权限。然而,这两个调用都需要host_priv
端口,只有 root 可以访问。此外,在过去,root 可以调用host_set_special_port
**并劫持任意端口,例如通过劫持 HOST_KEXTD_PORT
来绕过代码签名(SIP 现在阻止了这一点)。
这些端口分为 2 组:前 7 个端口由内核拥有,其中 1 是 HOST_PORT
,2 是 HOST_PRIV_PORT
,3 是 HOST_IO_MASTER_PORT
,7 是 HOST_MAX_SPECIAL_KERNEL_PORT
。
从数字 8开始的端口由系统守护程序拥有,可以在 host_special_ports.h
中找到声明。
主机端口:如果一个进程对此端口具有发送权限,他可以通过调用其例程获取有关系统的信息,例如:
host_processor_info
:获取处理器信息
host_info
:获取主机信息
host_virtual_physical_table_info
:虚拟/物理页表(需要 MACH_VMDEBUG)
host_statistics
:获取主机统计信息
mach_memory_info
:获取内核内存布局
主机特权端口:具有此端口的发送权限的进程可以执行特权操作,例如显示引导数据或尝试加载内核扩展。进程需要是 root才能获得此权限。
此外,为了调用**kext_request
** API,需要具有其他授权**com.apple.private.kext*
**,这些授权仅提供给 Apple 二进制文件。
可以调用的其他例程包括:
host_get_boot_info
:获取 machine_boot_info()
host_priv_statistics
:获取特权统计信息
vm_allocate_cpm
:分配连续物理内存
host_processors
:向主机处理器发送权限
mach_vm_wire
:使内存常驻
由于root可以访问此权限,因此可以调用host_set_[special/exception]_port[s]
来劫持主机特殊或异常端口。
可以通过运行以下命令查看所有主机特殊端口:
这些端口是为众所周知的服务保留的。可以通过调用 task_[get/set]_special_port
来获取/设置它们。它们可以在 task_special_ports.h
中找到:
从这里:
TASK_KERNEL_PORT[任务自身发送权限]: 用于控制此任务的端口。用于发送影响任务的消息。这是由**mach_task_self(参见下面的任务端口)**返回的端口。
TASK_BOOTSTRAP_PORT[引导发送权限]: 任务的引导端口。用于发送请求返回其他系统服务端口。
TASK_HOST_NAME_PORT[主机自身发送权限]: 用于请求包含主机的信息的端口。这是由mach_host_self返回的端口。
TASK_WIRED_LEDGER_PORT[分类帐发送权限]: 命名源,此任务从中获取其有线内核内存。
TASK_PAGED_LEDGER_PORT[分类帐发送权限]: 命名源,此任务从中获取其默认内存管理内存。
最初,Mach没有“进程”,而是有“任务”,被认为更像是线程的容器。当Mach与BSD合并时,每个任务与一个BSD进程相关联。因此,每个BSD进程都具有成为进程所需的详细信息,每个Mach任务也具有其内部工作方式(除了不存在的pid 0,即kernel_task
)。
有两个与此相关的非常有趣的函数:
task_for_pid(target_task_port, pid, &task_port_of_pid)
: 获取与指定pid
相关的任务的任务端口的SEND权限,并将其提供给指定的target_task_port
(通常是使用mach_task_self()
的调用方任务,但也可以是不同任务上的SEND端口。)
pid_for_task(task, &pid)
: 给定对任务的SEND权限,找到此任务相关的PID。
为了在任务内执行操作,任务需要对自身调用mach_task_self()
(使用task_self_trap
(28))获得一个SEND
权限。有了这个权限,任务可以执行多个操作,如:
task_threads
: 获取所有任务线程的SEND权限
task_info
: 获取有关任务的信息
task_suspend/resume
: 暂停或恢复任务
task_[get/set]_special_port
thread_create
: 创建一个线程
task_[get/set]_state
: 控制任务状态
还可以在mach/task.h中找到更多信息
请注意,通过对不同任务的任务端口具有SEND权限,可以在不同任务上执行此类操作。
此外,任务端口也是**vm_map
端口,允许使用vm_read()
和vm_write()
等函数读取和操作任务内存。这基本上意味着具有对不同任务的任务端口的SEND权限的任务将能够向该任务注入代码**。
请记住,因为内核也是一个任务,如果有人设法获得对**kernel_task
的SEND权限**,它将能够使内核执行任何操作(越狱)。
调用mach_task_self()
以获取调用方任务的此端口的名称。此端口仅在**exec()
跨越时继承**;使用fork()
创建的新任务会获得一个新任务端口(作为特例,在suid二进制文件中的exec()
后,任务也会获得一个新任务端口)。生成任务并获取其端口的唯一方法是在执行fork()
时执行"端口交换舞蹈"。
这些是访问端口的限制(来自二进制文件AppleMobileFileIntegrity
的macos_task_policy
):
如果应用程序具有**com.apple.security.get-task-allow
授权**,来自相同用户的进程可以访问任务端口(通常由Xcode添加以进行调试)。未经认证的进程不允许将其用于生产发布。
具有**com.apple.system-task-ports
授权的应用程序可以获取任何进程的任务端口,但不能获取内核的任务端口。在旧版本中,它称为task_for_pid-allow
**。这仅授予给Apple应用程序。
Root可以访问未使用强化运行时编译的应用程序的任务端口(不是来自Apple)。
任务名称端口:_任务端口_的非特权版本。它引用任务,但不允许控制它。似乎唯一可通过它获得的是task_info()
。
线程也有关联的端口,可以从调用**task_threads
**的任务和使用processor_set_threads
的处理器中看到。对线程端口的SEND权限允许使用thread_act
子系统的功能,如:
thread_terminate
thread_[get/set]_state
act_[get/set]_state
thread_[suspend/resume]
thread_info
...
任何线程都可以通过调用**mach_thread_sef
**获取此端口。
您可以从以下位置获取shellcode:
Introduction to ARM64v8entitlements.plist is a property list file that contains a dictionary of entitlements granted to a macOS application. These entitlements define the capabilities and permissions that the application has on the system. By modifying the entitlements.plist file, an attacker can potentially escalate privileges or bypass security restrictions imposed on the application.
Example entitlements.plist file:
In this example, the entitlements.plist file grants the application permission to get task allow and disable library validation.
编译前面的程序并添加权限以能够使用相同用户注入代码(如果不是,则需要使用sudo)。
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)