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 立ち上げて可視状態を保ちます:そして必要なプロセスを追跡していることを確認します:
次にウイルスを IDA Pro Disassembler にロードすると次の画面が表示されます。これはWinMain関数、つまりメイン関数ですので、分析を開始し、最初(そして唯一)の行動が
sub_40E070
:
という名前のプロシージャコールを行うことを確認します。 sub_40E080
:
その中身を詳しく見てみましょう:
その内容は1つの興味深いシステムコール
CreateFile
といくつかのプロシージャ sub_40C390
and sub_40C400
を含んでいます。これにより、現時点で仮想ラボのスナップショットを撮り、旅を続ける時です!
関数呼び出しを調べて、 CreateFileブレークポイント BreakPoint を設定し、調査用サンプルを実行します:
ほとんどのコンパイラはスタックを介して関数に引数を渡します。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:
スタックを見てみましょう:
スタックにプッシュされた最後の引数(最上位)は、関数が受け入れる最初の引数(
CreateFile
関数の構文に従った場合)であり、我々のケースではファイル名を含んでいます \.PHYSICALDRIVE0
、つまりターゲットは最初の物理ドライブです。次に、スタックから値を取り出して関数に送信された他の引数を分析します:
いくつかの結論:
- 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
:
ハンドラを取得したので、次の呼び出しに進みます。
sub_40C390
:
前回と同様に手続きを見ると、もう一つの興味深いコールが見受けられます:
関数ヒント:
- 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
):
- アドレスの
0x0011FCA4h
で関数によって意図されていた通りに出力バッファに書き込まれたバイト数が返されました(緑色でマークされて)要求どおり24シンボル得たことが確認されました。 - アドレスの
0x0011FCA8h
これで最初の物理ディスクのジオメトリ情報(\.PHYSICALDRIVE0
):- シリンダーの数 –
0x519h
(1305) - メディアタイプ –
0x0Ch
(12)は固定ハードディスクを意味します。 - シリンダーあたりのトラック –
0x0FFh
(255) - トラックあたりのセクター –
0x3Fh
(63) - セクターあたりのバイト –
0x200
(512)
- シリンダーの数 –
次にプロシージャ三に入ります、 sub_40C400
:
プロシージャ内にはすぐに注目を引く2つのコールがありました、特に
SetFilePointerEx
およびWriteFile
:
- hfile – 我々がよく知っているデバイス記述子
\.PHYSICALDRIVE0
; - liDustanceToMove – 書き込み開始の初期位置
0
; - lpNewFilePointer – ファイルから新しい位置ポインタを取得するための変数へのポインタ
0x0011FCA8h
。このパラメータがNULLの場合、ファイル内で新しいポインタは返されません; - dwMoveMethod – バイト単位の入力バッファのサイズ。我々のケースでは
0x200h
(512)– セクタ内のバイト;
さて、我々の「獣」は WriteFile
関数を使用して、すでに確立した直接で信頼性のあるアクセス先のファイルをゼロで満たす。
関数ヒント:
該当する WriteFile 関数は、ファイルまたは入力/出力(I/O)デバイスに指定された位置からデータを書き込みます。この関数は同期および非同期の両方の操作に対応しています。
Function arguments list:さて、面白いものを見逃さないように、ダッシュボードを見て、全てのことの中でスタックに注目しましょう:
すべて準備完了です:
- 1 – 関数コールにステイし
WriteFile
- 2 - Process Monitor 我々の「獣」がほんのわずかでも動くのを待ち構えています
- 3 – レジスタからスタックにプッシュされた引数
- 4 – そして、スタック自体
- Hex View-EBP ウィンドウ内で第二の関数引数(データバッファ)が参照される領域をマークしており、これには
0x200h
(512
分のゼロが正確に含まれていることを信じてください。
F8キーを押してコマンドを実行し、変更を観察します:期待通り、 Process Monitor の鋭い目は正確で、位置0から始まる512バイトの書き込みを確実にします。そしてプロシージャが終了し、再び異なる値で呼び出されます:
:
This merry-go-round goes on until the check of 0x40C865h
address returns the value of EBX
register equal to 0x100h
(255 times
):
To make sure this happened we put a stop point on the instruction that follows our vicious cycle of “destruction”:
As a total we receive a 256
of re-write operations:
That’s basically it, our “beast” has done its dirty work and closes the file handler by calling the CloseHandle
function.
Function hint:
Function arguments:
Our traditional view of the stack preparation:
And what do we have in ESI register?
As expected our familiar descriptor 0x44h
is passed to stack as function argument.
We see values returned by the function in the EAX register:
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:
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:
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:
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:
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.
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:
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
IDA Pro クールなツールなので、ただコマンドを書く必要があります。
考慮すべき唯一の点は、コマンドのサイズが前後でどうなるかです。では、条件分岐のシーケンスがどれだけのバイトを使用するか確認しましょう:
条件分岐命令は
6
バイトであるのに対し、無条件シーケンスは 5
バイトです。それゆえ、Nopコマンドを追加して、バランスを保つことにします:前のものが何であれ(0がある場合でも)次のコマンドをNOPします:
次のコマンドのアドレスを基に、コマンドアドレスが均等化されたかどうかをダブルチェックします:
これらの操作の後、手順はシンプルに見えます:入り、と出て、空のループを作ります(プログラムの他の部分がどのように関与してくるかわからないので、この状態のままにしておきます)。Process Monitor によって発見された最終的な証拠として、分解された KillDisk の実行の結果、物理ディスクに損害が生じていないことが示されています:
マルウェアはディスクの情報を読み取り、それを調査し、その後、次の記事で詳しく分析するビジネスを続けました。
参考文献:
[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.
Andrii Bezverkhyi | CEO SOC Prime により原文から翻訳