Pwn iNt All! スクリプトエンジンの脆弱性を発見し、Windowsのユーザーモード保護メカニズムを明らかにします。 パート1







オペレーティングシステムは微妙な問題です。 そして非常に保護されています! 防御メカニズムを回避することは複雑で時間のかかる作業であり、魔法に少し似ています。 しかし、彼らは、母親の息子の息子がすでにいくつかのRCE脆弱性を発見し、エクスプロイトを作成し、すべての防御メカニズムをバイパスしたと言います。 私たちも試してみます!



過去のNeoQUEST-2018のタスク番号11の例でやってみましょう。 タスクは現実的です-ブラウザでJavascriptエンジンの操作中に発生するものに似た、リモートコード実行用の「ライブ」スクリプトを検討してください。 特定のインタープリターで脆弱性を探し、ARWプリミティブを受信して​​それらを実装します。その結果、ユーザーモードのWindows OS保護をバイパスします。 このタスクを渡すのは非常に困難であることが判明したため、成功への道の詳細な説明とともに一連の記事全体を取り上げます。



伝説によると、 バイナリプログラムと、このサービスがスピンしているアドレスがあります。 また、成功への最初のステップは特定の「キー」機能であることも言及されています。



それでは始めましょう!



キー検索



ソースデータを検討します。 ターゲットプログラムは、数値、ベクトル、行列の3種類のデータを操作するための一種のコマンドインタープリターです。 helpコマンドを使用します-これにより、サポートされているコマンドのセットがわかります。 インタプリタでは次のことができます。















プログラムは、コマンドを処理する無限のサイクルとして実装されます。











また、各チームは個別の正規表現で表されます。









そのため、「キー」機能を検索する際に、世界のすべての言語で「キー」という単語に関連付けられた特性線のバイナリをチェックします。 目的のフラグメントをすばやく見つけます。

















バイナリにはキーがないように見えます(これは論理的です!)。 どうやら、同じバイナリがサーバー上で回転しており、引数aFirstKeyIs2c7fの行に正しいキーがあります! しかし、リモートサーバーから変更された行の内容を取得する方法は?



初等分析では、見つかった関数を呼び出すだけでよく、キーのある行が標準出力に出力されることを確認しています。 この関数は、あるクラスの仮想メソッドであると思われます。





















したがって、作業の最前線は明確です。サーバー側のバイナリで目的の仮想メソッドを呼び出す方法を学ぶ必要があります。 2つのシナリオがあります。



  1. 目的のメソッドを呼び出す正当な機会を見つけてください。 理論的には、これはプログラムの文書化されていない機能である可能性があります。
  2. 脆弱性を検索します。 任意のコードを実行する魔法をマスターできるものを見つけた場合、簡単に目的のメソッドを呼び出してキーを取得できます。




どこから始めますか?



ブックマークは甘い



まず、文書化されていない機能のコードを分析しましょう。 明らかに、入力行処理関数にはそれらが含まれていません。ここで使用される正規表現は、上記のコマンドにのみ適用されます。 また、コード内でこの関数を直接呼び出すことはありません。 おそらく、文書化されたコマンドの中に、間接的に必要な「キー」関数を呼び出す隠された可能性がありますか?



しかし、違います。 一部のコマンド(find、what)には仮想メソッドの間接呼び出しが含まれていますが、呼び出されたメソッドの選択に影響を与えることは不可能です。vtableのオフセットはどこでも固定されています。























悲しいかな、失敗! 今こそ、「キー」機能を呼び出すことができる脆弱性の言い訳を探す時です。



求める者は常に求めるものを見つける



私たちはマザープログラマなので、インタプリタ用のコマンドのランダムで構文的に正しいセットを生成する簡単なファザーを作成しようとします。 ファザーJavascriptとDOMは、ブラウザーで脆弱性を検索するときに同じ方法で作成されます。 ファザーは、インタープリターを「ドロップ」することを期待して、コマンドのさまざまなシーケンスを生成します。 次の一連のコマンドを生成するときのファザー操作の原理は次のとおりです。



  1. 最初に、さまざまなタイプとサイズの変数が多数作成されます。 変数の名前とタイプは、後で使用するために保存されます。
  2. 次に、使用可能な変数のリストに従って、各コマンドがランダムに生成されます。

    • 任意のコマンド(パラメーターを除く)がリストから選択されます:新しい変数を作成するコマンド、算術演算、割り当てコマンド、検索、印刷コマンド。
    • コマンドのタイプに応じて、任意の引数が追加されます。 たとえば、findコマンドの場合、使用可能な変数のリストから任意の名前+任意の番号(2番目の引数)が選択されます。 算術演算コマンドの場合、パラメーターの数は多く(演算記号、変数、ベクトルと行列のインデックス)、それぞれが任意に選択されます。
    • 生成されたコマンドが現在のシーケンスに追加されます。 一部のコマンドは新しい変数を作成し、既存の変数のタイプを変更できるため、これらの場合、変数を含むリストが更新されます。 これにより、後続のコマンドを生成するプロセスで新しい/変更された変数を使用できるようになります。
  3. インタープリターはデバッガーの下で実行されます。 生成されたシーケンスは、インタープリターへの入力に送信されます。 送信されたコマンドの処理中に起こりうるクラッシュの登録。


III ...勝利! ファザーを起動した後、インタープリターが「落ちた」脆弱なコマンドシーケンスをすばやく見つけることができました。 結果は満足のいくものです-最大2つの異なる脆弱性!



脆弱性No. 1(UaF)



最初のシーケンスを受信し、それから不要なコマンド(インタープリターの落下に影響しない)をクリアすると、次の概念実証が得られます。











よく見てみましょう。 わかりますか? 私たちの前には、 Use-After-Freeのような古典的な脆弱性があります ! この一連のコマンドで起こることは次のとおりです。



  1. 大きなvecベクトルとintval整数が作成されます。
  2. vecベクトルのコピー-変数vec2が作成されます。
  3. 変数vec2には 、別のタイプの変数(整数) 割り当てられます。
  4. 元のvecベクトルを操作しようとすると、解放されたメモリに書き込みが行われ、それに応じてプログラムがクラッシュします。












インタープリターはCopy-On-Writeの概念を実装しているようです。その結果、変数vecvec2にはメモリ内の同じバッファーへの参照が含まれていました。 変数の1つを再割り当てすると、バッファは別の変数で参照されているという事実を考慮せずに解放されました。 その後、 vec変数には解放されたメモリへのポインタが含まれ、読み取りおよび書き込みのためにアクセスできました。 その結果、この脆弱性により、他のプログラムオブジェクトが存在する可能性のあるメモリへのランダムアクセスが可能になります。



脆弱性No. 2(整数オーバーフロー)



2番目の問題は、マトリックスオブジェクトのコンストラクターに関連しています。 数値配列にメモリを割り当てるときに、マトリックスの幅Wと高さHの検証が正しく実装されていません。











設計者は、 W * H整数セルにメモリを事前に割り当てます。 積W * Hが2 32 -1より大きい場合、整数オーバーフローのために非常に小さなバッファーが割り当てられます。 これは、クラスコンストラクターのバイナリで直接確認できます。











ただし、この配列のセルへの後続のアクセスの可能性は、 WHの大きな境界によって決まり、 W * Hのオーバーフロー値によっては決まりません。











この脆弱性は、最初の脆弱性と同様に、許容範囲を超えてメモリの読み取りと書き込みを可能にします。



見つかった各脆弱性により、RCEの神になり、任意のコードを実行するという望ましい結果を達成できます。 両方のケースでこの手順を検討してください。



私はあなたをコントロールします!



ヒープ上の空きメモリへのランダムアクセスの可能性は、悪用の強力なプリミティブです。 プロセスヒープ内の他のオブジェクトの制御された変更のために、破損したvecオブジェクト(最初の場合はベクトル、2番目の場合はマトリックス)を使用する必要があります( 犠牲者と呼びましょう)。 犠牲オブジェクトの内部フィールドを適切に変更することにより、任意のコードの実行を実現します。

まず、数値、ベクトル、行列のオブジェクトがメモリ内にあるものを理解します。 もう一度バイナリを見てみましょう。入力コマンド処理関数からそれほど遠くないところに、対応するクラスのコンストラクター呼び出しがあります。 それらはすべて基本クラスの継承者であり、同様のコンストラクターを持っています。 たとえば、ベクトルオブジェクトのコンストラクタは次のようになります。











オブジェクトを作成するとき、基本クラスのコンストラクターが呼び出され、オブジェクトのフィールドはそのタイプに従って入力されます。 フィールドの目的と順序を見ることができます:仮想関数テーブルアドレス、ベクターサイズ、バッファアドレス、数値識別子としてのオブジェクトタイプ。

破損したvecオブジェクトでアクセス可能な解放されたメモリ領域に犠牲変数が表示されることを確認するにはどうすればよいですか? vecオブジェクトを損傷状態にした直後に、プロセスのヒープに新しいオブジェクトを作成します。 また、解放されたメモリに分類されるオブジェクトを検索するにはインタープリターの検索コマンドを使用します 。 作成したオブジェクトのフィールドの1つに、ある種のマジックナンバーという珍しい値を入れましょう。 明らかに、バッファサイズはこれに最適です。 新しいオブジェクトを作成するときにユーザーが直接設定します。 破損したvecオブジェクトのバッファーでこの値の検索が成功した場合、目的のオブジェクトが見つかりました!

両方の脆弱性に対する被害者オブジェクトの場所を示します。 この段階でのアクションは、互いにわずかに異なります。



UaFタイプの最初の脆弱性の場合、 findコマンドはメモリの限られた領域を検索します-そのサイズはvecオブジェクトの解放されたバッファのサイズと一致します。 ヒープ内のオブジェクトの割り当ては、システムアロケータによって実行され、確定的なプロセスではありません。 元のvecオブジェクトと作成されたオブジェクトからバッファーサイズを選択し、それらの一部が少数の試行で適切なメモリーに入るようにします。











さて、被害者オブジェクトの被害者 (この場合はv1 )を発見しました!



2番目の脆弱性を使用して同じシナリオを実行すると、すべてが多少複雑になります。 この場合、マトリックスの幅Wまたは高さHが大きくなるため、メモリ全体で検索が実行される可能性があります。 ただし、バッファのすぐ近くに必要な数がない場合、検索サイクルはアドレス空間にマップされたメモリの境界を超えます。 これは、切望されているマジックナンバーを見つけるまで、インタープリタープロセスの崩壊につながります。 この問題は、メモリ内の多数のオブジェクトを割り当てることで解決されます-少なくとも1つのオブジェクトが、バッファのすぐ近くでメモリに入ります。























ビンゴ! この場合、 被害者オブジェクトも見つかりました!



そして今、最も興味深い部分は、 犠牲オブジェクトを変更する方法と、それで必要なコードの実行を開始する方法を正確に理解する必要があるということです。 したがって、「vector」クラスのオブジェクトを解放されたメモリに正常に配置しました。 概略的には、次のようになります。











被害者オブジェクトは、 vecオブジェクトを使用してアクセス可能なヒープセクションにあり、 被害者オブジェクトのフィールドを読み取り、変更することができます。 また、 vecオブジェクトのバッファ内の数値bufsizeのオフセットも知っています。 次に、 犠牲オブジェクトを次のように変更します。



  1. vtableのアドレスを読み取り、それによってメモリ内の実行可能アドレスを明らかにします(そしてASLR保護を無効にします)。
  2. インタープリターバイナリを調べた後、ベクターオブジェクトのテーブルvtableのアドレスと「キー」関数のアドレスとの一定の差を見つけます。 これにより、サーバー側の「キー」機能のアドレスを計算できます。













  3. 犠牲オブジェクトのバッファに偽のテーブルvtableを直接作成します。そこで、「キー」関数の計算されたばかりのアドレスを書き込みます。
  4. 犠牲オブジェクトの真のテーブルvtableのアドレスを偽のアドレスに置き換えます。
  5. 犠牲オブジェクトでwhatコマンドを呼び出します。 vtableの操作のおかげで、真の仮想メソッドの代わりに、「キー」関数が呼び出されます。 個々のROPガジェットではなく、関数全体を呼び出すため、Contol Flow Guardの保護は関数の呼び出しを妨げません。










必要な関数を呼び出すこのスキームは、両方の脆弱性で同じです。



私たちは成功から一歩離れていますが、すべてをまとめて鍵を手に入れることが残っています! 以下では、両方の例を検討します。

これは、Use-After-Free脆弱性の有効なエクスプロイトのコードです。











解放後使用の脆弱性を悪用するコード
var vec[4096] var m2:=vec var va=3 m2:=va va=find(vec,256) var v1[256] va=find(vec,256) var vtb=0 vtb=va-1 var buf=0 buf=va+1 var func=0 func=vec[vtb]-1547208 func=func+57632 v1[0]=func vec[vtb]=vec[buf] what v1
      
      









など-整数オーバーフロータイプの脆弱性の場合:











整数オーバーフローの脆弱性の悪用コード
 var m[65537][65536] var v1[256] var v2[256] var v3[256] var v4[256] var v6[256] var v5[256] var v7[256] var v8[256] var v9[256] var v10[256] var v11[256] var v12[256] var v13[256] var v14[256] var v16[256] var v15[256] var v17[256] var v18[256] var v19[256] var v20[256] var v21[256] var v22[256] var v23[256] var v24[256] var v25[256] var v26[256] var v27[256] var v28[256] var v29[256] var v30[256] var v31[256] var v32[256] var v33[256] var v34[256] var v35[256] var v36[256] var v37[256] var v38[256] var v39[256] var v40[256] var varr=0 var varc=0 varr=find(m,256).row varc=find(m,256).col var vtb=0 vtb=varc-1 var buf=0 buf=varc+1 var func=0 func=m[varr][vtb]-1547208 func=func+57632 v1[0]=func v2[0]=func v3[0]=func v4[0]=func v5[0]=func v6[0]=func v7[0]=func v8[0]=func v9[0]=func v10[0]=func v11[0]=func v12[0]=func v13[0]=func v14[0]=func v15[0]=func v16[0]=func v17[0]=func v18[0]=func v19[0]=func v20[0]=func v21[0]=func v22[0]=func v23[0]=func v24[0]=func v25[0]=func v26[0]=func v27[0]=func v28[0]=func v29[0]=func v30[0]=func v31[0]=func v32[0]=func v33[0]=func v34[0]=func v35[0]=func v36[0]=func v37[0]=func v38[0]=func v39[0]=func v40[0]=func m[varr][vtb]=m[varr][buf] what v1 what v2 what v3 what v4 what v5 what v5 what v6 what v7 what v8 what v9 what v10 what v11 what v12 what v13 what v14 what v15 what v16 what v17 what v18 what v19 what v20 what v21 what v22 what v23 what v24 what v25 what v26 what v27 what v28 what v29 what v30 what v31 what v32 what v33 what v34 what v35 what v36 what v37 what v38 what v39 what v40
      
      







やった! キーが受信され、それに対するボーナスは重要な情報です。 2番目のキーがありますが、サーバー側のバイナリの隣のファイルにあります。 この問題を解決するには、ROPチェーンを構築する必要があります。つまり、CFGをバイパスする必要があります。 しかし、それについては次の記事で詳しく説明します!



そして、 NeoQUEST-2018で少なくとも1つのタスクを完了したすべての人に賞品が与えられることを思い出させてください! メールで手紙を確認し、届かない場合はsupport@neoquest.ruにメールしてください



All Articles