macOS IPC - Inter Process Communication
Last updated
Last updated
AWSハッキングの学習と実践:HackTricks Training AWS Red Team Expert (ARTE) GCPハッキングの学習と実践:HackTricks Training GCP Red Team Expert (GRTE)
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
を使用してfileportからFDを作成することができます。
前述のように、Machメッセージを使用して権限を送信することが可能ですが、Machメッセージを送信する権限がない場合は権限を送信することはできません。では、最初の通信はどのように確立されるのでしょうか?
このために、ブートストラップサーバ(macではlaunchd)が関与します。誰でもブートストラップサーバにSEND権限を取得できるため、他のプロセスにメッセージを送信する権限を要求することができます:
タスクAは新しいポートを作成し、その上に受信権限を取得します。
受信権限の所有者であるタスクAは、ポートのためにSEND権限を生成します。
タスクAはブートストラップサーバと接続を確立し、最初に生成したポートのSEND権限を送信します。
誰でもブートストラップサーバにSEND権限を取得できることを覚えておいてください。
タスクAはbootstrap_register
メッセージをブートストラップサーバに送信して、com.apple.taska
のような名前で指定されたポートを関連付けします。
タスクBは、サービス名(bootstrap_lookup
)のためにブートストラップサーバと対話します。ブートストラップサーバが応答するために、タスクBはルックアップメッセージ内で以前に作成したポートに対するSEND権限を送信します。ルックアップが成功すると、サーバはタスクAから受け取ったSEND権限をタスクBに複製し、タスクBに送信します。
誰でもブートストラップサーバにSEND権限を取得できることを覚えておいてください。
このSEND権限を使用して、タスクBはタスクAにメッセージを送信することができます。
双方向通信のために通常、タスクBは受信権限と送信権限を持つ新しいポートを生成し、SEND権限をタスクAに渡すことで、タスクBにメッセージを送信できるようにします(双方向通信)。
ブートストラップサーバはサービス名を認証できません。これは、タスクが潜在的にシステムタスクをなりすます可能性があることを意味します。たとえば、認証サービス名を偽装して承認リクエストを承認することができます。
その後、Appleはシステム提供サービスの名前を、安全な構成ファイルに格納しています。これらのファイルは、SIPで保護されたディレクトリにあります:/System/Library/LaunchDaemons
および/System/Library/LaunchAgents
。各サービス名には、関連するバイナリも格納されています。ブートストラップサーバは、これらのサービス名ごとに受信権限を作成し、保持します。
これらの事前定義されたサービスについては、ルックアッププロセスがわずかに異なります。サービス名が検索されると、launchdはサービスを動的に開始します。新しいワークフローは次のとおりです:
タスクBは、サービス名のためにブートストラップルックアップを開始します。
launchdは、タスクが実行されているかどうかをチェックし、実行されていない場合は開始します。
タスクA(サービス)はブートストラップチェックイン(bootstrap_check_in()
)を実行します。ここで、ブートストラップサーバはSEND権限を作成し、保持し、受信権限をタスクAに転送します。
launchdはSEND権限を複製し、タスクBに送信します。
タスクBは受信権限と送信権限を持つ新しいポートを生成し、タスクA(svc)にSEND権限を渡します(双方向通信)。
ただし、このプロセスは事前定義されたシステムタスクにのみ適用されます。非システムタスクは引き続き最初に説明したように動作し、なりすましを許可する可能性があります。
したがって、launchdがクラッシュするとシステム全体がクラッシュする可能性があります。
mach_msg
関数は、基本的にシステムコールであり、Machメッセージの送受信に使用されます。この関数は、送信するメッセージを最初の引数として必要とします。このメッセージは、mach_msg_header_t
構造体で始まり、実際のメッセージ内容が続きます。この構造体は以下のように定義されています:
プロセスは 受信権 を持っていると、Mach ポートでメッセージを受信できます。逆に、送信者 は 送信権 または 一度だけ送信権 を付与されます。一度だけ送信権は、1回のメッセージ送信にのみ使用され、その後に無効になります。
初期フィールド msgh_bits
はビットマップです:
最初のビット(最も重要なビット)は、メッセージが複雑であることを示すために使用されます(後述)
3番目と4番目はカーネルによって使用されます
2番目のバイトの最も下位の5ビット は バウチャー に使用できます: キー/値の組み合わせを送信するための別のポートの種類。
3番目のバイトの最も下位の5ビット は ローカルポート に使用できます
4番目のバイトの最も下位の5ビット は リモートポート に使用できます
バウチャー、ローカルポート、リモートポートに指定できるタイプは、mach/message.h から次の通りです:
たとえば、MACH_MSG_TYPE_MAKE_SEND_ONCE
は、このポートに対して派生および転送されるsend-once rightを示すために使用できます。また、MACH_PORT_NULL
を指定して、受信者が返信できないようにすることもできます。
簡単な双方向通信を実現するために、プロセスは、メッセージの受信者がこのメッセージに返信を送信できるように、mach メッセージヘッダー内のmach portを指定できます。これを reply port (msgh_local_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つのタスクから他のタスクに渡す必要があります。
トレーラーは、カーネルによってメッセージに追加される情報であり(ユーザーによって設定することはできません)、メッセージ受信時にフラグMACH_RCV_TRAILER_<trailer_opt>
でリクエストできます(リクエストできる異なる情報があります)。
ただし、追加のポート権限を渡すか、メモリを共有するなど、より複雑なメッセージもあります。この場合、カーネルはこれらのオブジェクトを受信者に送信する必要があります。この場合、ヘッダーmsgh_bits
の最上位ビットが設定されます。
渡す可能性のある記述子は、mach/message.h
で定義されています。
ポートはタスクの名前空間に関連付けられているため、ポートを作成または検索するには、タスクの名前空間もクエリされます(詳細は 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_msg_overwrite
はメッセージを送受信するために使用される関数なので、これらにブレークポイントを設定すると、送信されたメッセージと受信されたメッセージを検査できます。
たとえば、デバッグできるアプリケーションをデバッグ開始すると、この関数を使用する libSystem.B
が読み込まれます。
mach_msg
の引数を取得するには、レジスタを確認します。これらは引数です(mach/message.hから):
レジストリから値を取得します:
その種類の mach_msg_bits_t
は、返信を許可するために非常に一般的です。
name はポートにデフォルトで与えられる名前です(最初の3バイトでどのように増加しているかを確認してください)。 ipc-object
はポートの難読化された一意の識別子です。
また、send
権限のみを持つポートが所有者を識別していることに注目してください(ポート名 + pid)。
また、同じポートに接続された他のタスクを示すために +
の使用にも注目してください。
また、procesxp を使用して、SIPが無効になっているために com.apple.system-task-port
の必要性により、登録されたサービス名 も表示できます:
iOS でこのツールをインストールするには、http://newosxbook.com/tools/binpack64-256.tar.gz からダウンロードしてください。
sender がポートを割り当て、名前 org.darlinghq.example
の send right を作成し、それを ブートストラップサーバー に送信する方法に注目してください。送信者はその名前の send right を要求し、それを使用して メッセージを送信 しました。
Inter-Process Communication (IPC) mechanisms are commonly used in macOS for processes to communicate with each other. This can introduce security risks if not implemented correctly.
Shared Memory: Processes can share memory segments to communicate with each other. Care must be taken to prevent unauthorized access to sensitive data.
Mach Messages: Mach is the underlying IPC mechanism in macOS. Messages can be sent between tasks using Mach ports. Secure coding practices should be followed to prevent message interception.
XPC Services: XPC services allow processes to communicate with each other securely. Developers should validate input and implement proper authentication mechanisms.
Always validate input received through IPC mechanisms.
Implement proper authentication and authorization checks.
Encrypt sensitive data before sending it over IPC.
Regularly audit IPC usage for potential security vulnerabilities.
By following secure coding practices and implementing proper security measures, developers can mitigate the risks associated with IPC in macOS.
特定の機密操作を実行したり、特定の機密データにアクセスしたりすることができる特別なポートがいくつかあります。これらのポートは、それらに対して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
で宣言されています。
ホストポート: このポートに対して SEND 権限を持つプロセスは、次のようなルーチンを呼び出すことで システムに関する情報を取得できます:
host_processor_info
: プロセッサ情報を取得
host_info
: ホスト情報を取得
host_virtual_physical_table_info
: 仮想/物理ページテーブル(MACH_VMDEBUG が必要)
host_statistics
: ホスト統計情報を取得
mach_memory_info
: カーネルメモリレイアウトを取得
ホスト特権ポート: このポートに対して 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
: ホストプロセッサに対する送信権限
mach_vm_wire
: メモリを常駐させる
root はこの権限にアクセスできるため、host_set_[special/exception]_port[s]
を呼び出して ホストの特別ポートや例外ポートを乗っ取ることができます。
すべてのホスト特別ポートを表示することができます。
これらはよく知られたサービスのために予約されたポートです。task_[get/set]_special_port
を呼び出すことで取得/設定することができます。これらはtask_special_ports.h
に見つけることができます。
こちらから:
TASK_KERNEL_PORT[task-self send right]: このタスクを制御するために使用されるポート。タスクに影響を与えるメッセージを送信するために使用されます。これは**mach_task_self (下記のTask Portsを参照)**によって返されるポートです。
TASK_BOOTSTRAP_PORT[bootstrap send right]: タスクのブートストラップポート。他のシステムサービスポートの返却を要求するメッセージを送信するために使用されます。
TASK_HOST_NAME_PORT[host-self send right]: 含まれるホストの情報を要求するために使用されるポート。これはmach_host_selfによって返されるポートです。
TASK_WIRED_LEDGER_PORT[ledger send right]: このタスクが有線カーネルメモリを取得する元を示すポート。
TASK_PAGED_LEDGER_PORT[ledger send right]: このタスクがデフォルトのメモリ管理メモリを取得する元を示すポート。
元々Machには「プロセス」ではなく「タスク」があり、これはスレッドのコンテナのように考えられていました。MachがBSDと統合された際、各タスクはBSDプロセスと関連付けられました。したがって、すべてのBSDプロセスにはプロセスであるために必要な詳細があり、すべてのMachタスクにも内部動作があります(kernel_task
である存在しないpid 0を除く)。
これに関連する非常に興味深い関数が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を見つけます。
タスク内でアクションを実行するために、タスクはmach_task_self()
を呼び出して自身に対するSEND
権を必要としました(これは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"を実行することです。
これらはポートへのアクセス制限です(バイナリAppleMobileFileIntegrity
のmacos_task_policy
から):
アプリが**com.apple.security.get-task-allow
権限**を持っている場合、同じユーザーのプロセスがタスクポートにアクセスできます(デバッグ用にXcodeによって一般的に追加されます)。ノータリゼーションプロセスはこれを本番リリースには許可しません。
com.apple.system-task-ports
権限を持つアプリは、カーネルを除く任意のプロセスのタスクポートを取得できます。以前のバージョンでは**task_for_pid-allow
**と呼ばれていました。これはAppleアプリケーションにのみ付与されます。
Rootは、ハード化されていないランタイムでコンパイルされたアプリケーションのタスクポートにアクセスできます(Apple製品からは除く)。
タスク名ポート: _タスクポート_の権限がないバージョン。タスクを参照しますが、制御することはできません。これを介して利用可能な唯一のものはtask_info()
のようです。
スレッドにも関連付けられたポートがあり、**task_threads
**を呼び出すタスクとprocessor_set_threads
からプロセッサが見えます。スレッドポートへのSEND権を持つと、thread_act
サブシステムの関数を使用できます:
thread_terminate
thread_[get/set]_state
act_[get/set]_state
thread_[suspend/resume]
thread_info
...
どのスレッドも、**mach_thread_sef
**に呼び出すことでこのポートを取得できます。
シェルコードを取得できます:
Introduction to ARM64v8エンタイトルメント.plist
前のプログラムをコンパイルし、同じユーザーでコードをインジェクトできるように権限を追加します(そうでない場合はsudoを使用する必要があります)。
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)