-
Memory Integrity Enforcementとポインタ認証の導入
Memory Integrity Enforcementとポインタ認証を連携させ、アプリをメモリ破損から保護する仕組みについて学びます。このセッションでは、型認識セキュアアロケータがMemory Tagging Extensionsを活用してエクスプロイト(悪用)を防ぐ仕組みや、アロケータラッパーやオブジェクトプールを適合させてハードウェア保護を維持する方法をご紹介します。さらに、Xcodeでarm64eを使ってポインタ認証を有効にし、制御フローの整合性を維持する方法もご確認ください。
このセッションは、「Appleに相談」のイベントである「アプリの強化:セキュリティを向上させるための必須戦略」の一部として実施されました。
詳しい情報や関連セッションについては、ビデオ全編をご視聴ください。リソース
-
このビデオを検索
本日最初のディープダイブへようこそ。 私の名前はヘンリー・カペルです。 私はセキュリティエンジニアで、 後ほど同僚たちもステージに上がります。 エラとオリバー・ハント。 これから、当社の最も魅力的なセキュリティ機能のうち 2つをご紹介します。 メモリの整合性、強制、およびポインタ認証。 これは上級者向けの講演になります。 今回は、メモリ割り当て、ポインタ、 そしてセキュリティモデルについて説明します。 まず、長さについて 理解していただくことから始めます。 メモリ整合性の強制は、 アプリケーションを保護するために行われます。 するとフィリッポがやり方を教えてくれます 特定のカスタムメモリ管理実装に対応するため セキュリティアプリケーションの動作を 妨げたり、 そもそも機能しなかったりする可能性がある。 誠実性の徹底を忘れないでください。 それでは話題を変えましょう そして、オリバーが登壇し、当社のセキュリティ戦略の 別の側面について解説します。 ポインター認証の使用方法 攻撃者が任意のコード実行を 行うことを防ぐため。
しかし、 自己紹介はこれくらいにしておきましょう。 まずはメモリ整合性の強制から始めましょう。 先に述べたように、セキュリティ問題の大部分は メモリに関するものです。 メモリ整合性強制におけるデータ破損バグ。 私たちの使命は、DDSの大部分を 悪用可能にすることです。 これは非常に大きなことです。それらはもはやあなたのアプリケーションにとって セキュリティ上の問題ではなくなります。
メモリ整合性強制機能は、 私たちがそれを使用するのとまったく 同じ方法でリリースされます。 私たち自身のためだ。 ここでは、それは彼の妨げにはならない。 それは、ファーストパーティアプリがまた、 サードパーティ製アプリも、ユーザーの 安全を守る上で同様に重要です。 そのため、私たちは始めるための素晴らしい リソースをいくつかまとめました。 メモリ整合性強制機能付き。 すべてのセキュリティ軸について 説明するブログを公開します。 メモリ整合性強制が動作する仕組み。
技術講演では、その方法を段階的に説明します。 アプリケーションにメモリ整合性の 強制機能を組み込む。
そして私たちは素晴らしい終わりもまとめました ほぼ同じテーマに関する ドキュメントを締めくくる。 メモを取る必要はありません。 これらのリンクはすべてメールでお送りします。 後で視聴する場合でも、 それらはオンラインセッションに添付されます。 ぜひチェックしてみてください。 素晴らしいですよ。今日は 詳しい紹介はしませんが。 メモリ整合性の強制に関して。 そして私は、アプリケーションにのみ 集中します。 セキュリティの中核部分と連携し、 メモリを活用する 型認識型セキュアメモリ割り当て器 タグ付け拡張機能。
MTVはハードウェア技術である そして、メモリ整合性の強制を支える 最大の投資の一つは、 そして、おそらくアプリケーションに 最も目に見える影響を与えるものでもある。 それでは、簡単に見ていきましょう。 これは鍵と錠のシステムです。 メモリへのロックは 16バイト単位で割り当てられます。 それはページよりもはるかに細かい粒度です。 そして、それは動的な割り 当てには理想的と言えるでしょう。 キーは代わりにポインタに格納されます。 上位ビット、ロック、 キーは一般的にタグと呼ばれているため、 その名前が付けられています。 メモリタグ付けソフトウェアは、 タグの割り当てを制御します。 それが、いわゆるタグ付けポリシーです。 画像では、黄色のバッファタグ7は ソフトウェアの決定です。 そして2つのポインタも同様です 左側のハードウェアでは、 代わりにチェックポリシーと呼ばれるものを 実装しています。 鍵と錠が一致する 場合にのみアクセスを許可する。 ポインタにタグが付くようになったとしても、
これはソフトウェアにはそれほど 大きな影響を与えません。 実際、メモリ整合性の強制を有効にするのは 非常に簡単です。 Xcodeを数回クリックするだけです。 他の技術とは異なり、導入には 相当な労力が必要となるが、MITは、 ほとんど改変されていない ソフトウェア上で作業を行っている。 いや、つまり、何か裏があるに違いない。 そうでなければ、私たちは 今日ここにいないだろう。
ただし、アプリケーションが独自のメモリ管理を 実装している場合は除きます。 これまでこの話題を何度か 遠回しに述べてきたので、 それが何を意味するのかを 改めて説明しましょう。 アプリケーションには3つのパターンがあります これにより、カスタムメモリ管理が 可能になります。 ここでは、可能性の高い 順にそれらを使用します。
1つ目は割り当てられたラッパーです。 割り当て済みラッパーは、 システム割り当て済み APIをラップするインターフェースです。 そして、通常は携帯性の理由からそうする。 あるいは、メモリ操作に関する 追加のロジックを実装する必要があるでしょう。
これらは圧倒的に 最もよく見られる構造物である。 そして、これから説明する 他の2つのケースとは反対に、 この考え方をより分かりやすくするために、 簡単な例を挙げましょう。 ここでは、 allocate memory という名前の関数を定義します。 これは実際にmallocを呼び出すものです。
残りのコードは、malloc ディレクトリを 呼び出します。 しかし、代わりに常にこのインターフェースに 移動します。 この例に注目してみましょう。 フィリッポは短い時間でステージに上がり、 それについて詳しく説明します。
2つ目の直接メモリ管理パターンは、 プーリングとキャッシング戦略です。 これらの戦略の目標は 頻繁に使用される物品をリサイクルすることで パフォーマンスを向上させる、 システムアロケータへの往復通信を 回避するためです。 それらも多少は一般的だが、 アロケータラッパーほどではない。
そして最後に、幸いなことに、 アプリケーションでは非常にまれなことですが、 しかし、フレームワークではより一般的である これらは完全にカスタマイズ可能な メモリ割り当て器です。 この場合は、そのまま 交換できる部品があります。 これは、システムデフォルトのものを 完全にバイパスします。
直感的に、これら 3つのパターンはすべて有害である メモリタグ付けの整合性強制の セキュリティに干渉するため または、システムアロケータを 完全にバイパスする。 そして、当然のことながら、 本日の重要な推奨事項は以下のとおりです。
デフォルトのシステムアロケータを使用します。 もう少しこのスライドに留まります。 ラッパーやキャッシュなしで デフォルトのシステムアロケータを使用します。 あるいは、カスタム実装も可能です。 拡張性を高めるために 多くの時間を費やしています そして大多数のユーザーにとって高速で、 そしてそれは過去3年間で ゼロから構築されたものです。 つまり、3年以上前の業績数値を参考にすると、 それらを再評価してください。 きっと嬉しい驚きがあるでしょう。 そして、私たちはそれを書き直し、 誠実性の確保において際立つようにしました。 これで私たちは理解しました。 障害物の中には、存在する 正当な理由があるものもある。 もしかしたら、古いコードがまだかろうじて 動いているだけなのかもしれません。 そして、それを引き 継いでいかなければならない。 それでいいんです。だから この講演の残りの部分では、 それらをどのように維持できるかを 見ていきましょう。 しかし、そのためには 当社の位置情報サービスが満たす 極めて高いセキュリティ基準を満たすため。 そのためには、なぜ私たちがそのような設計にしたのかを 理解する必要があります。 つまり、セキュリティについて 理解する必要があるということです その背後にある科学的根拠。
では、攻撃者が何をするのか、つまり システムを悪用することから始めましょう。 そして、きっと共感を呼ぶであろう何かと共に そして、この話題について皆さんに 少しでも分かりやすく説明したいと思います。
エクスプロイトを作成するのは、 デバッグの逆の作業である。
メモリ破損の問題をデバッグする際は、 必ず現場検証から始める。 メモリの一部が誤って変更されたか、 誤って使用された可能性があります。 そして、プログラムが誤動作するようにする。 原因を突き止めるために 断片をつなぎ合わせようとするが、 バグは何だったのか? メモリのどの部分が破損していたのか? 起源は?
搾取する側ではなく、全く逆だ。 メモリから開始しますが、 その処理が間違っています。 そして、変更する 価値のあるメモリを探そうとする。
もっと科学的に言うと、 私たちは業界では少し独特な アプローチをとっています。 そして実際、それは 攻撃者中心の設計になっている。
我々には、攻撃者型と呼ばれる記憶が一つある。 これが腐敗を生み出している原因だ。 ここでいう「タイプ」という用語は、 多くの役割を果たします。 これは、メモリ位置の固有の特性を指します。 それはデータ構造かもしれない。 それは文字列かもしれないし、 レコードの集合体かもしれない。 次に、被害者タイプがあります。 それは攻撃者にとって 改ざんする価値のあるメモリだ。 それがメモリ破損の標的です。 メモリにはパスワード、関数ポインタ、 あるいは、後々攻撃者に能力を与えるもの 任意のコードを実行する。
これら2種類のデータは、 メモリ上のどこかに存在する。 つまり、両者の間には 一定の距離が存在するということだ。 患者がいる場合、距離は1となる。 あるいは、それらが重なり合う場合もあり、 その場合は距離はゼロとなる。
攻撃者がシステムを監視できると仮定します そして、任意の数の演算を実行します。 カーネルと通信したり、 ファイルシステムを調べたり、 特権によって許されることなら何でも。 私たちは攻撃者の行動に 上限を設けることに頼っていません。
攻撃側がコントロールすれば勝利する そして、加害者から 被害者までの距離を予測する。 以上です。それが、書き込みメモリ、 つまりデータ破損の悪用に関する本質です。
そしてこれは単純に見えるかもしれないし、 まあ、彼は通常優れた科学であるが、 しかし、それは実は非常に奥深いものなのです。 そこに着くまで長い時間がかかった。 そしてそれは、私たちの選択肢を 明確にする上で重要です。 メモリ破損に対する防御に関して言えば。 そしてそれは、私たちが 実際に引くことができるレバーは 2種類しかないことを示している そして距離。守備側の視点から見ると、 ゲーム全体は、タイプ選択と距離を 不可能にすることを目的としています あるいは、攻撃者にとって 予測や制御が極めて困難である。
これが、当社のセキュアメモリ割り当て ツールがすべて実装している理由です。 4つの主要なセキュリティ特性。
まず第一に、それらはタイプを認識します。 従来、malloc を介して行われる 割り当ては、単なるバイトの集合でした。 アロケータは、要求された サイズ以上の情報はほとんど知りません。 割り当ての意図については認識していない。 代わりに、当社のセキュアアロケータは 型を理解します そして、これらの大きなレバレッジは 主に手動入力によって実現されます カーネルおよびコンパイル時の自動型付け、 これは、ユーザー空間で 私たちが使用するものです。 型情報が手に入ったら、プレイを開始できます。 当社のアロケータは、攻撃者が 制御するのが困難になるように設計できます。 メモリ内で型を異なる 領域に分割することができます。 また、種類がたくさんあるので、種類ごとに 1つの地域を設けることはできません。 それは理想的でしょう。 そこで私たちは、似たような特徴を 持つものをまとめて集めるのです。 ブート時にそれらのコレクションを ランダム化します そのため、デバイスごとにグループが異なり、 これにより、攻撃者はエクスプロイトを微調整する 方法を見つけざるを得なくなる。 それぞれの組み合わせごとに。 それらはデバイス上で動作する可能性がある。
ほぼ同じ趣旨で、 これらのタイプの領域のメモリ上の 配置をランダム化します。 これもまた、起動時に行います。 そこで、デバイス間でさらにばらつきが 生じることになる。 これにより、攻撃者の試みは再び阻止される。 異なるタイプクラス間の距離を予測する。
そして最後に、 メモリタグ付け拡張機能を活用します 様々な型クラス内での攻撃を阻止するため。 各場所で異なるタグを割り当てて、 無料でサイクルします。 そして、地域全体にタグが均等に 分布するようにする。また、私たちは 禁止事項も実施します。 2 つの放射線オブジェクトが 同じタグを持っているか、 または同じタグを持っています。 これにより、1と小型サイズの間のあらゆる 距離を悪用することが不可能になる。
これが、当社のセキュリティアロケータの 動作原理です。 メモリ破損バグを止めるために、
それでは、フィリッポに ステージをお渡しします。 誰があなたのできることをカバーするのか 3つの一般的なカスタムメモリ管理パターンを 防止する 先ほどお話しした通りです。 つまり、それらはアプリケーションのセキュリティを 阻害しないということです。
次はあなたの番です。
私の名前はフィリッポです。カエレで セキュリティエンジニアとして働いています。 さて、エンリコが言ったように、 ほとんどの場合、メモリ整合性強制の採用には、 そちら側でコードの変更はありますか?
しかし、特別な配慮が 必要となる状況もいくつか存在する。 そして、まさにこれらが私が 今取り組んでいることなのです。 それぞれが記憶、整合性、 執行にどのように影響するかを見ていきます。 また、そのセキュリティモデルについても 説明し、最適な解決策についても解説する。
それではまず見ていきましょう おそらく最も一般的に見られる抽象化において あらゆる種類のコードベース、 アロケータラッパー。
それでは、エンリコが先ほど示した 例を取り上げて、それを見ていきましょう。 メモリ割り当て機能、 これにより、探すべき 機能のセットを定義する機会が得られます。 コードベース内のアロケータラッパーを 特定する。
まず、ラッパーとは、 ロジックを追加する関数です。 システムアロケータAPI。これらは、 使用されているメモリを要求することを 可能にするという意味で汎用的です。 さまざまな種類のものを保管するために、 そしてそれらはコードベース全体で 抽象化レイヤーとして 使用されますアロケータとやり取りする。
ここまでは、ごく 無害なことのように思えますよね? それでは、型分離が実際にどのように 機能するのかを見ていきましょう。 アロケータラッパーのセキュリティ上の 影響を理解するため。
ここに、パケット処理ロジックを 実装するコード群があります。
ご心配なく、これを全部読む必要はありません。 そして実際、システムアロケータインターフェースの 中核となる部分に 焦点を当ててみましょう。 ここに型分離の基礎がある。 そして実際には、コンパイラがすべての 作業を代行してくれているのです。
Xcodeで型アロケータの サポートを有効にすると、 型メモリ操作と呼ばれる コンパイラ機能を有効にします。 この機能を搭載しているため、 コンパイラは自動的に型を推論します 各割り当てコードサイトについて そして、各呼び出しを型認識型の 呼び出しに書き換え、 型情報を追加の引数として渡す。
コンパイラが各割り当てに異なる 形状を割り当てる様子を想像してみてください。 そしてこれらの形状は、 その後伝達される情報を正確に表している。 システムアロケータへ、 これは、割り当てを分離するために使用します。 型バケット化ポリシーを実装することにより、 それらの型に基づいて分類します。
ラッパーを実装すると、この呼び出し 箇所はコンパイラにとって不透明になります。 これは単一の形状しか認識できないでしょう。 これで、すべての割り当てが システムアロケータによってまとめて バケット化されます。 生成された型情報のみを参照するため ラッパー内の呼び出し箇所で。
それでは、この問題に対処するために 何ができるか考えてみましょう。
もちろん、ラッパーによって 実装されるロジックが重要でない場合 コードの機能性に関して、 最も簡単な解決策は、 ラッパーを完全に削除することです。 そして、システムアロケータのインターフェースを 直接呼び出します。
包装紙を保管する必要がある場合は、 型認識バリアントを適切に実装できます 型情報を転送する 型メモリ操作を採用することで、 システムアロケータに。 オペレーティングシステム全体で使用されている ものと同じコンパイラ技術。
それでは、そのプロセスを 段階的に見ていきましょう。
まず、上位の型のバリアントを宣言します。 これは、サイズ引数の直後に 型ID引数を追加します。
次に、アンダースコアを使用して型指定されていない バリアントに注釈を付けます。 malloc型マクロは、 対応する型バリアントを指定することによって 作成されます。 そして、サイズ引数の位置。 コンパイラに、すべての呼び 出しを型指定のないバリアントに 変換するように指示しています。 型認識型への呼び出しに、 また、各呼び出し箇所で型記述子を合成する。
最後に、型認識型のバリアントを 実装する必要があります。 しかし、これは非常に簡単です。 追加したロジックはすべてそのまま 維持できます。 必要な変更は、適切な型を 呼び出すことだけです。 取得した型記述子の値を転送することで、 malloc インターフェースを認識します 議論として。
この方法では、何も変更する必要はありません 実際にラッパーを使用しているコードへ。 コンパイラは、各呼び出し 箇所で型情報を自動的に提供します。 あなたのために。
これは簡単な概要にすぎません。 より詳細な情報については、 この手法について解説した 資料をご覧になることをお勧めします。
よし。これでアロケータラッパーの 扱い方が理解できた。 それでは次に、プールとキャッシュについて 見ていきましょう。
プールについて話すとき、 私たちは、何らかの形のリサイクルを実施するすべての アプローチを指します。 往復を避けるために同じ種類のオブジェクト システムアロケータへ、 そのような物品が廃棄され、 その後再び使用されるたびに。 キャッシング戦略もこのカテゴリーに 当てはまります。
この文脈では、重要な概念は 理解すべきことは、これらの抽象化が ライフサイクルを変えるということである。 リサイクルされている物品のうち。
そしてこれは、セキュリティに 関して深刻な影響を及ぼす。 メモリタグ付けによって提供される 保護機能に関して。 それでは、例を挙げながら、 それらの意味合いについて説明しましょう。
ここにオブジェクトのプールがあります。 それぞれは、メモリタグ付けを使用してタグ付けされた 割り当てによって裏付けられています。
プールからオブジェクトを取り出すと、 通常はそのオブジェクトへの ポインタを取得します。 これは、割り当てに関連付けられた メモリと同じタグを持つことになります。
対象物を使い終わったら、 それをプールに戻します。 ここで、コードにバグがある 場合を考えてみましょう。 そして、そのオブジェクトへのダングリングポインタを 保持しておく。 これはまさに、攻撃者が 悪用できる脆弱性を無料で提供する。
単にその物をリサイクルすれば、 これは、ダングリングポインターが そして、新しいライトにも 有効なタグが付いています。 ダングリングポインタを制御できる攻撃者は、 リサイクルされた後に オブジェクトを修正するために、 メモリ破損を引き起こし、アプリのロジックが 変わってしまう可能性があります。
割り当てをそのような脆弱性から 保護するためには、 オブジェクトがリサイクルされる前に、 タグを更新する必要があります。
再タグ付け後、古いポインタを使用して 実行されるアクセスはすべて 古いタグは安全にエラー処理を行い、 アプリを終了させて悪用を防ぎます。
それでは、それを実際にどのように 実現できるかについて説明しましょう。
弊社は、パケット処理コードに リサイクルポリシーを実装しました。 パケットを割り当てる必要がある場合、 まず、私たちが管理している スレッドローカルキューから それを抽出します。そして、 リリースパケット機能を補充します 物を処分するとき。 お分かりのように、このアプローチは まさに今見た問題と同じです。 では、MTAが提供する保護措置を維持するために、 私たちは何ができるでしょうか?
もちろん、もうお気づきかもしれませんね。 最善の解決策は、やはり システムアロケータを直接使用することです。 これにより、すべての割り当てが適切に 再タグ付けされることが保証されます。 メモリに期待される保護機能を提供します 倫理規範の徹底。
私たちは何年もかけて設計しました そして、安全かつ安全なシステムアロケータを 実装するそして、 それはあらゆるシナリオにおいて従来の割り 当て方式よりも優れた性能を発揮します。 そして実際、スレッドローカルキャッシュを 使用すると、 当社のシステムアロケータは、 このようなシナリオにも最適化されています。
この方法があなたに適さない稀なケースでは、 コードのプロファイリングをお勧めします そして、生活を変えないさまざまな 戦略を探求する 物体の循環。 例えば、使用するオブジェクトを バッチ処理で割り当てるなどです。
よし、この方法だと非常に簡単だ プーリング戦略を適応させる MIが提供するセキュリティ特性を 維持するため。
それでは、カスタムアロケータについて 少しお話ししたいと思います。 これは必ずしもアプリケーションコードに 実装されているとは限りません。 しかし、それらはあなたが使用している 外部依存関係の一部である可能性があります。 ライブラリは歴史的にパフォーマンス上の 理由からこれらを実装してきた あるいは、プラットフォーム間の 互換性を確保するため。
カスタムアロケータが存在する場合、 何が起こるか予測不可能だ。 使用するコードを理解するのが非常に重要です カスタムアロケータは Ma の利点を得られず、特に、 アプリは型分離の保護の恩恵を 受けることができません そしてメモリタグ付け。
このようなシナリオでは、セキュリティを真剣に 評価する必要があります。 こうした実装を維持し続けることの意味。 つまり、私たちの提案が 何になるかはもうお分かりでしょう。
そのコードを、システムアロケータを 直接使用するように移行してください。 私たちは、それがアプリのセキュリティにとって 不可欠な基礎要素であると確信しています。 しかし、私たちは、 カスタムアロケータの使用をやめましょう。
そのため、26.1以降、 すべてのプラットフォームで、 SDKには、必要なすべての 構成要素が含まれています。 カスタムアロケータにMTの サポートを実装する。
それでは、それらを一つずつ 見ていきましょう。しかし、各アロケータの 実装にはそれぞれ独自の特性があるため、 理解するのはあなた 次第ですこれらの構成要素を、 ご自身のユースケースで活用してください。
まず最初に、実行時に MTが有効になっているかどうかを確認する 必要があります。そして、 ここに示されているように、 OSのセキュリティ設定APIを使用してこれを 行うことができます。 ハードウェアメモリタグを有効にした後、 アプリは、それをサポートするデバイスでは 空のオプションを有効にして実行されます。 しかし、同じコードは、 それをサポートしていない デバイスでも実行する必要があります。 空の命令セットに依存するすべての 操作を空にします。 建築は実行のみされるべきである プロセス内で実際に空の状態が 有効になっていることを確認した後。
次に、アロケータが使用する メモリページを割り当てると、 VMにメモリタグ付けをサポートする ページを提供するように 要求する必要があります。 VMフラグをバイパスする。 VM割り当て呼び出しで 空のフラグを使用しています。 この段階で、カーネルはページを提供します。 関連付けられたタグはすべてゼロになります。
そしてそれが理由です。 最後に、そして最も重要なことは、 割り当てにタグを付けたいのですね。 いくつか異なる可能性が考えられます タグ付け方式を選ぶ際には、 そして考慮すべき多くのニュアンスがあるだろう これらをそれぞれ実装する際に。
利用可能なAPIの概要を簡単に説明すると、 各場所にタグを付け直すという 簡単な例を考えてみましょう。 解放されたら、アロケータに戻ります。
タグ付けと割り当てに関しては、 2つの部分があります。 ターゲットを選択し、ポインタに含める そして、そのタグをタグ保存メモリに保存する。 タグを選択する最初のステップは、 除外マスクを生成することです。 現在割り当てに関連付けられている ターゲットに対して。 これにより、ハードウェアに 問い合わせることができます 以前使用したタグを除外して、 新しいランダムなタグを生成します。 空のランダムタグを生成すると、 ポインタが返されます。 同じメモリ領域に書き込むが、 上位ビットに異なるランダムなタグを付ける。
最後に、空のストアタグを使用して ハードウェアに問い合わせます。 新しく生成されたタグをタグストレージメモリに 保存するには、 新しいタグが付いたポインター そして、タグ付けが必要な基となる メモリブロックのサイズ。 アロケータでメモリにタグを付けるために 必要なのは、これだけです。
以上が、サポートを実装するために 必要な基本的なツールです。 メモリ割り当て器における メモリタグ付けのため。
これで、ミラノでの旅は終わりです。 しかし、結論を出す前に、 このセッションの重要なポイントを 改めてお伝えします。 そして、メモリ整合性の強制を最大限に 活用するためにできること。
ハードウェアメモリのタグ付けを 有効にする必要があります そして、Typekitアロケータのサポートを ユーザーに最も具体的な保護を提供するため 授業で使用するテクノロジー。 メモリ破損の脆弱性を軽減することに関しては、 導入はとても簡単です。 特に、大多数のシナリオでは、 これらの技術によって 得られるセキュリティ上のメリットをすべて 享受できますコードに 一切変更を加えることなく提供できます。
しかし、この講演で見てきたように、 保護効果を低下させる特定のパターンが存在する メモリ整合性強制機能によって提供されます。 コードベースを監査し、それらを特定することを お勧めします。そうすることで、 リスクへの曝露状況をより 適切に評価できるようになります。
これらのいずれかを特定したとき。 好ましい解決策のアプローチは移行である システムアロケータを直接使用する。
これはまさに、あらゆる面で 最高のものを手に入れることができる。
しかし、時としてこれが実行不可能な場合、 私たちはあなたにツールを提供しました そして、そのような抽象化を 適応させる必要があるという 理解メモリ、整合性、 深く根付いたセキュリティ特性を執行し、 維持するオペレーティングシステムに 組み込む。
それでは、オリバーをステージにお招きして、 制御フローの完全性についてお話いただきます。 ポインター認証付き。
ありがとう、フィリッポ。皆さん、こんにちは。 私はオリバー・ハントです。セキュリティと ツール開発を担当するエンジニアです。 Apple社内のセキュリティツールと コンパイラ。 セッションのこの時点で、 メモリ安全性のエラーから コードを保護する方法について学びました。 メモリ整合性の強制機能付き。 MIはメモリ安全性のバグを悪用することを 非常に困難にし、 しかし、あらゆる攻撃から 身を守ることはできない。 ハードウェアを導入する方法をご紹介します。 アプリケーションに提供するポインタ認証 制御フローの完全性を維持した上で。 つまり、攻撃者が私を迂回して 任意のメモリを破壊できたとしても、 彼らにはまだ能力がない アプリケーションが実行するコードを制御する ポインター認証付き。 ハードウェア、コンパイラ、 そしてオペレーティングシステムもすべて 連携して動作します 攻撃者が標的とするポインタの有効性を確保する アプリケーションを乗っ取ろうとしたとき。 仕組みはこうです。 ボンネットの下では、
ポインター認証を使用する場合、 CPUとオペレーティングシステムは ポインタの暗号署名を作成し、 そして、それらの署名をポインタ自体に 埋め込むのです。 これらの署名により、ハードウェアは ポインタの有効性をチェックできる。 使用される前に、コードに 信頼の連鎖を与えるポインタが 使用される時点の値を、 過去まで遡ってリンクする元の価値に、 そして
ポインタ認証はこれらのポインタを 保護し続けますマウスも 使用するアプリケーションにおいて。 署名を透過的に調整することで メモリタグ付けを拡張する また、既存のタグに 対応するための認証操作も行います。 これらすべてが揃った追加タグ、 攻撃者がポインタを改ざんしようとすると、 署名はもはや有効ではなく、 信頼関係は断ち切られた。
さて、後でコードがそのポインタを 使用しようとすると、 ハードウェアがこれを検知し、 アプリケーションを安全に停止します。 攻撃者は悪意のある コードを実行する前に阻止されました。
Appleでは、ポインター認証APIを 開発してきました。 ほぼ10年間、私たちはそれをプラットフォームを 保護するためのツールとして使用してきました。 その間ずっと。
そして、その設計は安定性と堅牢性を備えている ため、採用することが可能です。 そして、私たちが使用しているのと全く同じ保護機能をあなたの コードにも採用してください。 自社ソフトウェアを保護するため。
ポインタ認証は大きな変化をもたら すがコード生成に 関しては、アプリケーションに何らかの差異が 生じる可能性のある箇所。
それでは、主要なものを見ていきましょう。
返信先住所は長年攻撃者の標的となっており、 そして長年にわたり、 さまざまな緩和策が実施されてきた。 ポインタ認証でそれらを保護する。 これはさらに上のレベルへと引き上げられる。 電話をかけるたびに、相手方の 住所が分かります。 ポインタ認証では、戻りアドレス自体と、 現在のコールフレームに 関する情報も含まれています。 返信先住所に埋め 込まれている署名に反映させる。 この情報はその後も使用されます 返信先アドレスを追跡する前に、 そのアドレスを認証する必要があります。
これにより、返還の瞬間から 信頼の連鎖が維持されます アドレスが最初に記録され、 使用される時点まで、 さらに、攻撃者が有効な署名を 再利用することも防ぎます。 前回の呼び出しからのポインタ。
そしてこれらすべては、基本的な呼び 出し規約の一部として発生します。 また、アセンブリ言語で 記述された関数からは、ほとんど見えない。
さて、コードが相互作用する 場合関数呼び出しの基本を超えた コールスタックでは、また、 すべてのAPIがそして、 これを行うために 使用しているコンパイラ機能は、 スムーズに動作する。
それでは、実際に使用している 機能について見ていきましょう。 アプリケーションの動的な制御フローのため。
まずは関数ポインタから始めましょう。 これらは動的制御フローの最も 基本的な形態であるため アプリケーションには、あらゆる 種類の最も制限のない Cや C+ + などの言語における 間接的なコード実行の一形態。 だから、必ず署名してもらうようにしています。
これはC言語における 一般的な慣用表現として重要である。 C+ + では、関数ポインタを整数または 不透明ポインタにキャストします。 そして、私たちはあなたが常にこれを避けられるとは 限らないことを知っています。 そのため、ポインタ認証を確実に行いました このモデルは、これらの操作全体を通して 埋め込み署名を維持します。 コードを変更する必要はありません。
これは、信頼の連鎖が再び 維持されることを意味する。 攻撃者がこれらのポインタを変更できる ポイントは存在せず、 コンパイラはそれらが関数であることさえ 認識しなくなるかもしれないが ポインタ。
しかし、あなたのコードでは、 関数ポインタを使ってすべてを行う。つまり、 言語がサポートする動的ディスパッチを幅広く 活用しているということですね。
そしてそれは、Swiftの仮想メソッドで finalでないメソッドを呼び出す場合です。 C+ + でのメッセージ送信、または Objective-Cでのメッセージ送信など、 あらゆる言語に対応しています。 動的ディスパッチは、複数の間接参照層の 上に構築されています。
最も単純な形では、 各オブジェクトインスタンスは、何らかの 型情報へのポインターを持っている。 またはメソッドテーブル。
そしてそのデータ構造には別のポインタがあり、 各手法の実際の実装について。
この相互作用は、攻撃者にとって魅力的です。 なぜなら、これらの各層はそれぞれ 独立して攻撃できるからである。
そのため、 Swift、 C+ +、 Objective-Cでは、 私たちはこのサプライチェーンのあらゆる 段階を保護してきました。 そして、このチェーン内の各ポインタの 署名を作成すると、 オブジェクトの種類、 ID、 あるいは、オブジェクトの位置、 さらには対象メソッドの種類なども含まれます。
そして、動的呼び出しを行うと、 各ステップは同じ情報を使用して認証されます。
この作業をすべて行うことで、 私たちは、あなたのアプリケーションが 保護されていることを確認しました。 メモリ破損だけでなく、ライフタイムや 型の混同攻撃からも影響を受ける。
しかし、このレベルの保護は ポインターが認証にはコードの変更が 必要になる場合があります。 その理由を理解するために、 最初の認証ステップに注目してみましょう。
オブジェクトの位置を各署名に組み込むことで、 署名が常に有効であることを確認しました 記憶の中のこの場所に。
これにより、攻撃者がメモリ安全性のエラーを 利用することを防ぐことができます。 あるオブジェクトを別のオブジェクトの 上にコピーする。
しかし、Memcpyのような低レベル関数を 使用してこれらのオブジェクトをコピーすると、 それは攻撃者がやっていることと 根本的に何ら変わりません。 そして結果は同じになるだろう。 後でそのオブジェクトを使用しようとすると、 認証エラーが発生します。
これはポインタ認証が 変更されるケースの1つです 既存のコードが未定義になるのを防ぎ、 一見正常に動作するように見える動作が、 実行時に失敗する未定義の動作に変化する。
ここまで、最高レベルの保護についてのみ 説明してきました。 ポインター認証付き。 もっと奥深い配列もあるのですが、 それは皆さんが目にすることはないでしょう。
私たちはこの実装を設計しました 既存のコードと互換性を持たせるためです。 ですから、 皆さんもご自身のアプリケーションにぜひ 導入したいとお考えだと思います。
それでは、そのために 必要な手順をご説明しましょう。
さて、ご自身のアプリケーションで 採用を開始する前に。 埋め込むすべてのライブラリがまたは、 ポインター認証をサポートするリンク。
もしあなたがこれらのライブラリの 作成者であれば、 その作業はあなた自身で行うことになります。 しかし、外部で開発された ライブラリを使用している場合は、 取引先に連絡を取る必要があります ポインター認証を採用させる そして、汎用バイナリを提供します。
それが完了したら、自分のアプリケーションで 導入作業を開始できます。
それは、「拡張セキュリティを有効にする」 オプションを選択することで実現できます。 Xcodeプロジェクトのビルド設定で 設定してください。 これにより、メモリの整合性、 強制、およびポインタ認証が可能になります。
この操作を行うと、 Xcode が プロジェクトを自動的に構成します。 おなじみのArm64スライスを含む ユニバーサルバイナリを構築する ハードウェアで使用される 追加のArm64 eスライス ポインタ認証をサポートする。
一度に一つのことだけを取り 入れることに集中したいなら、 ポインタ認証のみに焦点を当てるには、 代わりに「ポインター認証を有効にする」 オプションを使用してください。
ポインターをすべて再設計しました。 ポインタ認証に基づく 保護機能はすべて、既存のコードでは、 そしてほとんどのアプリケーションは 構築されますそして、 これらの保護機能がすべて備わった 状態で正しく動作します。 しかしもちろん、それは保証ではありません。 次のステップは、アプリケーションを 徹底的にテストすることです。
さて、最初に遭遇するバグのいくつかは、 認証エラーによって検出されるようになった、 コード内の既存のバグ。 これは予想通りの結果だ。 これでこれらのバグがわかったので、 修正できるようになります。
しかし、書かれた コードを持っている可能性もある ポインタ認証と互換性のない方法で。
そして、それらについては、 いくつかの変更を加える必要があります。
これらの不適合の根本原因は 意図せず未定義の動作を引き起こした場合。
これらの操作は重複するため、 攻撃者が悪用するバグと重複することが多い。 彼らはまさにそうした保護措置によって 阻止されるのだ。
実際には、ほとんどのコードはこれらの 問題に遭遇しません。 しかし、最もよく使われる 情報源をいくつか簡単に説明しましょう。 これまでに発生した 互換性に関するバグについて。
私たちが目にした最も 一般的なパターンは、安全でない memcpyなどの関数を使用して、 ポリモーフィックなオブジェクトをコピーする。
memcpyや類似の関数を使うのが 安全でない理由については既に説明しました。 これをしているとき。 しかし、これらの電話を直接かけることはないかもしれません が、 コンテナ型には、これらの操作を実行する コードが含まれている可能性があります。 そして、まさにそこに、 こうした失敗の事例が見られるのです。 これらのエラーの修正方法は、 より高レベルの言語機能を採用することです。 データ構造、またはオブジェクトの コピー用のライブラリ関数を採用する そして初期化も、これらはあなたの言語に 組み込まれているからです。 彼らはあなたの所有物の種類を知っています。 そして彼らは正しい意味論を保証するために 必要なすべての作業を行うでしょう 可能な限り効率的に。
さらに稀な例としては、 関数ポインタの上位ビットに 余分なデータを格納する方法がある。 コードがこのような処理を行うと、 そのストレージによって署名が破損します。
これを修正するには、 スターターを移動する必要があります。 ポインタの下位ビットへ、 あるいは、そのデータをポインタの外に 完全に移動させるだけでも良いでしょう。
これらは私たちが目にした 最も一般的なパターンです ポインタ認証を採用した コードもありますが、まだ非常にまれです。 極めて大規模なコードベースの 場合でも同様です。
しかし、アプリケーションでこれらの パターンを認識している場合は、 それらは、あなた自身の養子縁組活動の 一環として対処する必要があるでしょう。
必要な変更を行った後テストの結果、 アプリケーションは期待どおりに 動作していることが確認されました。 あなたはそれをユーザーに展開するでしょう。
そして、どのリリースにも言えることですが、 新たなクラッシュの報告が 寄せられる可能性もあります。 そして、これらが認証失敗なのかどうかを 知りたいのですね。
この問題を診断するために、 プログラムが終了したら、 生成されたクラッシュログには 診断メッセージが含まれます。 認証エラーが原因である可能性があります。
ただし、クラッシュログを自分で 取得している場合は、 障害発生アドレスの上位ビットを 調べることができます 同様の診断結果を提供するため。
衝突が迫っていることを知っているだけで 認証失敗だけでは十分ではありません。 それでは、認証失敗の例を見てみましょう。 そうすることで、それらがどのように 表示されるか、 そしてどのように修正できるかを確認できます。 ここに、認証エラーが発生した 非常にシンプルなプログラムがあります。
Xcodeのデバッガーで エラーを検出した場合。 最初は他のクラッシュと同じように 見えるだろうが、
しかし、認証失敗によって発生する トラップは常に上位ビットを設定します 障害発生アドレスにおいて。 そして、それがここにある光景です。 さて、設定されている存在の正確な部分に 焦点を当てないでください。 それはハードウェアの世代によって 異なる場合があるからです。
そして、私たちの小さなテストプログラムでは、 この呼び出しの直後に障害が発生しています オブジェクトのコピー機能へ。 そして、私の既存のコードが データをコピーする際に、 無効なオブジェクトが生成される可能性がある。 それでは、その関数を見ていきましょう。
これは何かがうまくいかないデモなので、 当然のことかもしれませんが、この関数は、 Memcpyを使用して ポリモーフィックオブジェクトをコピーします。 なぜなら、以前はこれでうまくいっていたから だ。 この例を見れば、コード内でこのようなことが 起こるかどうかは非常に明白ですが、 これは、特注または特殊なケースのコンテナ内でのみ 発生する可能性が高い。 そしてデータ構造。
幸いなことに、コンパイラは既にこれらの 問題を見つけるのに役立っています。 これらの危険な作業に対して 早期に警告を発することで、 ポインタ認証が有効になっていない場合でも。
コードベースがそれを許可している場合。 これらの警告をエラーとして 扱うように設定してください。
これで問題点が分かりましたね。 どのように解決するかを決める必要があります。 そして、あなたにはいくつかの 選択肢があります。 それでは、いくつか例を見ていきましょう。
最も簡単な解決策は、型指定のない メモリアクセス関数の使用をやめることです。 それは、mem が標準ライブラリに コピーまたは移動したということです。 オブジェクトの移動、コピー、 初期化を行うための関数を提供します。
これらの機能により、 必要な作業が確実に行われます。 オブジェクトが正しく 初期化されるようにするために、 そして、安全であればMemcpyのような 関数を使用するでしょう。
しかし、すでにこれらの低レベル機能から 離れつつあることを考えると、 より高度な言語機能を直接採用することを 検討すべきです。
例えば、これらのコピー操作の安全な 同等物は次のようになります。 あなたの中に配置のようなものを使う。
私たちがこれまで見てきた 中で最も難しいケースで、 あなたにとっても非常に難しいケースです Memcpy を使用している 場合、修正するには、 なぜなら、オブジェクトの動的な型を 維持しようとしているからです。
この問題を解決するには、コードの構造を 再構築する必要があるかもしれません。 言語レベルの多相性のようなものを使う。 例えば、この例では、 コピーを仮想クローン方式に置き換えました。 あるいは、これらのコピーを実行するために、 型を考慮した手動ロジックを採用する。 つまり、タイプをチェックするという ことですね。 もちろん、これは非常に 基本的なデモプログラムです。 認証失敗がどのように表示されるかを お見せしますそして、 それを解決するためのアプローチ方法についても 説明します。
しかし、ほぼすべての言語における 認証失敗は修正されています 同様に、低レベルの操作から 脱却する必要があります。 代わりに、使用している言語が 提供するサポートを利用してください。 そのランタイムとライブラリ。
ポインタ認証がコードをどのように 保護するかについては、 これまで何度も説明してきました。 さらに、C++で書かれた例も見てきました。
しかし、ポインタ認証を採用する 必要はないと考えるかもしれません。 すでに養子縁組を済ませているので、 あるいは、Swiftのような安全な 言語を採用する過程にある。
しかし、それだけでは コードを保護するには不十分です。
その理由を理解するには、 攻撃者がどのように攻撃を仕掛けてくるのかを 見ていく必要があります。 コードをターゲットにする。
基本的に、攻撃者がアプリケーションを 制御するには、2つのものが必要です。 まず、彼らはあなたのアプリケーションの 状態を破壊するために 利用できるバグを見つける必要があります。
この例では、安全でない scanf 関数の使用により、攻撃者は 結果バッファをオーバーフローさせる。
第二に、彼らはその破損した状態の結果に 基づいて動作するコードを必要としている。
ここでは バッファオーバーフローを利用できます エラー処理関数の内容を上書きする 呼び出し元の関数内で エラーハンドラを呼び出す場合。 そのエラーハンドラが 呼び出されると、攻撃者が 選択した命令を実行します。 彼らはあなたのアプリケーションの 制御フローを引き継ぎました そして、彼らは望むあらゆる コードを実行できる。
ソフトウェアの悪用について考えるとき、 元のバグだけでなく、 もっと多くのことを考える必要があります。 攻撃者がそのバグを使って何をしようとしている のかを考える必要があります。 それでは、その発信者について 今すぐ見ていきましょう。 それは、これまで何度も目にしてきたような、 安全でないC言語または C+ + 言語のコードです。 では、安全な言語で 表現するとどうなるのでしょうか? ええと、この関数をSwiftで書き直しました そして、C言語版とほぼ同じです。 実際、エラーハンドラを上書きするために 使用されたのと全く同じエクスプロイトが C言語の関数は、Swift版でも 置き換えることができます。
繰り返しますが、 皆さんが理解しやすいように非常に 簡単な例を使用しています。 しかし、どの言語を使っても結果は同じです。 元のバグが何であれ、そして、 それがどれほど複雑に悪用されようとも。 それは、ユーザーが アプリを実行しているときに、 彼らは単にあなたのコードを実行している だけではありません。 彼らはすべてのライブラリから コードを実行しています あなたのコードと並行して実行されているもの。 だから、どの言語を使っていても、 コードは存在する可能性のある バグから保護する必要があります プロセス内で実行されている 安全でないコードのいずれか。
ポインタ認証を採用すれば、 それが実現できます。 攻撃者があなたのアプリケーションを侵害することが、 はるかに難しくなりました。 たとえそれを成し遂げたとしても メモリの整合性や強制などの 他の保護を回避するため。
しかも、既存のコードにほとんど、 あるいは全く変更を加えることなく、 この保護機能を実現できます。
これがポインター認証の使い方です。 攻撃者によるアプリの乗っ取りを防ぐため。
それでは、エンリコに ご参加いただきありがとうございました。 フィリッポと私は、 記憶、整合性、執行、ポインタ認証は ハードウェアによってユーザーを保護し、 コンパイラ、オペレーティングシステム、 そしてアプリケーションがすべて 連携して動作します。
きっとあなたは、これらのツールを使った作業は 実用的で、ほとんど、 あるいは全く必要ありません。 コードの変更、そしてそれは整合性に 大きなメリットをもたらしますそして、 アプリ全体の安全性。
ありがとうございました。それでは、 カートの話に戻りましょう。
-