macOS Thread Injection via Task port

Reading time: 14 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

Code

1. Thread Hijacking

最初に、task_threads() 関数がタスクポートで呼び出され、リモートタスクからスレッドリストを取得します。ハイジャックするためのスレッドが選択されます。このアプローチは、thread_create_running()によって新しいリモートスレッドの作成が禁止されているため、従来のコードインジェクション手法とは異なります。

スレッドを制御するために、thread_suspend() が呼び出され、その実行が停止します。

リモートスレッドで許可される唯一の操作は、停止開始レジスタ値の取得変更です。リモート関数呼び出しは、レジスタ x0 から x7引数を設定し、pc をターゲット関数に設定し、スレッドをアクティブにすることで開始されます。戻り値の後にスレッドがクラッシュしないようにするためには、戻りを検出する必要があります。

1つの戦略は、thread_set_exception_ports()を使用してリモートスレッドのために例外ハンドラを登録することです。関数呼び出しの前に lr レジスタを無効なアドレスに設定します。これにより、関数実行後に例外がトリガーされ、例外ポートにメッセージが送信され、スレッドの状態を検査して戻り値を回復できるようになります。あるいは、Ian Beerのトリプルフェッチエクスプロイトから採用された方法として、lr を無限ループに設定します。スレッドのレジスタは、pc がその命令を指すまで継続的に監視されます。

2. Mach ports for communication

次の段階では、リモートスレッドとの通信を促進するためにMachポートを確立します。これらのポートは、タスク間で任意の送信および受信権を転送するのに重要です。

双方向通信のために、ローカルタスクとリモートタスクの2つのMach受信権が作成されます。その後、各ポートの送信権が対となるタスクに転送され、メッセージの交換が可能になります。

ローカルポートに焦点を当てると、受信権はローカルタスクによって保持されます。ポートは mach_port_allocate() で作成されます。このポートに送信権をリモートタスクに転送することが課題となります。

戦略の1つは、thread_set_special_port()を利用して、リモートスレッドの THREAD_KERNEL_PORT にローカルポートへの送信権を配置することです。その後、リモートスレッドに mach_thread_self() を呼び出して送信権を取得させます。

リモートポートについては、プロセスが基本的に逆になります。リモートスレッドに mach_reply_port() を介してMachポートを生成させます(mach_port_allocate()はその返却メカニズムのため不適切です)。ポートが作成されると、リモートスレッド内で mach_port_insert_right() が呼び出され、送信権が確立されます。この権利はその後、thread_set_special_port()を使用してカーネルに保存されます。ローカルタスクに戻ると、thread_get_special_port()をリモートスレッドで使用して、リモートタスク内の新しく割り当てられたMachポートへの送信権を取得します。

これらのステップを完了すると、Machポートが確立され、双方向通信の基盤が整います。

3. Basic Memory Read/Write Primitives

このセクションでは、基本的なメモリの読み書きプリミティブを確立するために実行プリミティブを利用することに焦点を当てます。これらの初期ステップは、リモートプロセスに対するより多くの制御を得るために重要ですが、この段階でのプリミティブはあまり役に立ちません。すぐに、より高度なバージョンにアップグレードされます。

Memory Reading and Writing Using Execute Primitive

メモリの読み書きを特定の関数を使用して行うことが目標です。メモリを読み取るためには、以下の構造に似た関数が使用されます:

c
uint64_t read_func(uint64_t *address) {
return *address;
}

メモリへの書き込みには、この構造に似た関数が使用されます:

c
void write_func(uint64_t *address, uint64_t value) {
*address = value;
}

これらの関数は、指定されたアセンブリ命令に対応しています:

_read_func:
ldr x0, [x0]
ret
_write_func:
str x1, [x0]
ret

適切な関数の特定

一般的なライブラリのスキャンにより、これらの操作に適した候補が明らかになりました:

  1. メモリの読み取り: property_getName() 関数は、Objective-C ランタイムライブラリからメモリを読み取るための適切な関数として特定されました。関数の概要は以下の通りです:
c
const char *property_getName(objc_property_t prop) {
return prop->name;
}

この関数は、objc_property_tの最初のフィールドを返すことによって、実質的にread_funcのように機能します。

  1. メモリの書き込み: メモリを書き込むための事前構築された関数を見つけることは、より困難です。しかし、libxpcの_xpc_int64_set_value()関数は、以下の逆アセンブルを持つ適切な候補です:
c
__xpc_int64_set_value:
str x1, [x0, #0x18]
ret

特定のアドレスに64ビットの書き込みを行うには、リモートコールは次のように構成されます:

c
_xpc_int64_set_value(address - 0x18, value)

これらのプリミティブが確立されると、共有メモリを作成するためのステージが整い、リモートプロセスの制御において重要な進展が見られます。

4. 共有メモリのセットアップ

目的は、ローカルタスクとリモートタスク間で共有メモリを確立し、データ転送を簡素化し、複数の引数を持つ関数の呼び出しを容易にすることです。このアプローチは、libxpcとそのOS_xpc_shmemオブジェクトタイプを活用し、Machメモリエントリに基づいています。

プロセスの概要:

  1. メモリの割り当て
  • mach_vm_allocate()を使用して共有用のメモリを割り当てます。
  • xpc_shmem_create()を使用して、割り当てたメモリ領域のためのOS_xpc_shmemオブジェクトを作成します。この関数は、Machメモリエントリの作成を管理し、OS_xpc_shmemオブジェクトのオフセット0x18にMach送信権を格納します。
  1. リモートプロセスでの共有メモリの作成
  • リモートプロセスでmalloc()へのリモート呼び出しを使用してOS_xpc_shmemオブジェクトのためのメモリを割り当てます。
  • ローカルのOS_xpc_shmemオブジェクトの内容をリモートプロセスにコピーします。ただし、この初期コピーはオフセット0x18で不正なMachメモリエントリ名を持っています。
  1. Machメモリエントリの修正
  • thread_set_special_port()メソッドを利用して、リモートタスクにMachメモリエントリの送信権を挿入します。
  • リモートメモリエントリの名前でオフセット0x18のMachメモリエントリフィールドを上書きして修正します。
  1. 共有メモリセットアップの最終化
  • リモートのOS_xpc_shmemオブジェクトを検証します。
  • xpc_shmem_remote()へのリモート呼び出しで共有メモリマッピングを確立します。

これらの手順に従うことで、ローカルタスクとリモートタスク間の共有メモリが効率的にセットアップされ、データ転送が簡単になり、複数の引数を必要とする関数の実行が可能になります。

追加のコードスニペット

メモリの割り当てと共有メモリオブジェクトの作成について:

c
mach_vm_allocate();
xpc_shmem_create();

リモートプロセスで共有メモリオブジェクトを作成および修正するには:

c
malloc(); // for allocating memory remotely
thread_set_special_port(); // for inserting send right

Machポートとメモリエントリ名の詳細を正しく処理して、共有メモリのセットアップが適切に機能するようにしてください。

5. 完全な制御の達成

共有メモリを確立し、任意の実行能力を獲得することに成功すると、実質的にターゲットプロセスに対する完全な制御を得たことになります。この制御を可能にする主要な機能は次のとおりです。

  1. 任意のメモリ操作:
  • memcpy()を呼び出して共有領域からデータをコピーすることで、任意のメモリ読み取りを実行します。
  • memcpy()を使用して共有領域にデータを転送することで、任意のメモリ書き込みを実行します。
  1. 複数の引数を持つ関数呼び出しの処理:
  • 8つ以上の引数を必要とする関数の場合、呼び出し規約に従って追加の引数をスタックに配置します。
  1. Machポートの転送:
  • 以前に確立されたポートを介してMachメッセージを通じてタスク間でMachポートを転送します。
  1. ファイルディスクリプタの転送:
  • Ian Beerがtriple_fetchで強調した技術を使用して、ファイルポートを介してプロセス間でファイルディスクリプタを転送します。

この包括的な制御は、threadexecライブラリにカプセル化されており、被害者プロセスとのインタラクションのための詳細な実装とユーザーフレンドリーなAPIを提供します。

重要な考慮事項:

  • システムの安定性とデータの整合性を維持するために、メモリの読み取り/書き込み操作にmemcpy()を適切に使用してください。
  • Machポートやファイルディスクリプタを転送する際は、適切なプロトコルに従い、リソースを責任を持って処理して、リークや意図しないアクセスを防いでください。

これらのガイドラインに従い、threadexecライブラリを利用することで、プロセスを効率的に管理し、細かいレベルでインタラクションを行い、ターゲットプロセスに対する完全な制御を達成できます。

参考文献

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする