KillDiskの解体: BlackEnergy破壊コンポーネントのリバース

[post-views]
2月 29, 2016 · 19 分で読めます
KillDiskの解体: BlackEnergy破壊コンポーネントのリバース

BlackEnergy脅威の長い導入を省略して、「ololo.exe」として一般に知られるマルウェアコンポーネントの調査に直接移りましょう。KillDiskは、APT操作中にデータ破壊や混乱・気を引くために設計されたBlackEnergyフレームワークのモジュールです。

https://www.virustotal.com/en/file/11b7b8a7965b52ebb213b023b6772dd2c76c66893fc96a18a9a33c8cf125af80/analysis/今日の分析で使用する主なツールは Process Monitor 、これはMark RussinovichによるSysInternalsユーティリティの一部です。 IDA Pro Disassembler。すべての操作はWindows XPオペレーティングシステムに基づいた仮想環境で行われます。最初の設定としてテストVMを素早くセットアップし、マシンを電源投入して「感染前」という名前のスナップショットを作成します。始めましょう!研究対象に関連するすべてのイベントを追跡するために Process Monitor 立ち上げて可視状態を保ちます:BlackEnergy-killdisk-1そして必要なプロセスを追跡していることを確認します:

BlackEnergy-killdisk-2次にウイルスを IDA Pro Disassembler にロードすると次の画面が表示されます。これはWinMain関数、つまりメイン関数ですので、分析を開始し、最初(そして唯一)の行動が sub_40E070:

 BlackEnergy-killdisk-3
という名前のプロシージャコールを行うことを確認します。 sub_40E080:

BlackEnergy-killdisk-4その中身を詳しく見てみましょう:BlackEnergy-killdisk-5その内容は1つの興味深いシステムコール CreateFile といくつかのプロシージャ sub_40C390 and sub_40C400を含んでいます。これにより、現時点で仮想ラボのスナップショットを撮り、旅を続ける時です!

関数呼び出しを調べて、 CreateFileブレークポイント BreakPoint を設定し、調査用サンプルを実行します:

BlackEnergy-killdisk-6この関数に関するちょっとしたヒント:

CreateFile 関数はファイルまたはI/Oデバイスを作成または開きます。最も一般的に使用されるI/Oデバイスには次のものがあります:ファイル、ファイルストリーム、ディレクトリ、物理ディスク、ボリューム、コンソールバッファ (CONIN$ or CONOUT$)、テープドライブ、通信リソース、メールスロット、名前付きパイプが含まれます。関数は、指定されたファイルまたはデバイスへのアクセスに使用できるハンドルを返します。

ほとんどのコンパイラはスタックを介して関数に引数を渡します。Cで初めに書かれたサンプルを扱っているため、Cの指令では引数は スタック 上に 右から左 の順にプッシュされ、最初の関数引数は最後にスタックにプッシュされ、スタックの最上位となります。 [1].

There is a special register for working with the stack - ESP (Extended Stack Pointer). The ESP, by definition, always points to the top of the stack:

BlackEnergy-killdisk-41スタックを見てみましょう:BlackEnergy-killdisk-7スタックにプッシュされた最後の引数(最上位)は、関数が受け入れる最初の引数( CreateFile 関数の構文に従った場合)であり、我々のケースではファイル名を含んでいます \.PHYSICALDRIVE0、つまりターゲットは最初の物理ドライブです。BlackEnergy-killdisk-8次に、スタックから値を取り出して関数に送信された他の引数を分析します:BlackEnergy-killdisk-9いくつかの結論:

  • lpFileName – 開かれたり作成されたりするファイルの名前(パス)を含むASCIIZ文字列へのポインタ(すでに判明した通り);
  • dwDesiredAccess – ファイルへのアクセスの種類:我々のケースでは値が 0C0000000h であり GENERIC_READ+GENERIC_WRITE or 読み書きアクセスを意味します;
  • DwShareMode – さまざまなプロセスとファイルを共有可能にするモードで、このパラメータは我々のケースでは00000003bでFILE_SHARE_READ+FILE_SHARE_WRITE を翻訳します。他のプロセスはファイルを読み書きのために開けます;
  • IpSecurityAttributes – カーネルファイルオブジェクトに関連するセキュリティ設定を定義するSecurityAttributes構造(winbase.h)のポインタ。セキュリティが設定されていない場合、 NULL 値が使用されます;
  • dwCreationDistribution – ファイルが存在するか否かの判断を行います。我々のケースでは 3 これは OPEN_EXISTING or ファイルが存在する場合に開き、存在しない場合にはエラーを返します;
  • DwFlagsAndAttributes – フラグと属性。このパラメータは作成されたファイルの特性を設定するのに使用され、読み取りモード0では無視されます。
  • hTemplateFile – 新しいファイルが作成された場合のみ使用されるパラメータ 0.
  • 成功すると、関数は新しいファイルのハンドラを ЕАХ に返します。 NULL 関数が失敗した場合、 ЕАХ レジスタに書き込まれます。

さて、 CreateFile を呼び出した後、EAXに非ゼロ値を得ると、要求されたファイルのハンドラを含むことを意味します。例として \.PHYSICALDRIVE0:

BlackEnergy-killdisk-26ハンドラを取得したので、次の呼び出しに進みます。 sub_40C390:

BlackEnergy-killdisk-10前回と同様に手続きを見ると、もう一つの興味深いコールが見受けられます:BlackEnergy-killdisk-11関数ヒント:

DeviceIoControl 関数は、指定されたデバイスドライバに制御コードを直接送信し、対応するデバイスが対応する操作を実行するようにします。

この関数を呼び出す直前のスタックを見てみましょう。BlackEnergy-killdisk-11結論:

  • hDevice – デバイス記述子(ここでは見覚えのあるもの \.PHYSICALDRIVE0);
  • dwIoControlCode – 操作コード。我々のケースでは、値は 0x70000h であり ディスクのジオメトリ情報(シリンダーの数、メディアの種類、シリンダー上のトラック、1トラックあたりのセクタ、1セクタあたりのバイト数など)の取得 を指します。
  • lpInBuffer – 入力データ用のバッファ。入力データは不要なので NULL 空のままです;
  • nInBufferSize – バイト単位の入力バッファサイズであり、我々のケースでは 0 に等しい;
  • lpOutBuffer – 出力バッファへのポインタ。其の型はdwIoControlCodeパラメータ 0x0011FCA8h;
  • で設定されます。 – output buffer size in bytes 0x18h (24);
  • lpBytesReturned – 出力に書き込まれたバイト数を含む変数へのポインタ 0x0011FCA4h;
  • lpOverlapped – OVERLAPPED構造体へのポインタ;
  • コールが処理されるとバッファの中を見てみます(引数として送られたアドレスで lpOutBuffer 視覚的に 0x0011FCA8h):

BlackEnergy-killdisk-12回答を解釈してみましょう:

  • アドレスの 0x0011FCA4h で関数によって意図されていた通りに出力バッファに書き込まれたバイト数が返されました(緑色でマークされて)要求どおり24シンボル得たことが確認されました。
  • アドレスの 0x0011FCA8h これで最初の物理ディスクのジオメトリ情報(\.PHYSICALDRIVE0):
    • シリンダーの数 – 0x519h (1305)
    • メディアタイプ – 0x0Ch (12)は固定ハードディスクを意味します。
    • シリンダーあたりのトラック – 0x0FFh (255)
    • トラックあたりのセクター – 0x3Fh (63)
    • セクターあたりのバイト – 0x200 (512)

次にプロシージャ三に入ります、 sub_40C400:

BlackEnergy-killdisk-13プロシージャ内にはすぐに注目を引く2つのコールがありました、特にSetFilePointerExおよびWriteFile:

BlackEnergy-killdisk-14関数についてのヒント:

SetFilePointerEx 関数は指定された開いているファイルのファイルポインタを移動します。

従来の手法に従って、関数コールスタックに注目します:BlackEnergy-killdisk-15

  • hfile – 我々がよく知っているデバイス記述子 \.PHYSICALDRIVE0;
  • liDustanceToMove – 書き込み開始の初期位置 0;
  • lpNewFilePointer – ファイルから新しい位置ポインタを取得するための変数へのポインタ 0x0011FCA8h。このパラメータがNULLの場合、ファイル内で新しいポインタは返されません;
  • dwMoveMethod – バイト単位の入力バッファのサイズ。我々のケースでは 0x200h (512)– セクタ内のバイト;

さて、我々の「獣」は WriteFile 関数を使用して、すでに確立した直接で信頼性のあるアクセス先のファイルをゼロで満たす。

関数ヒント:

該当する WriteFile 関数は、ファイルまたは入力/出力(I/O)デバイスに指定された位置からデータを書き込みます。この関数は同期および非同期の両方の操作に対応しています。

Function arguments list:BlackEnergy-killdisk-16さて、面白いものを見逃さないように、ダッシュボードを見て、全てのことの中でスタックに注目しましょう:BlackEnergy-killdisk-17すべて準備完了です:

  • 1 – 関数コールにステイし WriteFile
  • 2 - Process Monitor 我々の「獣」がほんのわずかでも動くのを待ち構えています
  • 3 – レジスタからスタックにプッシュされた引数
  • 4 – そして、スタック自体
  • Hex View-EBP ウィンドウ内で第二の関数引数(データバッファ)が参照される領域をマークしており、これには 0x200h (512分のゼロが正確に含まれていることを信じてください。

F8キーを押してコマンドを実行し、変更を観察します:BlackEnergy-killdisk-18期待通り、 Process Monitor の鋭い目は正確で、位置0から始まる512バイトの書き込みを確実にします。そしてプロシージャが終了し、再び異なる値で呼び出されます:BlackEnergy-killdisk-19BlackEnergy-killdisk-20

This merry-go-round goes on until the check of 0x40C865h address returns the value of EBX register equal to 0x100h (255 times ):

BlackEnergy-killdisk-21
To make sure this happened we put a stop point on the instruction that follows our vicious cycle of “destruction”:

BlackEnergy-killdisk-22
As a total we receive a 256 of re-write operations:

BlackEnergy-killdisk-23

That’s basically it, our “beast” has done its dirty work and closes the file handler by calling the CloseHandle function.

Function hint:

A CloseHandle function closes an object handle of opened object.

Function arguments:

BlackEnergy-killdisk-24
Our traditional view of the stack preparation:

BlackEnergy-killdisk-25
And what do we have in ESI register? BlackEnergy-killdisk-26

As expected our familiar descriptor 0x44h is passed to stack as function argument.

We see values returned by the function in the EAX register: BlackEnergy-killdisk-27

If function ends with success the returned value non-null.

If function exists with an error the returned value is null.

This action did not miss the watchful eye of our Process Monitor:

BlackEnergy-killdisk-28

Everything worked out as normal (if you can call the destruction of the most important part of the system drive normal) ) and the file is closed.

Let us go on:

BlackEnergy-killdisk-29

For a more comfortable perception I renamed function to Eraser. After it successfully destroyed the data on PhysicalDrive0, counter located in ESI register is increased by 1, checked with value of 0x0Ah (10) and launches re-write operation with a new value:

BlackEnergy-killdisk-30

and so on until 10 is reached.

This way the beast keeps walking across all physical drives and erasing first 512 * 255 = 128kb (this depends on the amount of bytes in a sector that “beast” learned from knowing disk’s geometry).

This time however, result of the function CreateFileW will be a following value:  BlackEnergy-killdisk-31

Which means that in our «improvised lab» there is no second physical disk (or well not any kind of disk with sequence number greater than 0) exists and the cycle has nothing to re-write.

part1_op_en

Now that we are armed with a freshly gained knowledge let us try to disable the destructive function we just learned by modifying the code in a way that our modification can allow to bypass the re-writing of the data:

BlackEnergy-killdisk-33
Let us remember procedure sub_40E080 that contains function call CreateFileW. Since we know that value of 0xFFFFFFFFh would appear after completion of function CreateFileW only in the case when physical drive is not present and in such scenario the “data destruction” function performs an exit, let us change a conditional branch instruction located at the 無条件分岐命令へ変更しましょう。 IDA Pro

 BlackEnergy-killdisk-34
IDA Pro クールなツールなので、ただコマンドを書く必要があります。

考慮すべき唯一の点は、コマンドのサイズが前後でどうなるかです。では、条件分岐のシーケンスがどれだけのバイトを使用するか確認しましょう:

BlackEnergy-killdisk-35条件分岐命令は 6 バイトであるのに対し、無条件シーケンスは 5 バイトです。それゆえ、Nopコマンドを追加して、バランスを保つことにします:BlackEnergy-killdisk-36前のものが何であれ(0がある場合でも)次のコマンドをNOPします:

BlackEnergy-killdisk-37次のコマンドのアドレスを基に、コマンドアドレスが均等化されたかどうかをダブルチェックします:

BlackEnergy-killdisk-38そしてキャンセルを押します。完了です。

これらの操作の後、手順はシンプルに見えます:入り、と出て、空のループを作ります(プログラムの他の部分がどのように関与してくるかわからないので、この状態のままにしておきます)。BlackEnergy-killdisk-39Process Monitor によって発見された最終的な証拠として、分解された KillDisk の実行の結果、物理ディスクに損害が生じていないことが示されています:

BlackEnergy-killdisk-40マルウェアはディスクの情報を読み取り、それを調査し、その後、次の記事で詳しく分析するビジネスを続けました。

参考文献:

[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.

Andrii Bezverkhyi | CEO SOC Prime により原文から翻訳

この記事は役に立ちましたか?

いいねと共有をお願いします。
SOCプライムのDetection as Codeプラットフォームに参加してください ビジネスに最も関連する脅威の可視性を向上させるために。開始をお手伝いし、即時の価値を提供するために、今すぐSOCプライムの専門家とミーティングを予約してください。