web-dev-qa-db-ja.com

ジャンプ指向プログラミング(JOP)の概念

ジャンプ指向プログラミングの概念がわかりません。誰かがこれを理解しやすい方法で私に説明してくれますか?

JOPは、バッファオーバーラン攻撃からソフトウェアを保護するために実装されたセキュリティ測定により進化した概念であることがわかります。

バッファオーバーラン攻撃では、ソフトウェアは入力の境界チェックを実行しない関数を使用します。したがって、配列には実際に格納する必要があるよりも多くの値を割り当てることができるため、スタックメモリを上書きできます。

当初、シェルコードはスタックに書かれていました。私は想像しますmain()の戻りアドレスは、スタックの他の場所へのポインターで上書きされました。スタックのこの特定の場所に、シェルコードが挿入されました。ただし、データ実行防止機能が導入されたため、スタックからのコードの実行が不可能になりました。さらに、ASLRはメモリをランダム化するために導入されました。 ASLRがランダム化するメモリはここですか?スタックアドレスにすることはできますか?

カナリアも導入されました。これは、特定の値を持つことがわかっているスタック上のメモリの場所です。関数から戻る前に、スタックはカナリアが変更されていないことを確認します。

Return-Oriented-Programmingは、DEP、ASLR、カナリアが存在する場合でも、バッファオーバーランの脆弱性を利用できるようにする概念です。どのように機能しますか? ROPでは、シェルコードはシステムライブラリの関数呼び出しのみで構成されています。またはこのようなもの(許してください、私はこれすべてではそれほど良くありません。だから私は本当にあなたの助けが必要です!)。

したがって、ROPでは、バッファオーバーランの脆弱性がこのような方法で悪用される可能性があります。 main()の戻りアドレスは、libcへのポインターで上書きされます。 libcにはシステム関数があります(execveを除いて、実際には何も知りません)。この関数は確かに実行可能です。そして、それらはシェルコードの作成に使用できます。

では、システムをROPから保護するにはどうすればよいでしょうか。そして、この保護はどのようにしてJOPを通じて無効になるのでしょうか。誰かがJOPを理解するのを手伝ってくれませんか?

なんでもありがたいです!

4
user503842

あなたの質問の多くはここで重複していますので、私はそれらを非常に簡単にカバーします。これはすべて、エクスプロイト開発者とOS /コンパイラ開発者の間の何十年にもわたる武装競争であったと言えば十分でしょう。

最初はエクスプロイト保護はありませんでした。スタックバッファーオーバーフローのバグを見つけ、それを使用してスタック上の現在の呼び出しフレームの戻りアドレスを上書きし、悪用された関数がその終わりに達して戻るのを待つことで、指示できる命令ポインター(IP)を制御できますエクスプロイトペイロード(シェルコード)の一部としてスタックバッファーに入れた命令に戻り、任意のコードを実行します。

次に、スタックCookieが来ました。これらは、リターンポインタの直前にランダムに生成された値です。各エピローグの各関数の最後で、関数はCookie値が正しいことを確認してから戻ります。スタックアドレスを上書きしてスタックアドレスを上書きすると、スタックCookieが上書きされ、関数の最後にチェックされ、コードが実行されなくなります。これを回避するために、攻撃者は代わりに、通常スタックフレームの最後であるがリターンポインターの前に出現する例外処理構造(SEH)をターゲットにし始めました。 SEH構造を上書きすることにより、OSをだまして、制御しているアドレス(通常はシェルコードで上書きしたスタックバッファー)で例外ハンドラーを実行させることができます。

これを修正するために、OS開発者はいくつかのアプローチをとりました。 1つはSafeSEHでした。これは、有効な例外ハンドラアドレスの読み取り専用テーブルを実行可能構造の一部としてテーブルに保持し、例外ハンドラアドレスがそのテーブルのアドレスの1つであることを確認してから、制御フローをそこにリダイレクトします。 x86-64 Windowsシステムでは、SEH構造を格納するためにスタックがまったく使用されなくなったため、この攻撃は完全に軽減されていますが、ここでは少し先んじています。

その間、いくつかの進取的なエクスプロイト開発者が、ヒープ(スタックではない動的メモリ)のバッファオーバーフローと、それらで何ができるかを調べ始めました。ヒープバッファオーバーフローにより、命令ポインターの制御、つまりコードの実行につながったポインター(イベントハンドラー、コールバックなど)を上書きできる場合があります。繰り返しになりますが、攻撃者は通常、オーバーフローしたのと同じバッファにシェルコードを配置します。

上で述べたもう1つのアプローチは、データ実行防止(DEP)でした。これは少し混乱を招く問題です。初期の頃、Microsoftは、本当にDEPでなくてもSafeSEHを「ソフトウェアDEP」と呼んでいたからです。したがって、「ソフトウェアDEP」がどこかに書かれている場合、それらはSafeSEHを意味します。最近DEPと呼ばれることが多いハードウェアDEPは、プロセッサにページアクセスフラグを適用するハードウェア拡張を中心に構築された機能です。ほとんどの人はこの機能をNX(No Execute)と呼んでいますが、XD(eXecute Disable)と呼ばれることもあります。私はそれをNXと呼びます。 NXが行うことは、プロセッサーが実行不可とマークされたページを実行できないようにすることです。命令ポインタを実行不可能なページにリダイレクトしようとすると、プロセッサは保護違反をスローします。オペレーティングシステムは、NXを使用してDEPを実装し、スタックとデータセグメントを実行不可としてマークしました。これにより、シェルコードをスタックまたはヒープバッファにドロップしてそこで実行するだけでは不可能になりました。これらのページは実行不可能になり、CPUはコードの実行を拒否しました。

これは、ROP、別名ret2libc(同じことのLinuxism)が出てきた場所です。シェルコードをスタックまたはヒープに入れて実行する代わりに、ROPは代わりに、一緒にチェーンできるライブラリコードの小さなチャンクを見つけましたコード実行を取得するため。エクスプロイトの最初の部分は、何らかの方法で(たとえば、ヒープの破損、UAFなどを介して)命令ポインターの制御を取得し、ROPチェーンデータでスタックを埋めるか、スタックポインターを上書きして、 ROPチェーンデータ(これはスタックピボットと呼ばれます)。どちらの方法でも、スタックポインターは最終的にROPチェーンの先頭を指します。 ROPチェーンの各「ガジェット」は、「何かをして、戻る」という形式の小さなコードスニペットです。これらのガジェットのアドレスを次々にスタックに配置することにより、アプリケーションは最初のガジェットにジャンプし、いくつかの命令を実行し、return命令をヒットします。これにより、スタックから次のガジェットアドレスが読み取られ、実行がそのガジェットに転送されます。 ROPチェーンの終わり。通常、チェーンはLinuxのexecまたはWindowsのCreateProcessのような関数を呼び出します。もう1つの方法は、OSのメモリ保護関数(WindowsではVirtualProtectなど)を呼び出し、それを使用して、制御するメモリのセクションを実行可能としてマークし、ROPチェーンの最後の項目をリダイレクトすることです。シェルコードを実行できるように、そのメモリに。きちんとね?

この攻撃の重要な部分は、これらのガジェットがメモリ内のどこにあるかを知る必要があることです。これを困難にする(または境界線を不可能にする)ために、OSベンダーはアドレス空間レイアウトランダム化(ASLR)を導入しました。これにより、スタックや実行可能モジュールなど、メモリのさまざまな重要なビットのベースアドレスがランダム化されます。 ASLRの実装が改善されたため、ランダム化が進み、ランダム化の予測が難しくなっています。アイデアは、攻撃者がターゲットプロセスのメモリのどこにモジュールがあるかを知ることができない場合、ROPチェーンを構築できないということです。ただし、残念ながら、ASLRを解除するためにプロセスに読み込まれるのは1つの非ASLRモジュールだけです。新しいオペレーティングシステム(完全にパッチされたWin10など)では、すべてのモジュールにASLRを強制するポリシーを構成できますが、これはデフォルトではありません(私の知る限り)。 ASLRは、ポインターリークを使用してバイパスすることもできます。これらは、プロセス内の一部の実行可能メモリのアドレスが攻撃者にリークされたときに発生します(たとえば、UAFバグ、または不十分なAPI設計によって)。攻撃者は、リークしたポインタからモジュールベースを計算し、それを使用してROPチェーンを構築できます。

JOPはROPに非常に似ています。これは、スタック保護が使用されている場合に役立ちます。これにより、スタックバッファーの上書き、スタックピボット、または戻りアドレスフィルター(部分的な制御フローの強制の形式)が防止されます。これにより、ヒープの破損、UAFなどによるヒープのみの悪用が可能になります。

スタック上に一連のROPガジェットアドレスを割り当て、各ガジェットの最後のRETを使用して次へジャンプする代わりに、JOPはヒープ内のジャンプテーブル(ROPチェーンと同じですが、代わりにJOPガジェットを使用)と発車係。各JOPガジェットは、「何かを実行する、間接ジャンプ」の形式のライブラリコードのチャンクです。 _add rcx, 4; jmp [rdi]_。 ROPのように、各ガジェットが直接次のガジェットにジャンプする代わりに、各ガジェットは「ディスパッチャー」ガジェットにジャンプして戻ります。

たとえば、次の2つの指示を検討してください。

_dispatch:
  add rax, 8
  jmp [rax]
_

この命令ペア(または互換性のあるもの)は、ライブラリコードのどこかでかなり簡単に見つけることができます。攻撃者がraxをディスパッチテーブルのアドレス(たとえば、それらが埋めたヒープバッファー)に設定し、命令ポインターをdispatchのアドレスに設定できる場合、それらは一緒にチェーンできます。 JOPガジェットは、次のようにコードを実行します。

  1. 攻撃者はディスパッチテーブルを既知のヒープアドレスにロードします。
  2. 攻撃者は、raxrdx、およびripを設定するバグを悪用して、raxがステップ1で割り当てられたディスパッチテーブルを指すようにします。rdx上記のようにディスパッチテーブルガジェットを指し、ripもディスパッチテーブルガジェットにリダイレクトされます。
  3. ディスパッチ関数はraxをインクリメントするため、raxはディスパッチテーブルの次のエントリを指すようになりました。
  4. ディスパッチ関数はraxのアドレスにジャンプし、そこでガジェットを実行します。
  5. ガジェットは何かを実行してから、_jmp [rdx]_で終了します。これにより、ディスパッチガジェットに制御がリダイレクトされます。
  6. 手順3に進み、チェーンが完了してエクスプロイトが成功するまで繰り返します。

ここのレジスター(raxおよびrdx)は単なる例であり、汎用レジスターに置き換えることができます。

JOPの詳細については、論文 "Jump-Oriented Programming:A New Class of Code-Reuse Attack" を参照してください。

ただし、要するに、JOPは、スタック保護が設定されているためにスタックを悪用できない場合にのみ非常に役立ちます。

JOPは、制御フローガード(CFG)などの制御フロー強制を使用して軽減できます。 CFGなどの保護機能は、プログラム内の間接呼び出しサイトを識別し、それらが指すことができる有効なターゲットアドレスのテーブルを作成します。たとえば、C++でtypedef void (*SomeCallback)(int, int);として定義されたコールバックがある場合、コンパイラは、そのシグネチャを持つ関数(たとえば、void MyCallbackImplementation(int foo, int bar) { ... })のみが有効なターゲットであることを認識しています。次に、コンパイラーは、ターゲットジャンプアドレスが有効であることを確認するチェックを各間接ジャンプ命令の前に挿入します。そうでない場合は、プログラムを終了します。

もう1つのROP/JOPトリックは、アラインされていないガジェット、つまり、コンパイラーが生成したときに解釈されることを意図していたために命令で構成されていないガジェットを使用することですが、代わりに、命令の途中。これらはコンパイラによってある程度軽減することもできますが、この特定のタイプの保護がどれほど一般的であるかはわかりません。

うまくいけば、これであなたはスピードを上げることができます。この回答はすでに非常に長く、さまざまな悪用防止のトピックを非常にすばやくカバーしているので、いくつかのことについて説明しました。一部の詳細が欠落しているので、少しずつ微妙なニュアンスを理解できるように、各保護を1つずつバイパスすることをお勧めします。

10
Polynomial