最近の数ヶ月のセンセーショナルな出来事は、ソフトウェアの重大な脆弱性の問題がいかに緊急であるかを明確に示しています。 WannaCryおよびPetya ランサムウェアウイルスを使用するユーザーに対する大量攻撃は、Windowsネットワークサービス(SMBv1およびSMBv2)のゼロデイ脆弱性をリモートで悪用することによって実行されました。 リモートコード実行の脆弱性を見つけて悪用することは、明らかに簡単な作業ではありません。 しかし、最高の最高の情報セキュリティの専門家がそれを行うことができます!
NeoQuest-2017フルタイムツアーのタスクの1 つでは 、ネットワーク上で利用可能なコマンドインタープリタープログラムの脆弱性を見つけ、それらを使用してリモートサーバーでコードを実行する必要がありました。 ハッキングを証明するには、サーバー上のファイルディレクトリの内容を読み取る必要がありました。条件によっては、ジョブキーが見つかりました。 参加者はバイナリインタープリターファイルにアクセスできました。 だから、氷が壊れた!
勉強を始める
まず、 バイナリを実行して、それが何であるかを確認します。 実行可能ファイルがPE形式のインタープリターであり、Intel x64アーキテクチャ向けであることは明らかです。 また、 DEP / ASLRサポート付きでコンパイルされているが、 CFGなしでコンパイルされていることも明らかです。 サードパーティのライブラリもロードされません。
プログラム自体は、整数ベクトルを操作するためのコマンドの単純なインタープリターです。 サポートされているコマンドの構文は、プログラムに入るときに使用できます。 ベクターは、セル全体への書き込み、セルからの値の書き込みと読み取りをサポートしています。
表面分析が終わったら、逆戻りする時が来ました-ツアーはフルタイムで、時間がなくなっています!
そして、入り口に何を提出するのですか?
解決する必要がある最初の中間タスクは、攻撃対象領域の正確な決定です。 バイナリをIDA Proにロードすることにより、コマンドの数と形式を明確にします。 メイン関数は、画面に表示されている行を探すことで簡単に見つかります。
主な機能の分析により、次のことを述べることができます。
1. 1.最初に、 メイン関数のスタック上の配列arrayのアドレスが変数array_baseに入力されます。 ループでは、配列セルはゼロに初期化されます。 ループを終了する条件により、配列のサイズ-100(0x64)整数セルを見つけることができます。
2. 2.ようこそ画面が表示され、ユーザーコマンドが読み取られます。 行「cls」および「whoami」の存在は、 システム関数の呼び出しを示しています(将来、これは非常に役立つでしょう)。
3.変数の正規表現は、構文が正しいことを確認するために初期化されます。 文書化されていないコマンドはないことがわかります。 さらに、各命令の有効なパラメーターのセットが明確になります。
4.無限ループで一貫して、入力されたコマンドが正規表現に準拠しているかどうかがチェックされます。 いずれかのコマンドに一致するものが見つかった場合、コマンドの引数が計算され、そのハンドラーが呼び出されます。
メイン関数コードの分析により、文書化されていないコマンドがないことが明らかになりました。 以下のパラメーターに影響を与えることができます。
- set / getコマンドのインデックス。
- setコマンドに配置された値。
- 配列に書き込むバッファのサイズと内容。
ASLRとDEPの防御メカニズムが存在するため、2つの脆弱性を見つけて実装する必要があります。
- 実行可能メモリ領域のアドレス拡張の脆弱性-ASLRをバイパスし、DEPバイパスでROPチェーンを構築するため。
- 私たちが制御するアドレスへの制御フローの傍受の脆弱性-ROPチェーンとコード実行の先頭に制御を移すため。
ROP(Return Oriented Programming)は、DEP保護メカニズムをバイパスすることを目的とした最新の操作技術です。 この手法の本質は、 ret命令の前に実行可能メモリの小さなフラグメント(ガジェット)を再利用することです。 チェーンに並んで、ガジェットアドレスはスタックから順次削除され、ガジェット内のコマンドをretコマンドまで実行します。 彼女は次に、スタックなどから次のガジェットのアドレスを削除します。
脆弱性1
これで、ターゲット配列のサイズが一定であり、 メイン関数のスタック上にあることがわかりました。 配列の境界を超えるインデックスを持つセルを読み取ってみましょう。
まず、配列のサイズよりも大きい値で、正常な動作を確認します-エラーメッセージで終了します。 ただし、インデックス値が2147483647より大きい場合、プログラムはクラッシュするか、値を生成します。
負の数として扱われるインデックスは境界チェックに合格し、配列の境界外の負のインデックスでメモリの内容を返すようです。 脆弱性が見つかりました!
なぜこれが起こっているのですか? この脆弱性は、 set / getコマンドのインデックスの誤った処理にあります。 配列内の数値インデックスはsetコマンドのパラメーターとして読み取られ、 stoulを呼び出すことで文字列から数値形式に変換されます。 この関数は、 符号なし整数を返します。
ただし、パラメータをSET / GET関数に渡すと、同じ値が誤って符号付き整数 yに減少します。これは配列のサイズと比較され、符号付き比較コマンドjlの結果に影響します。 GETコマンドは、配列の先頭からカウントされたメモリセルの値を返します。
この機能を使用して、メモリから任意の実行可能アドレスを読み取ることができます。 配列はスタックに配置されているため、負のインデックスを持つ配列のセルを読み取ることで、配列が配置される前にスタックの内容を表示できます。 アーキテクチャはx64であるため、メモリ内のアドレスは8バイトを占有します。 配列からの読み取りは4バイト単位で実行されます。 したがって、メモリからアドレスを読み取るには、2つの連続したセルを印刷する必要があります。
デバッガを使用して、実行可能アドレスがあるバッファの前に実験的にそのようなメモリセルを見つけます-たとえば、4294967282 == -14および4294967283 == -13。 ROPチェーンの構築にそれらの値を使用します。
さらに、将来的には、操作中に、スタック上のバッファ自体のアドレスが必要になります。 彼を見つける方法は? main関数の先頭を見て、変数array_baseがバッファの先頭へのポインタを格納していることを確認してください。
スタックでは、この変数はrsp + 0x358-0x328にあり、バッファ自体はrsp + 0x358-0x198で始まります。
したがって、変数array_baseを読み取るには、配列の先頭から(0x328-0x198)/ 4 = 100個の4バイトセルだけ後退する必要があります。 検索されたオフセット:4294967196、4294967197。
この脆弱性により、プロセスメモリ内の実行可能アドレスを明らかにし、ROPチェーンのパラメーターを配置するための目的のバッファーのアドレスも発見しました。 次に、プログラムの制御フローをインターセプトする方法を見つける必要があります。
脆弱性2
これまで、 ロードインタープリター機能については調査していません。 それを詳しく見てみましょう。 すぐに、スタックでの古典的なバッファオーバーフローがここで検出されます。 キーボードから入力された文字列— loadコマンドの引数—が割り当てられ、 LOADハンドラー関数の最初のパラメーターとして渡されます。 2番目のパラメーターは、目的のバッファーのアドレスです。
LOAD関数は、サイズをチェックせずにこのバッファに周期的に書き込みます。 ご覧のとおり、書き込まれるバイト数は、入力文字列のサイズのみに依存します。
loadコマンドへの入力に十分な数の文字を渡すことにより、スタック上のリターンアドレスを書き換え、インタープリターを終了して、制御を制御するアドレスに制御を移すことができます。 スタック上のバッファのサイズと場所を考えると、プライマリコントロール転送アドレスは4 *(100 + 2)バイトのオフセットで配置する必要があります(スタックに格納されたrbpレジスタの値をさらに8バイトで上書きします)。
オーバーフローしたバッファは、ヒープではなくスタック上にあるため、 ロードコマンドの入力パラメータのオフセット4 *(100 + 2)から始まるROPチェーン全体がそこに配置されます。
これですべての材料が準備できました。 彼らから戦闘のエクスプロイトを構築する時が来ました!
やった!
ジョブキーを取得するには、サーバー上のローカルディレクトリの内容を読み取って表示する必要があります。 分析の前半で、あいさつを表示する前に、画面の内容がクリアされ、ユーザー名があいさつに含まれていることがわかりました。
明らかに、これを実行し、 「cls」および「whoami」文字列をパラメーターとして受け入れる関数は、システムライブラリ関数と同様に動作します。 この関数を呼び出す必要がありますが、実行するコマンドとして「dir」を使用し、サーバー上のローカルディレクトリの内容を表示します。
これを可能にするROPチェーンを構築します。 操作のシーケンスは次のとおりです。
- 脆弱性がトリガーされた時点でわかっていたアドレスに文字列「dir」を配置します。
- この行のアドレスをrcxレジスタに入れます。
- system_func関数に制御を渡します。
バッファにアドレスを配置するとき、ASLRの存在を呼び出して、実行可能メモリの以前に読み取ったアドレスとバッファの先頭を使用して動的に計算する必要があります。 さらに、それらはリトルエンディアン表記法に従って、つまり下位バイトから上位バイトまでバッファに書き込まれます。
次に、インタープリターの実行可能ファイルで必要なガジェットのオフセットを見つける必要があります。 1つ目は文字列「dir 」のアドレスをrcxに入れるために必要で、2つ目はsystem_fun c関数を呼び出すために必要です。 インタープリターの実行可能ファイルのコードセクション内のオフセット0x24c00および0x16ce6には、それぞれ必要なコードセクションがあります。
ガジェットの後に文字列「dir」を配置します。 したがって、そのアドレスは、バッファーアドレスよりも4 *(100 + 2)+ 6 * 4バイト大きくなります。
以下は、バッファーの内容を生成するためのスクリプトのバージョンです。
from __future__ import print_function import sys n = 102 f=open("buf.txt", "w") base_l = 0x2f150000 - 0x0 # get 4294967282 base_h = 0x7ff6 # get 4294967283 buffer_l = 0x0afcf6e0 # get 4294967196 buffer_h = 0xe8 # get 4294967197 def rev(x): return ((x << 24) & 0xff000000 | (x << 8) & 0x00ff0000 | (x >> 8) & 0x0000ff00 | (x >> 24) & 0x000000ff) arr = [ base_l + 0x24c00, base_h, # pop rcx ; ret buffer_l + n*0x4 + 6*0x4, buffer_h, # buffer ptr - in rdi base_l + 0x16ce6, base_h # &system in our binary ] for i in range(0,n): # dumb print("%08x" % rev(0xdeadbeef), end='', file=f) for i in arr: print("%08x" % rev(i), end='', file=f) # "dir\0" print("%08x" % 0x64697200, end='', file=f) f.close()
すべて準備ができました。鍵を取りに行く時間です!
- リモートサーバーに接続し、getコマンドで必要なアドレスを見つけます。
- それらに基づいて脆弱なバッファを生成します。
- loadコマンドを実行し、main関数からのリターンアドレスを書き換えます。
- exitコマンドを実行します。これにより、メイン機能が完了し、エクスプロイトが起動します。
検索キー: fb520eb552747437c09f2770a9a282ea
結果は何ですか?
NeoQUESTでは、情報セキュリティのさまざまな分野からの知識を必要とするさまざまなタスクを収集し、セキュリティに対する不注意な態度に満ちているものを示すことができます:弱いパスワード、貧弱なサーバー実装(ファジングによって脆弱性を検索し、この記事で詳しく説明します )、不安定な暗号。 そして、このタスクの例では、プログラミングの過失が情報のセキュリティに重大な損害を与える可能性があることが明確に見られます。