Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
ツール/GitHubGitHub/bazad/blanket
特権昇格iOSセキュリティ脆弱性分析エクスプロイトポストエクスプロイトモバイルセキュリティバイナリエクスプロイト
GitHubbazad/blanket

blanket

CVE-2018-4280: iOS 11.2.6上のlaunchdにおけるMachポート置換の脆弱性で、sandbox escape、privilege escalation、codesigning bypassに至る。

リポジトリを見る
26043457年前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

blanket

Blanket は iOS 11.2.6 を標的としたサンドボックス脱出ツールであり、主要な脆弱性は iOS 11.4.1 でのみ修正されました。このツールは launchd の Mach ポート置換の脆弱性 (CVE-2018-4280) と、その他のサービスにおけるいくつかの小さな脆弱性を悪用し、サンドボックス化されておらず、root で動作し、task_for_pid-allow エンタイトルメントを持つ ReportCrash プロセス内でコードを実行します。これにより、Blanket は amfid のようなセキュリティ上重要なプロセスを含む、電話機上のすべてのプロセスを制御できるようになります。

エクスプロイトは複数のステージで構成されています。この README では、主要な脆弱性とサンドボックス脱出の各ステージを段階的に説明します。

システムサービスのなりすまし

iOS のクラッシュレポートを調査中に、launchd に Mach ポート置換の脆弱性を発見しました。特定の方法でクラッシュすることにより、プロセスはカーネルに Mach メッセージを launchd へ送信させ、そのメッセージによって launchd が自身の IPC 名前空間内の Mach ポートへの送信権を過剰に解放してしまいます。これにより、攻撃者はシステムの他の部分に対して、自身が検索可能な任意の launchd サービスになりすますことができ、権限昇格への多くの経路が開かれます。

この脆弱性は macOS にも存在しますが、iOS で脆弱性を引き起こすのはより困難です。launchd には Mach 例外メッセージがカーネルから送信されたものであることを確認するチェックがあるためです。

CVE-2018-4280: EXC_CRASH 例外メッセージ処理時の launchd Mach ポート過剰解放

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 で逆コンパイルし、読みやすく軽く編集したものです。```C 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 exception_type_t exception, // thread and task ports could mach_exception_data_t code, // be arbitrary send rights. mach_msg_type_number_t codeCnt) { __int64 __stack_guard; // ST28_8@1 kern_return_t kr; // w0@1 MAPDST kern_return_t result; // w0@4 __int64 codes_left; // x25@6 mach_exception_data_type_t code_value; // t1@7 int pid; // [xsp+34h] [xbp-44Ch]@1 char codes_str[1024]; // [xsp+38h] [xbp-448h]@7

root@kitploit:~
__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 ( current_audit_token.val[5] )                   // (b) If the message was sent by
{                                                   //     a process with a nonzero PID
    result = KERN_FAILURE;                          //     (any non-kernel process),
}                                                   //     the message is rejected.
else
{
    if ( codeCnt )
    {
        codes_left = codeCnt;
        do
        {
            code_value = *code;
            ++code;
            __snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", code_value);
            --codes_left;
        }
        while ( codes_left );
    }
    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_port(thread);                   // (c) The "thread" port sent in
    if ( kr )                                       //     the message is deallocated.
    {
        _os_assumes_log(kr);
        _os_avoid_tail_call();
    }
    kr = deallocate_port(task);                     // (d) The "task" port sent in the
    if ( kr )                                       //     message is deallocated.
    {
        _os_assumes_log(kr);
        _os_avoid_tail_call();
    }
    if ( exception == EXC_CRASH )                   // (e) If the exception type is
        result = KERN_FAILURE;                      //     EXC_CRASH, then KERN_FAILURE
    else                                            //     is returned. MIG will
        result = 0;                                 //     deallocate the ports again.
}
*__stack_chk_guard_ptr;
return result;

}

root@kitploit:~
コードが行う処理は以下の通りです。

1. この関数は、`mach_exception_raise` 例外メッセージのための Mach サービルーチンです。launchd が `mach_exception_raise` Mach 例外メッセージを処理する際に、Mach システムによって直接呼び出されます。サービルーチンの引数は Mach メッセージから解析され、したがってメッセージの送信者によって制御されます。
2. (b) で、launchd は Mach 例外メッセージがカーネルによって送信されたことを確認します。送信者の監査トークンには、フィールド 5 に送信プロセスの PID が含まれており、カーネルの場合のみゼロになります。メッセージがカーネルによって送信されていない場合は拒否されます。
3. メッセージからのスレッドポートとタスクポートは、(c) と (d) で明示的に解放されます。
4. (e) で、launchd は例外タイプが `EXC_CRASH` であるかどうかを確認し、そうであれば `KERN_FAILURE` を返します。その意図は `EXC_CRASH` メッセージを処理しないようにすることであり、おそらく ReportCrash が遺体ハンドラとして呼び出されるようにするためです。しかし、この時点で `KERN_FAILURE` を返すと、後で例外メッセージがクリーンアップされる際にタスクポートとスレッドポートが再度解放されることになります。つまり、これら2つのポートは過剰に解放されることになります。

この脆弱性を有用にするためには、launchd が提供する Mach サービスへの送信権を解放し、それによってシステムの他の部分に対してそのサービスを偽装できるようにしたいと考えます。つまり、例外メッセージ内のタスクポートとスレッドポートが、実際に launchd 内で解放したい Mach サービスポートへの送信権である必要があります。そして、launchd に悪意のある例外メッセージを送信してサービスポートを解放した後、同じポート名が再利用されるようにしますが、今度は自分たちが受信権を持つ Mach ポートに対して再利用されるようにします。そうすれば、クライアントが launchd にサービスの Mach ポートへの送信権を要求したときに、launchd は代わりに自分たちのポートへの送信権を渡し、そのサービスをクライアントに対して偽装できるようになります。その後、システム特権を獲得するためのさまざまな方法があります。

### 脆弱性の引き金

実際に脆弱性を引き起こすには、メッセージがカーネルによって送信されたというチェックをバイパスする必要があります。なぜなら、例外メッセージを直接 launchd に送信しても破棄されるだけだからです。何とかして、カーネルに、実際のスレッドポートとタスクポートの代わりにシステムサービス用の Mach 送信権を含む「悪意のある」例外メッセージを送信させる必要があります。

実際には、`task_set_special_port` という Mach トラップがあり、特定の状況で真のタスクポートの代わりに使用されるカスタム送信権を設定するために使用できます。この状況の1つは、カーネルがタスクに代わって例外メッセージを生成するときです。カーネルは、例外メッセージに真のタスク送信権を配置する代わりに、`task_set_special_port` によって供給された送信権を使用します。具体的には、タスクが `task_set_special_port` を呼び出して、その `TASK_KERNEL_PORT` 特殊ポートにカスタム値を設定し、その後タスクがクラッシュすると、カーネルによって生成された例外メッセージの "task" フィールドには、真のタスクポートではなく、カスタムポートへの送信権が含まれます。同様の API `thread_set_special_port` を使用して、生成された例外メッセージの "thread" フィールドにカスタムポートを設定できます。

この動作により、カーネルにタスクポートとスレッドポートの代わりに Mach サービスポートを含む「悪意のある」例外メッセージを生成させることは実際には全く難しくありません。ただし、生成した例外メッセージが確実に launchd に配信されるようにする必要があります。

繰り返しになりますが、適切な API を知っていれば、カーネルに「悪意のある」例外メッセージを launchd に配信させることは難しくありません。`thread_set_exception_ports` 関数は、このスレッド上の例外メッセージが配信されるポートとして任意の Mach 送信権を設定します。したがって、`thread_set_exception_ports` をブートストラップポートで呼び出すだけで、生成した任意の例外によってカーネルが launchd に例外メッセージを送信するようになります。

パズルの最後のピースは、正しい例外タイプを得ることです。この脆弱性は `EXC_CRASH` 例外に対してのみ引き起こされます。少し試行錯誤すれば、標準の `abort` 関数を呼び出すことで `EXC_CRASH` 例外を簡単に生成できることがわかります。

したがって、要約すると、既存の文書化された API を使用して、カーネルに代わりに悪意のある `EXC_CRASH` 例外メッセージを生成させ、それを launchd に配信させることで、脆弱性を引き起こし、Mach サービスポートを解放できます。

1. `thread_set_exception_ports` を使用して、このスレッドの例外ハンドラとして launchd を設定します。
2. `bootstrap_look_up` を呼び出して、偽装したいサービスのサービスポートを launchd から取得します。
3. `task_set_special_port`/`thread_set_special_port` を呼び出して、例外メッセージ内の真のタスクポートとスレッドポートの代わりにそのサービスポートを使用します。
4. `abort` を呼び出します。カーネルは `EXC_CRASH` 例外メッセージを launchd に送信しますが、メッセージ内のタスクポートとスレッドポートはターゲットのサービスポートになります。
5. Launchd が例外メッセージを処理し、サービスポートを解放します。

### クラッシュ後のコード実行

上記の戦略には問題があります。`abort` を呼び出すと、私たちのプロセスが強制終了されます。脆弱性を引き起こした後でもコードを実行できるようにするには、別のプロセスでクラッシュを実行する方法が必要です。

(他の例外タイプでは、プロセスは実際に例外から回復できます。プロセスが回復する方法は、スレッド例外ハンドラを launchd に設定し、タスク例外ハンドラを自分自身に設定することです。launchd が例外を処理し、処理に失敗した後、カーネルは例外をタスクハンドラに送信し、タスクハンドラがスレッド状態をリセットして、例外が処理されたことをカーネルに通知します。ただし、プロセスは自分自身の `EXC_CRASH` 例外をキャッチできないため、確かに2つのプロセスが必要です。)

1つの戦略は、まず iOS 上の別のプロセスの脆弱性を悪用し、そのプロセスにカーネルポートを設定させてクラッシュさせることです。ただし、概念実証としては、アプリ拡張機能を作成する方が簡単です。

iOS 8 で導入されたアプリ拡張機能は、アプリケーションの機能の一部をパッケージ化して、アプリケーションの外部でも利用できるようにする方法を提供します。アプリ拡張機能のコードは、別のサンドボックス化されたプロセスで実行されます。これにより、特殊ポートを設定し、`EXC_CRASH` の例外ハンドラとして launchd を登録し、`abort` を呼び出すプロセスを非常に簡単に起動できます。

アプリがプログラム的に自身のアプリ拡張機能を起動して通信するためのサポートされた方法はありません。しかし、Ian McDowell 氏は、プライベートな `NSExtension` API を使用してアプリ拡張機能プロセスを起動し通信する方法を説明した [素晴らしい記事][Multi-Process iOS App Using NSExtension] を書いています。ここではほぼ同じ戦略を使用しています。唯一の違いは、Mach ポートをアプリ拡張機能プロセスに通信する必要がある点で、これには launchd にダミーサービスを登録し、アプリ拡張機能がそれに接続するというステップが含まれます。

[Multi-Process iOS App Using NSExtension]: https://ianmcdowell.net/blog/nsextension/

### launchd でのポート再利用の防止

説明どおりにエクスプロイトを実行すると、解放されたポートを再取得できない場合があることに気付くでしょう。その理由は、カーネルがプロセスの空き IPC エントリをフリーリストで管理しており、新しくポートが IPC テーブルに割り当てられると、解放されたばかりのポート名が(異なる世代番号で)再利用されるからです。したがって、launchd がその IPC エントリスロットを他のポートに再利用しない場合にのみ、目的のポート名を再割り当てできます。

これを回避する方法は、空き IPC エントリスロットをフリーリストの奥深くに埋めることです。つまり、launchd が新しいポートを割り当てる場合、他のスロットが最初に使用されるようにします。どうやってこれを行うのでしょうか? launchd に、自分たちが受信権を持つポートを使用したダミーの Mach サービスを多数登録します。`abort` を呼び出すと、最初に例外ハンドラが起動し、その後 Mach ポートを含むプロセス状態がクリーンアップされます。launchd が `EXC_CRASH` 例外を受信すると、不注意にターゲットのサービスポートを解放し、そのポート名に対応する IPC エントリスロットをフリーリストの先頭に置きます。その後、アプリ拡張機能の残りの Mach ポートが破棄されると、launchd は通知を受信し、ダミーのサービスポートを解放し、ターゲットの IPC エントリスロットを解放されたばかりのポートのスロットの後ろに埋めます。したがって、launchd が登録したダミーサービスの数よりも少ないポートを割り当てる限り、ターゲットスロットはまだフリーリスト上にあり、launchd に元のサービスと同じポート名でそのスロットを再割り当てさせることができます。

この戦略の制限は、launchd にサービスを登録するために `com.apple.security.application-groups` エンタイトルメントが必要なことです。launchd に Mach ポートを隠す他の方法もありますが、アプリケーショングループを使用するのが確かに最も簡単であり、この概念実証には十分です。

### 解放されたサービスの偽装

クラッシャーアプリ拡張機能を起動し、launchd 内の Mach 送信権を解放したら、その Mach ポート名を、自分たちが受信権を持つ送信権で再割り当てする必要があります。そうすれば、launchd がそのポート名に送信するメッセージはすべて私たちが受信し、launchd がそのポート名をクライアントと共有するときはいつでも、クライアントは私たちのポートへの送信権を受け取ります。特に、launchd の Mach サービスへの送信権を解放できれば、そのサービスを launchd に要求するすべてのプロセスは、実際のサービスポートの代わりに私たち自身のポートへの送信権を受け取ります。これにより、サービスを偽装したり、中間者攻撃を実行して、クライアントがサービスに送信するすべてのメッセージを検査したりできます。

解放されたポート名を再利用して、それが私たちが所有するポートを指すようにすることも、アプリケーショングループのエンタイトルメントを使用することをすでに決めているので、非常に簡単です。launchd にダミーの Mach サービスを、そのうちの1つが元のポート名を再利用するまで登録するだけです。バッチで行う必要があり、多数のダミーサービスを一緒に登録し、それらのいずれかが解放されたポート名を正常に再利用したかどうかを確認し、その後それらを登録解除します。その理由は、登録が IPC ポートフリーリストを完全に遡り、埋めた目的のポート名を回復することを確実にする必要があるからです。

解放されたポート名の再利用に成功したかどうかは、`bootstrap_look_up` で元のサービスを検索することで確認できます。検索結果が登録したサービスポートの1つを返した場合、成功です。

元のサービスと同じポート名を取得する新しいサービスの登録に成功すると、launchd で元のサービスを検索するクライアントはすべて、実際のサービスポートではなく、私たちのポートへの送信権を付与されます。したがって、システムの残りの部分に対して(少なくとも攻撃後にそのサービスを検索するプロセスに対して)事実上、元のサービスを偽装していることになります。

Stage 1: host-priv ポートの取得
---------------------------------------------------------------------------------------------------

任意のシステムサービスを偽装する能力を手に入れたら、次のステップは host-priv ポートを取得することです。このステップは簡単で、iOS 11.3 の変更によって影響を受けません。この攻撃の大まかなアイデアは、SafetyNet を偽装し、ReportCrash をクラッシュさせ、例外メッセージで送信される死にかけの ReportCrash タスクポートから host-priv ポートを取得することです。

### ReportCrash と SafetyNet について

ReportCrash は、iOS 上でクラッシュレポートを生成する役割を担っています。この1つのバイナリは実際には4つの異なるサービスを提供しています(それぞれ別のプロセスで動作しますが、常にすべてが実行されているとは限りません)。

1. `com.apple.ReportCrash` は、クラッシュするプロセスのクラッシュレポートを生成する責任があります。これは、`EXC_CRASH`、`EXC_GUARD`、`EXC_RESOURCE` 例外のホストレベルの例外ハンドラです。
2. `com.apple.ReportCrash.Jetsam` は Jetsam レポートを処理します。
3. `com.apple.ReportCrash.SimulateCrash` はシミュレートされたクラッシュのレポートを作成します。
4. `com.apple.ReportCrash.SafetyNet` は、`com.apple.ReportCrash` サービスの登録済み例外ハンドラです。

私たちにとって興味深いのは `com.apple.ReportCrash` と `com.apple.ReportCrash.SafetyNet` であり、以降これらを単に ReportCrash および SafetyNet と呼びます。これらは両方とも MIG ベースのサービスであり、実質的に同じコードを実行します。

ReportCrash が起動すると、launchd 内で SafetyNet サービスを検索し、返されたポートをタスクレベルの例外ハンドラとして設定します。その意図は、ReportCrash 自体がクラッシュした場合に、別のプロセスがそのクラッシュレポートを生成するようにすることのようです。しかし、このコードパスは機能していないように見えます。ReportCrash と SafetyNet はどちらも `mach_exception_raise_state_identity` メッセージのみを処理するにもかかわらず、ReportCrash は `mach_exception_raise` メッセージ用に SafetyNet を登録します。それでも、両方のサービスは依然として存在し、iOS コンテナサンドボックス内からアクセス可能です。

### ReportCrash 操作プリミティブ

以下の攻撃を実行するには、ReportCrash(または SafetyNet)を意図したとおりに動作するように操作できる必要があります。具体的には、次の機能が必要です。要求に応じて ReportCrash を起動する、ReportCrash を強制終了する、ReportCrash をクラッシュさせる、そして使用中に ReportCrash が終了しないようにする。ここでは、それぞれの目的をどのように達成するかを説明します。

ReportCrash を起動するには、単に Mach メッセージを送信するだけで十分です。launchd がオンデマンドで起動します。ただし、その特有の設計により、`mach_exception_raise_state_identity` 以外のメッセージタイプは、ReportCrash が新しいメッセージに応答しなくなり、最終的に終了する原因となります。したがって、その後に生き続けてもらいたい場合は、`mach_exception_raise_state_identity` メッセージを送信する必要があります。

ReportCrash を終了させるには、他のタイプの Mach メッセージを送信するだけで済みます。

ReportCrash をクラッシュさせる方法は多数あります。最も簡単なのは、スレッドポートを `MACH_PORT_NULL` に設定した `mach_exception_raise_state_identity` メッセージを送信することでしょう。

最後に、使用中に ReportCrash が終了しないようにする必要があります。処理する `mach_exception_raise_state_identity` メッセージごとに、元のスレッドがクラッシュレポートを生成している間に、別のスレッドを生成して次のメッセージをリッスンします。ReportCrash は、クラッシュレポートを生成しているすべての未処理のスレッドが完了すると終了します。したがって、クラッシュレポートを生成している途中にあるスレッドを停止できれば、ReportCrash が終了するのを防ぐことができます。

私が見つけた最も簡単な方法は、タスクフィールドとスレッドフィールドにカスタムポートを設定した `mach_exception_raise_state_identity` メッセージを送信することです。ReportCrash がクラッシュレポートを生成しようとすると、"task" ポート上で `task_policy_get` を呼び出し、それが私たちが送信したポートに Mach メッセージを送信して応答を待つことになります。しかし、"task" ポートは単なる通常の Mach ポートなので、Mach メッセージに応答しないだけで、ReportCrash は `task_policy_get` の戻りを無期限に待ち続けます。

### ReportCrash からの host-priv 抽出

エクスプロイトの最初のステージでは、攻撃計画は比較的単純です。

1. SafetyNet サービスを起動し、攻撃中は生存させ続けます。
2. launchd サービス偽装プリミティブを使用して SafetyNet を偽装します。これにより、実際の SafetyNet サービス宛のメッセージを受信できる新しいポートが得られます。
3. 既存の ReportCrash インスタンスを終了させます。これにより、次のステップで ReportCrash が私たちの SafetyNet ポートを検索することを確実にします。
4. ReportCrash を起動します。ReportCrash は launchd 内で SafetyNet を検索し、結果のポート(受信権を所有する偽の SafetyNet ポート)を `EXC_CRASH` メッセージの宛先として設定します。
5. ReportCrash でクラッシュを発生させます。元の例外タイプの登録ハンドラがないことを確認した後、ReportCrash はプロセス終了フェーズに入ります。この時点で XNU は、ReportCrash が `EXC_CRASH` 例外を受信するために偽の SafetyNet ポートを登録したことを認識し、例外メッセージを生成してそのポートに送信します。
6. 次に、偽の SafetyNet ポートで `EXC_CRASH` メッセージをリッスンします。メッセージのタイプは `mach_exception_raise` であり、ReportCrash のタスクポートが含まれます。
7. 最後に、ReportCrash のタスクポートで `task_get_special_port` を使用して、ReportCrash のホストポートを取得します。ReportCrash はサンドボックス外でルートとして実行されるため、これは host-priv ポートです。

サンドボックス脱出のこのステージの終わりには、使用可能な host-priv ポートを手に入れることになります。これだけでも、これが深刻なセキュリティ問題であることを示しています。

Stage 2: サンドボックスの脱出
---------------------------------------------------------------------------------------------------

host-priv ポートを手に入れたとしても、私たちの目標はサンドボックスから完全に脱出し、`task_for_pid-allow` エンタイトルメントを持つルートとしてコードを実行することです。それを達成するための最初のステップは、単にサンドボックスを脱出することです。

技術的には、サンドボックスを脱出する前に host-priv ポートを取得する必要はありません。これら2つのステップは独立しており、どちらの順序でも実行できます。ただし、このステージまたは後続のステージが失敗するとシステムが不安定になるため、後回しにする価値があります。

大まかな攻撃は、同じ launchd 脆弱性を再度使用してシステムサービスを偽装することです。ただし、今回は、クライアントが Mach メッセージ内でタスクポートを送信するサービスを偽装することが目標です。iOS 11.2.6 での実験により、backboardd がホストする `com.apple.CARenderServer`(以下 CARenderServer)を偽装し、`com.apple.DragUI.druid.source` と通信すると、サンドボックス外の druid デーモンが Mach メッセージ内で自身のタスクポートを偽のサービスポートに送信することが簡単にわかります。

このエクスプロイトのステップは iOS 11.3 では機能しません。druid が CARenderServer への Mach メッセージ内でタスクポートを送信しなくなったためです。それにもかかわらず、この脆弱性がサンドボックス脱出にまだ使用できると確信しています。これを行う1つの方法は、他のサービスからの入力を信頼するサンドボックス外のサービスを探すことです。この種の「脆弱性」は、システムサービスを置き換える能力がなければ悪用不可能であり、Apple の内部および外部の両方でおそらく低優先度の攻撃対象領域であることを意味します。

### druid のクラッシュ

ReportCrash と同様に、druid がすでに実行中の場合、それを再起動させて launchd 内で偽の CARenderServer ポートを検索させる必要があります。この目的のために、既に修正が予定されていた libxpc のバグを使用することにしました。

libxpc を調べていると、任意の XPC サービスを強制的にクラッシュさせるために使用できる範囲外読み取りを発見しました。```C
void _xpc_dictionary_apply_wire_f
(
        OS_xpc_dictionary *xdict,
        OS_xpc_serializer *xserializer,
        const void *context,
        bool (*applier_fn)(const char *, OS_xpc_serializer *, const void *)
)
{
...
    uint64_t count = (unsigned int)*serialized_dict_count;
    if ( count )
    {
        uint64_t depth = xserializer->depth;
        uint64_t index = 0;
        do
        {
            const char *key = _xpc_serializer_read(xserializer, 0, 0, 0);
            size_t keylen = strlen(key);
            _xpc_serializer_advance(xserializer, keylen + 1);
            if ( !applier_fn(key, xserializer, context) )
                break;
            xserializer->depth = depth;
            ++index;
        }
        while ( index < count );
    }
...
}

問題は、攻撃者が制御するデータに対してチェックされないstrlenを使用すると、シリアライズされたディクショナリエントリのキーがデータバッファの終端を超えて拡張されることです。つまり、ディクショナリをデシリアライズするXPCサービスは、strlenが範囲外のメモリを参照するか、_xpc_serializer_advanceが提供されたデータの終端を超えてシリアライザを進めようとするとクラッシュします。

このバグは私が発見した時には既にiOS 11.3 Betaで修正されていたため、Appleには報告しませんでした。エクスプロイトは私のxpc-crashリポジトリで独立したプロジェクトとして利用可能です。

このバグを使用してdruidをクラッシュさせるには、druidサービスに不正な形式のXPCメッセージを送信し、ディクショナリのキーが終端されずにメッセージの最終バイトまで拡張されるようにするだけです。

druidのタスクポートを取得する

サービス偽装プリミティブを使用してiOS 11.2.6でdruidのタスクポートを取得するのは簡単です:

  1. Machサービス偽装機能を使用してCARenderServerになりすます。
  2. druidサービスにメッセージを送信して起動させる。
  3. 数秒経ってもdruidのタスクポートが取得できなければ、XPCバグを使用してdruidを強制終了し、再起動する。
  4. druidは偽のCARenderServerポート上でタスクポートを送信してくる。

プラットフォームバイナリのタスクポート制限を回避する

druidのタスクポートを取得したら、druidプロセス内でコードを実行する方法をまだ考え出す必要があります。

問題は、XNUがプラットフォームバイナリのタスクポートを非プラットフォームバイナリによる変更から保護していることです。この防御は関数task_conversion_evalに実装されており、convert_port_to_locked_taskとconvert_port_to_task_with_exec_tokenによって呼び出されます:```C kern_return_t task_conversion_eval(task_t caller, task_t victim) { /* * Tasks are allowed to resolve their own task ports, and the kernel is * allowed to resolve anyone's task port. */ if (caller == kernel_task) { return KERN_SUCCESS; }

root@kitploit:~
if (caller == victim) {
	return KERN_SUCCESS;
}

/*
 * Only the kernel can can resolve the kernel's task port. We've established
 * by this point that the caller is not kernel_task.
 */
if (victim == kernel_task) {
	return KERN_INVALID_SECURITY;
}

#if CONFIG_EMBEDDED /* * On embedded platforms, only a platform binary can resolve the task port * of another platform binary. / if ((victim->t_flags & TF_PLATFORM) && !(caller->t_flags & TF_PLATFORM)) { #if SECURE_KERNEL return KERN_INVALID_SECURITY; #else if (cs_relax_platform_task_ports) { return KERN_SUCCESS; } else { return KERN_INVALID_SECURITY; } #endif / SECURE_KERNEL / } #endif / CONFIG_EMBEDDED */

root@kitploit:~
return KERN_SUCCESS;

}

root@kitploit:~
これらの関数に依存するMIG変換ルーチン(`convert_port_to_task` や
`convert_port_to_map` を含む)は、druidのタスクに対して呼び出した場合に失敗します。例えば、
`mach_vm_write` ではdruidのメモリを操作できません。

しかし、XNUのMIGファイル `osfmk/mach/task.defs` を調べていると、興味深いことに
気づきました:```C
/*
 *	Returns the set of threads belonging to the target task.
 */
routine task_threads(
		target_task	: task_inspect_t;
	out	act_list	: thread_act_array_t);

関数 task_threads は、タスク内のスレッドを列挙するものですが、実際には task_t ではなく task_inspect_t を取ります。つまり、MIG はそれを convert_port_to_task ではなく convert_port_to_task_inspect を使って変換します。convert_port_to_task_inspect を少し見てみると、この関数は task_conversion_eval チェックを実行しないことがわかります。つまり、プラットフォームバイナリに対しても正常に呼び出すことができるということです。これは興味深いことに、返されるスレッドは thread_inspect_t 権限ではなく、完全な thread_act_t 権限です。言い換えれば、task_threads は変更不可能なタスク権限を変更可能なスレッド権限に昇格させます。そして、同等の thread_conversion_eval が存在しないため、Mach スレッド API を使用して、そのタスクがプラットフォームバイナリであっても、そのタスクのスレッドを変更できるということです。

これを活用するために、私は threadexec というライブラリを作成しました。これは Mach スレッド API の上にフル機能の関数呼び出し機能を構築するものです。threadexec プロジェクト自体はかなりの労力を要しましたが、このエクスプロイトとは間接的に関連するだけなので、内部動作の詳細な説明は省略します。

ステージ 3: 新しいホストレベルの例外ハンドラのインストール

ホスト特権ポートと druid 内でのサンドボックス解除されたコード実行を手に入れたら、完全なサンドボックスエスケープの次のステージは、新しいホストレベルの例外ハンドラをインストールすることです。このプロセスは、現在の能力を考えれば単純です:

  1. host_get_exception_ports を呼び出して、EXC_BAD_ACCESS の現在のホストレベルの例外ハンドラを取得する。
  2. EXC_BAD_ACCESS の新しいホストレベルの例外ハンドラとなる Mach ポートを割り当てる。
  3. ホスト特権ポートと先ほど割り当てた Mach ポートへの送信権を druid に送信する。
  4. druid 内の実行コンテキストを使用して、druid に host_set_exception_ports を呼び出させ、EXC_BAD_ACCESS のホストレベルの例外ハンドラとして私たちの Mach ポートを登録させる。

このステージの後、プロセスが無効なメモリアドレスにアクセスするたびに(そして登録された例外ハンドラも持っていない場合)、EXC_BAD_ACCESS 例外メッセージが新しい例外ハンドラポートに送信されます。これにより、クラッシュしたプロセスのタスクポートを取得でき、EXC_BAD_ACCESS は回復可能な例外なので、今回はそのタスクポートを使用してコードを実行できます。

ステージ 4: ReportCrash のタスクポートの取得

次のステージは、ReportCrash で EXC_BAD_ACCESS 例外をトリガーして、そのタスクポートが私たちの新しい例外ハンドラポートに例外メッセージとして送られるようにすることです:

  1. 前述の手法を使用して ReportCrash をクラッシュさせる。これにより、ReportCrash は EXC_BAD_ACCESS 例外を生成する。ReportCrash には EXC_BAD_ACCESS 用の例外ハンドラが登録されていないため(SafetyNet は EXC_CRASH 用に登録されていることを思い出してください)、例外はホストレベルの例外ハンドラに配信されます。
  2. ホスト例外ハンドラポートで例外メッセージをリスンする。
  3. ReportCrash の例外メッセージを受信したら、タスクポートとスレッドポートを保存する。クラッシュしたスレッドを一時停止し、KERN_SUCCESS を返して、例外が処理され ReportCrash が再開可能であることをカーネルに示す。
  4. タスクポートとスレッドポートを使用して、druid で行ったのと同様に、ReportCrash 内に実行コンテキストを確立する。

この時点で、サンドボックス解除、root、task_for_pid-allow のプロセス内でコード実行が可能になります。

ステージ 5: 元のホストレベルの例外ハンドラの復元

次の 2 つのステージは厳密には必要ありませんが、とにかく実行すべきです。

ReportCrash 内でコード実行を確立したら、druid を使用して EXC_BAD_ACCESS のホストレベルの例外ハンドラをリセットする必要があります:

  1. 古いホストレベルの例外ハンドラポートを druid に送信する。
  2. druid で host_set_exception_ports を呼び出して、EXC_BAD_ACCESS の古いホストレベルの例外ハンドラを再登録する。

これにより、私たちの例外ハンドラポートが他のクラッシュプロセスの例外メッセージを受信しなくなります。

ステージ 6: launchd の修正

最後のステップは、サービスポートを IPC 名前空間から解放して偽装する際に launchd に与えたダメージを修復することです:

  1. ReportCrash で task_for_pid を呼び出して launchd のタスクポートを取得する。
  2. 偽装した各サービスについて:
    1. 偽のサービスポートへの送信権に対する launchd の名前を取得する。これは実際のサービスポートの元の名前です。
    2. 偽のサービスポートを破棄し、launchd から偽のサービスを登録解除する。
    3. ReportCrash 内で mach_port_insert_right を呼び出し、実際のサービスポートを元の名前で launchd の IPC 空間にプッシュする。

このステップが完了すると、システムは再び完全に機能するはずです。エクスプロイトが成功した後は、デバイスを強制的にリセットする必要はありません。エクスプロイトはすべての損傷を自分で修復するからです。

ポストエクスプロイト

Blanket はまた、amfid をバイパスし、バインドシェルを起動するポストエクスプロイトペイロードをパッケージ化しています。このセクションでは、それがどのように達成されるかを説明します。

ペイロードプロセスの起動

ReportCrash でコード実行を獲得した後でも、その能力を使うのは簡単ではありません。プロセス内から個々の関数呼び出しを実行するだけでは、複雑なタスクを実行するのは困難です。理想的には、ReportCrash にコードを注入するか、同じ(またはそれ以上の)権限を持つ新しいプロセスを起動して、ReportCrash の権限でネイティブにコードを実行する方法が欲しいところです。

Blanket はプロセス起動のルートを選択します。ReportCrash のプラットフォームバイナリステータスと task_for_pid を使用して launchd のタスクポートを取得し、launchd 内に制御可能な新しいスレッドを作成します。次に、そのスレッドを使用して posix_spawn を呼び出し、ペイロードバイナリを起動します。ペイロードバイナリは、task_for_pid-allow を含む制限されたエンタイトルメントで署名され、追加の機能を付与されます。

amfid のバイパス

iOS が新しく生成したバイナリを受け入れるためには、コード署名をバイパスする必要があります。長年にわたって様々な戦略が議論されてきましたが、現在最も一般的な戦略は、amfid の例外ハンドラを登録し、amfid が MISValidateSignatureAndCopyInfo を呼び出そうとするときにクラッシュするようにデータパッチを適用することです。これにより、その関数の実装を偽装して、コード署名が有効であるように見せかけることができます。

しかし、より堅牢で柔軟だと考える別のアプローチがあります。amfid にパッチを当てる代わりに、単にカーネルに新しい amfid ポートを登録するだけです。

カーネルは、HOST_AMFID_PORT というホストスペシャルポートを使用して、どのポートに amfid へのメッセージを送信するかを追跡しています。サンドボックス解除された root コード実行がある場合、このポートを新しい値に設定できます。Apple は、検証リクエストへの応答が本当に amfid から来たかどうかを確認することで、この攻撃から保護しています。送信者の cdhash が amfid の cdhash と比較されます。しかし、これは実際にはメッセージが amfid 以外のプロセスに送信されることを防ぐわけではなく、応答が amfid 以外のプロセスから来ることを防ぐだけです。カーネルがメッセージを私たちに送信し、私たちが応答を生成して amfid に渡し、amfid がその応答をカーネルに送信するという三角形を設定すれば、送信者チェックをバイパスできます。

このアプローチには多くの利点があり、その中で最大のものは、verify_code_directory サービスルーチン内の追加フラグへのアクセスが得られることでしょう。amfid がそれらすべてを使用していなくても、amfid がコード署名の動作を制御するために設定できる多くの他の出力フラグがあります。以下は verify_code_directory の部分的なプロトタイプです:```C kern_return_t verify_code_directory( mach_port_t amfid_port, amfid_path_t path, uint64_t file_offset, int32_t a4, int32_t a5, int32_t a6, int32_t * entitlements_valid, int32_t * signature_valid, int32_t * unrestrict, int32_t * signer_type, int32_t * is_apple, int32_t * is_developer_code, amfid_a13_t a13, amfid_cdhash_t cdhash, audit_token_t audit);

root@kitploit:~
Of particular interest for jailbreak developers is the `is_apple` parameter. This parameter does
not appear to be used by amfid, but if set, it will cause the kernel to set the
`CS_PLATFORM_BINARY` codesigning flag, which grants the application platform binary privileges. In
particular, this means that the application can now use task ports to modify platform binaries
directly.

脱獄開発者にとって特に興味深いのは、`is_apple` パラメータです。このパラメータは amfid によって使用されていないように見えますが、設定されると、カーネルが `CS_PLATFORM_BINARY` コード署名フラグを設定し、アプリケーションにプラットフォームバイナリの特権を付与します。特に、アプリケーションがタスクポートを使用してプラットフォームバイナリを直接変更できるようになることを意味します。

Loopholes used in this attack
---------------------------------------------------------------------------------------------------

This attack takes advantage of several loopholes that aren't security vulnerabilities themselves
but do minimize the effectiveness of various exploit mitigations. Not all of these need to be
closed together, since some are partially redundant, but it's worth listing them all anyway.

本攻撃で利用された抜け穴
---------------------------------------------------------------------------------------------------

この攻撃は、それ自体はセキュリティ脆弱性ではないものの、さまざまなエクスプロイト緩和策の効果を最小限に抑えるいくつかの抜け穴を利用しています。これらすべてを同時に閉じる必要はありません(一部は部分的に重複しているため)が、とにかくすべてを列挙する価値があります。

In the kernel:

1. `task_threads` can promote an inspect-only `task_inspect_t` to a modify-capable `thread_act_t`.
2. There is no `thread_conversion_eval` to perform the role of `task_conversion_eval` for threads.
3. A non-platform binary may use a `task_inspect_t` right for a platform binary.
4. Exception messages for unsandboxed processes may be delivered to sandboxed processes, even
   though that provides a way to escape the sandbox. It's not clear whether there is a clean fix
   for this loophole.
5. Unsandboxed code execution, the host-priv port, and the ability to crash a `task_for_pid-allow`
   process can be combined to build a `task_for_pid` workaround. (The workaround is: call
   `host_set_exception_ports` to set a new host-level exception handler, then crash the
   `task_for_pid-allow` process to receive its task port and execute code with the entitlement.)

カーネル内:

1. `task_threads` は、検査専用の `task_inspect_t` を変更可能な `thread_act_t` に昇格させることができます。
2. スレッドに対して `task_conversion_eval` の役割を果たす `thread_conversion_eval` は存在しません。
3. 非プラットフォームバイナリは、プラットフォームバイナリに対して `task_inspect_t` 権限を使用する可能性があります。
4. サンドボックス化されていないプロセスの例外メッセージが、サンドボックス化されたプロセスに配信される可能性があります。これはサンドボックスを脱出する方法を提供するものですが、この抜け穴に対する明確な修正方法があるかどうかは不明です。
5. サンドボックス化されていないコード実行、ホスト特権ポート、および `task_for_pid-allow` プロセスをクラッシュさせる機能を組み合わせて、`task_for_pid` の回避策を構築できます。(回避策は、`host_set_exception_ports` を呼び出して新しいホストレベルの例外ハンドラを設定し、`task_for_pid-allow` プロセスをクラッシュさせてタスクポートを受け取り、その権限でコードを実行するというものです。)

In app extensions:

1. App extensions that share an application group can communicate using Mach messages, despite the
   documentation suggesting that communication between the host app and the app extension should be
   impossible.

アプリ拡張機能内:

1. アプリケーショングループを共有するアプリ拡張機能は、ドキュメントではホストアプリとアプリ拡張機能間の通信が不可能であると示唆されているにもかかわらず、Mach メッセージを使用して通信できます。

Recommended fixes and mitigations
---------------------------------------------------------------------------------------------------

I recommend the following fixes, roughly in order of importance:

1. Only deallocate Mach ports in the launchd service routines when returning `KERN_SUCCESS`. This
   will fix the Mach port replacement vulnerability.
2. Close the `task_threads` loophole allowing a non-platform binary to use the task port of a
   platform binary to achieve code execution.
3. Fix crashing issues in ReportCrash.
4. The set of Mach services reachable from within the container sandbox should be minimized. I do
   not see a legitimate reason for most iOS apps to communicate with ReportCrash or SafetyNet.
5. As many processes as possible should be sandboxed. I'm not sure whether druid needs to be
   unsandboxed to function properly, but if not, it should be placed in an appropriate sandbox.
6. Dead code should be eliminated. SafetyNet does not seem to be performing its intended
   functionality. If it is no longer needed, it should probably be removed.
7. Close the `host_set_exception_ports`-based `task_for_pid` workaround. For example, consider
   whether it's worth restricting `host_set_exception_ports` to root or restricting the usability
   of the host-priv port under some configurations. This violates the elegant capabilities-based
   design of Mach, but `host_set_exception_ports` might be a promising target for abuse.
8. Consider whether it's worth adding `task_conversion_eval` to `task_inspect_t`.

推奨される修正と緩和策
---------------------------------------------------------------------------------------------------

以下の修正を、おおよそ重要度順に推奨します。

1. `launchd` サービスルーチンでは、`KERN_SUCCESS` を返す場合にのみ Mach ポートを割り当て解除する。これにより、Mach ポートの置換脆弱性が修正されます。
2. 非プラットフォームバイナリがプラットフォームバイナリのタスクポートを使用してコード実行を達成できる `task_threads` の抜け穴を塞ぐ。
3. ReportCrash のクラッシュ問題を修正する。
4. コンテナサンドボックス内から到達可能な Mach サービスのセットを最小限にすべきです。ほとんどの iOS アプリが ReportCrash や SafetyNet と通信する正当な理由はないと考えます。
5. できるだけ多くのプロセスをサンドボックス化すべきです。druid が適切に機能するためにサンドボックス化されていない状態が必要かどうかはわかりませんが、必要でなければ適切なサンドボックスに配置すべきです。
6. デッドコードを排除すべきです。SafetyNet は意図された機能を果たしていないようです。もはや不要であれば、削除すべきでしょう。
7. `host_set_exception_ports` ベースの `task_for_pid` 回避策を塞ぐ。例えば、`host_set_exception_ports` を root に制限するか、一部の設定でホスト特権ポートの使用性を制限する価値があるか検討する。これは Mach のエレガントなケーパビリティベースの設計に違反しますが、`host_set_exception_ports` は悪用の標的になり得ます。
8. `task_inspect_t` に `task_conversion_eval` を追加する価値があるか検討する。

Running blanket
---------------------------------------------------------------------------------------------------

Blanket should work on any device running iOS 11.2.6.

1. Download the project:

Blanket の実行
---------------------------------------------------------------------------------------------------

Blanket は iOS 11.2.6 を実行している任意のデバイスで動作するはずです。

1. プロジェクトをダウンロード:   ```
   git clone https://github.com/bazad/blanket
   cd blanket
  1. blanketがプロセスやタスクにコードを注入するために必要なthreadexecライブラリをダウンロードしてビルドします。 プロセスやタスク: ``` git clone https://github.com/bazad/threadexec cd threadexec make ARCH=arm64 SDK=iphoneos EXTRA_CFLAGS='-mios-version-min=11.1 -fembed-bitcode' cd ..
    root@kitploit:~
  2. Jonathan Levin の iOS binpack をダウンロードしてください。これには bind shell で使用されるバイナリが含まれています。 ペイロードを変更して別のことを行う場合、binpack は必要ありません。 ``` mkdir binpack curl http://newosxbook.com/tools/binpack64-256.tar.gz | tar -xf- -C binpack
    root@kitploit:~
  3. Xcodeを開き、プロジェクトを設定します。署名識別子を変更し、カスタムアプリケーショングループエンタイトルメントを指定する必要があります。
  4. ファイル headers/config.h を編集し、APP_GROUP を先に指定したアプリケーショングループ識別子に変更します。

その後、デバイス上でプロジェクトをビルドして実行できるはずです。

blanketが成功すると、ペイロードバイナリ(ソースは blanket_payload/blanket_payload.c)が実行されます。デフォルトではポート4242でバインドシェルを生成します。そのポートにnetcatで接続し、任意のシェルコマンドを実行できます。

クレジット

Ian Beer氏とJonathan Levin氏の優れたiOSセキュリティおよび内部構造に関する研究に感謝します。

タイムライン

この脆弱性は2018年1月に発見し、2月下旬にエクスプロイトの開発を開始しました。この問題を4月13日にAppleに報告しました。AppleはlaunchdにおけるMachポート置換の脆弱性をCVE-2018-4280に割り当て、7月9日にリリースされたiOS 11.4.1およびmacOS 10.13.6で修正されました。

ライセンス

BlanketはMITライセンスの下で公開されています。


Brandon Azad

ツールをダウンロード