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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
Code
- https://github.com/bazad/threadexec
- https://gist.github.com/knightsc/bd6dfeccb02b77eb6409db5601dcef36
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
メモリの読み書きを特定の関数を使用して行うことが目標です。メモリを読み取るためには、以下の構造に似た関数が使用されます:
uint64_t read_func(uint64_t *address) {
return *address;
}
メモリへの書き込みには、この構造に似た関数が使用されます:
void write_func(uint64_t *address, uint64_t value) {
*address = value;
}
これらの関数は、指定されたアセンブリ命令に対応しています:
_read_func:
ldr x0, [x0]
ret
_write_func:
str x1, [x0]
ret
適切な関数の特定
一般的なライブラリのスキャンにより、これらの操作に適した候補が明らかになりました:
- メモリの読み取り:
property_getName()
関数は、Objective-C ランタイムライブラリからメモリを読み取るための適切な関数として特定されました。関数の概要は以下の通りです:
const char *property_getName(objc_property_t prop) {
return prop->name;
}
この関数は、objc_property_t
の最初のフィールドを返すことによって、実質的にread_func
のように機能します。
- メモリの書き込み:
メモリを書き込むための事前構築された関数を見つけることは、より困難です。しかし、libxpcの
_xpc_int64_set_value()
関数は、以下の逆アセンブルを持つ適切な候補です:
__xpc_int64_set_value:
str x1, [x0, #0x18]
ret
特定のアドレスに64ビットの書き込みを行うには、リモートコールは次のように構成されます:
_xpc_int64_set_value(address - 0x18, value)
これらのプリミティブが確立されると、共有メモリを作成するためのステージが整い、リモートプロセスの制御において重要な進展が見られます。
4. 共有メモリのセットアップ
目的は、ローカルタスクとリモートタスク間で共有メモリを確立し、データ転送を簡素化し、複数の引数を持つ関数の呼び出しを容易にすることです。このアプローチは、libxpc
とそのOS_xpc_shmem
オブジェクトタイプを活用し、Machメモリエントリに基づいています。
プロセスの概要:
- メモリの割り当て:
mach_vm_allocate()
を使用して共有用のメモリを割り当てます。xpc_shmem_create()
を使用して、割り当てたメモリ領域のためのOS_xpc_shmem
オブジェクトを作成します。この関数は、Machメモリエントリの作成を管理し、OS_xpc_shmem
オブジェクトのオフセット0x18
にMach送信権を格納します。
- リモートプロセスでの共有メモリの作成:
- リモートプロセスで
malloc()
へのリモート呼び出しを使用してOS_xpc_shmem
オブジェクトのためのメモリを割り当てます。 - ローカルの
OS_xpc_shmem
オブジェクトの内容をリモートプロセスにコピーします。ただし、この初期コピーはオフセット0x18
で不正なMachメモリエントリ名を持っています。
- Machメモリエントリの修正:
thread_set_special_port()
メソッドを利用して、リモートタスクにMachメモリエントリの送信権を挿入します。- リモートメモリエントリの名前でオフセット
0x18
のMachメモリエントリフィールドを上書きして修正します。
- 共有メモリセットアップの最終化:
- リモートの
OS_xpc_shmem
オブジェクトを検証します。 xpc_shmem_remote()
へのリモート呼び出しで共有メモリマッピングを確立します。
これらの手順に従うことで、ローカルタスクとリモートタスク間の共有メモリが効率的にセットアップされ、データ転送が簡単になり、複数の引数を必要とする関数の実行が可能になります。
追加のコードスニペット
メモリの割り当てと共有メモリオブジェクトの作成について:
mach_vm_allocate();
xpc_shmem_create();
リモートプロセスで共有メモリオブジェクトを作成および修正するには:
malloc(); // for allocating memory remotely
thread_set_special_port(); // for inserting send right
Machポートとメモリエントリ名の詳細を正しく処理して、共有メモリのセットアップが適切に機能するようにしてください。
5. 完全な制御の達成
共有メモリを確立し、任意の実行能力を獲得することに成功すると、実質的にターゲットプロセスに対する完全な制御を得たことになります。この制御を可能にする主要な機能は次のとおりです。
- 任意のメモリ操作:
memcpy()
を呼び出して共有領域からデータをコピーすることで、任意のメモリ読み取りを実行します。memcpy()
を使用して共有領域にデータを転送することで、任意のメモリ書き込みを実行します。
- 複数の引数を持つ関数呼び出しの処理:
- 8つ以上の引数を必要とする関数の場合、呼び出し規約に従って追加の引数をスタックに配置します。
- Machポートの転送:
- 以前に確立されたポートを介してMachメッセージを通じてタスク間でMachポートを転送します。
- ファイルディスクリプタの転送:
- 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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。