我々はセキュリティのプロフェッショナルとして、あらゆるシーンにおける先進のマルウェアの解析に大いに関心があります。セキュリティ企業が、国家による高度なサイバー攻撃に関するレポートを公開するたびに、我々は皆すぐに、そのレポートに目を通します。その内容は前と同じようなものであったり、先進の攻撃であってもAPIの代わりにあらゆるOSツールを駆使しているといったようなケースもあります。

実際のところ、技術に精通した国家レベルのハッカーでなくとも、十分な機能を果たすマルウェアを作りだすことができるのです。シンプルでありながら強力なプログラムとしてわかりやすい例の1つに、Fauxperskyマルウェアがあります。タスクの自動化という通常の用途に使用するAutoHotKeyというツールを使って書かれており、認証情報の盗取に大きな威力を発揮することが確認されています。

大方の予想とは異なり、セキュリティの研究者が分析に多くの時間を費やしている対象は非常に手の込んだ国家レベルのATP攻撃ばかりというわけではありません。インターネット上で使われるアルファベットの表記法のように、複雑さはないながらも大きな影響を及ぼす攻撃を対象として、日々解析を行っているのが実情です。

話題を変えて、.NETについてお話ししましょう。.NETについて説明する理由はこのあとですぐわかります。.NETは2000年台初頭にMicrosoftが導入したプログラミングフレームワークで、その目的はプログラミングを簡素化することにありました。

単純なプログラムを開発する際でも、C言語であれば、メモリの割り当てや解放の処理を考慮せねばならず、C++の場合には、非常に長く複雑なコードを記述する必要がありますが、.NETがあれば、これらのことが一切不要になります。.NET言語のうちで特によく利用されているのはC#(Cシャープと読む)です。

.NET言語は、最新かつ実用的、汎用的なオブジェクト指向の言語であり、今日のプログラミング言語に当てはまるあらゆるバズワードが、.NETを表す表現として使われてきました。そして最も重要なことは、C#がWindows用プログラムの開発をきわめて容易にする点です(モノフレームワークの使用により、LinuxやMacOSにも対応しています)。

構文は非常に流動的であり、Visual StudioにはC#用のオートコンプリート機能が豊富に用意されています。また、Windows APIからの関数の呼び出しは非常にシンプルです。さらに、プロジェクトをコンパイルする場合、プロジェクトにもよりますが、EXEやDLLへのコンパイルが可能です。

CやC++よりもプログラミングが容易でコンパイル後にバイナリのPEも手に入るのであれば何が問題になるのかという疑問があるかもしれません。問題は、それがネイティブの本当の実行ファイルではない点にあります。CやC++のコードからコンパイルした実行ファイルとは異なり、.Netの場合は実行ファイルの内部に、X86のアセンブリコードを正確に特定することができません。これは、.NETの動作がCやC++とは異なるためです。


参照:http://www.developingthefuture.net/compilation-process-and-jit-compiler/

.NETのプロジェクトをコンパイルすると、実際には、MSIL(Microsoft Intermediate Language)と呼ばれるものにコンパイルされます。このコードは、プログラムが実行されたときに、JIT(ジャストインタイム)コンパイラを使ってコンパイルされたものです。

.NETのコンパイラやランタイムの詳細については、こちらにあるMicrosoftのドキュメントをご覧ください。また、MSILは上位レベルのアセンブリと考えることができます。

.NETのコンパイル手法などというひどく退屈な話題を持ち出したのには、それなりの理由があります。CやC++で記述された実行ファイルのアセンブリと、.NETで書かれた実行ファイルのそれとでは、違いがあることを知ってもらいたかったのです。

CやC++で記述された実行ファイルのような「通常」の実行ファイルに対してリバースエンジニアリングを行うと、ディスアセンブラには、x86/64アセンブリが表示されますが、.NETでコンパイルされた実行ファイルの場合は、アセンブリ自体は表示されるものの、それは別のアセンブリになります。

コードがMSILにコンパイルされるということはつまり、コードの内部にあるものが大量のメタデータであることを意味します。そのため、デコンパイルは非常に容易であり、実際のところ、.NET用のデコンパイラと少しばかりの忍耐力があれば、デコンパイルが可能です。

ここ最近のことですが、マルウェアのサンプルのテストに使用しているマシンにいくつかの奇妙なAutorunが存在するのを発見しました。そして、これらAutorunのキーがどのようにしてマシン内に侵入したのか、大いに興味をかき立てられました。

マシン上のすべてのファイルの動きを追跡した結果、Autorunが確認されるほんの数分前にマシン上で実行したあるマルウェアサンプルが問題の発端になっていることが判明しました。そして、オリジナルの実行ファイルを確認したところ、それが.NETのプロジェクトからコンパイルされていることがわかりました。

つまり、この実行ファイルを調査するには、通常とは全く異なるツールセットが必要だったのです。このため、IDA proのような固有のディスアセンブラではなく、.NETに使えるディスアセンブラやデコンパイラが必要でした。このような場合に私が好んで使っているのは、dnSpyです。これは非常に有用なデバッガで、ユーザーインターフェースも秀逸です。その理由はこのデバッガがILSpyという非常に優れたプロジェクトをベースにしていることにあります。

dnSpyのようなデコンパイラを使用すると、元のマルウェアに非常に近いかたちでコードを確認できます(変数やオブジェクト、クラスの名前が元のものとは異なる場合がありますが、それでも解析はきわめて容易です)。

しかし、デコンパイルされたコードや、クラスおよび関数の名前を見ると、それらが正しくないことがわかりました。これらには、難読化が施されているようです。具体例を以下に示します。


難読化が施されたネームスペース


難読化が施されパッケージングされたコード。ほとんどの内容が意味をなさないように見える

.NETプログラムはソースコードにきわめて近いかたちにデコンパイルが簡単にできるので、多くのプログラムの開発者が(そしてマルウェアの作成者も)、リバースエンジニアリングが難しくなるよう、あらゆる難読化の手法を駆使しています。しかし幸いにも、難読化を解除するツールは複数存在します。

この難読化解除のプロセスを簡素化するために、ここでは「detect it easy (die)」と呼ばれるツールを使用しています。ファイルをdieのウインドウに「ドラッグ」すると、使用されている難読化のツールの詳細などを含め、ファイルに関するいくつかの重要な情報が表示されます。このケースでは、難読化のツールとしてSmartAssemblyが使用されていました。

使用されている難読化のツールが判明したので、そのツールの処理を無効にする方法を探すことにしました。このような場合に使用するおめのツールは、de4dotです。これは、.NETを対象としたオープンソースの難読化解除およびアンパッキングのツールです。

すぐにde4dotを実行したところ、アンパッキングの処理と難読化解除の処理が行われ、以下のような悪意のあるファイルの存在が明らかになりました。

謎のマルウェアサンプルのアンパックと難読化の解除が完了したというわけです。
これをdnSpyで開いてみると、ネームスペースが変わっていることがわかりました。ある程度意味をなす内容も確認できます。この内容を以下に示します。

重要なポイント
  • ネームスペースはオリジナルのものとは異なっています。難読化解除のツールには、ネームスペースのほか、オブジェクトやシンボルの汎用名も表示されており、プログラムの内容が理解しやすくなっています。マルウェアの作成者が設定したオリジナルの名前を知る方法はありませんでした。
  • dnSpyでは、MSILのコードをVisual Basic(VB)、C#のどちらのかたちにも戻すことができます。ドロッパーはVBで書かれていましたが、ここではC#を選択することにしました。C#のほうが構文を理解しやすいからです。この記事に掲載したコードのスナップショットはすべてC#で記述されたものになっています。もとはVBで書かれたフォームコードをコンパイルしたMSLから変換しています。
    ネームスペース「-」の中身を確認してみると、「Class14」というクラス(これも難読化解除ツールの処理よって得られたものです)の存在することがわかりました。このクラスの配下には大量のクラスメソッドがあります。

そして、Class14の中身のコードを確認したところ、Interaction.Environコールを用いて環境変数を列挙しようとするコードがいくつか存在するのをはっきりと確認できました。

この環境変数が実際には、Base64で難読化された2つの文字列から成る文字列の組み合わせであることは明らかです。文字列をBase64でデコードしようとしたところ、内容の理解できないいくつかのデータが表示されました。この内容を以下に示します。

コードの別の部分を確認してみると、いくつかの暗号化のライブラリと関数が呼び出されていることもわかりました。

このサンプルには大量のスパゲッティコードが含まれていました。これらのコードでは、コード内のクラスにある別の箇所を参照するようになっており、コードの静的分析を難しくしていました。Base64の値も暗号化されていることはこの時点で容易に予想がつきました。この時点で取り得る選択肢には次のようなものがあります。

  1. 静的分析:コード内のあるゆる手掛かりを用いて、文字列の暗号を解除する即席の簡易コードを作成する。
  2. 動的分析:デバッガを使用してプログラムの実行プロセスを順を追って調査し、値の暗号の解除や難読化の解除がどのように行われているのか確認する。

ここで選択すべきは絶対に2つ目の方法です。コードは非常に読みやすく、扱いの厄介なアセンブリのコードを順番に追っていく必要もないため、そのほうが作業が早く終わるからです。

dnSpyはデバッガでもあるので、コードの必要な場所にブレークポイントを置くのも非常に簡単です。目的の行でF9を押すだけです。

1081行目にブレークポイントを置きました。ここからsmethod_56()が開始されます。一見してわかるようにこの関数は、1つのパラメーター、string_0を受け入れています(文字列の変数の名前はデコンパイラによって生成されたものです)。

string_0 はBase64形式の値です。(この値の難読化を解除して正常な値にすることはできません)。

(F10を使用して)コードを飛ばし読みしてみると、データにはさまざまなオブジェクトが付随していることがわかりました。この内容を以下に示します。

この内容により、smethod_56()の役割が何であるのか容易に理解できます。それは、以下の異なる3つの変数から3バイトのアレイを生成することにあります。

  1. S2(1B2c3D4e5F6g7H8)
  2. S(cffffffffffffffffff)
  3. String_0(Sk1N1W/kLlYPS5rz2GRFew==)

そして次にrfc2898DeriveBytesクラスをインスタンス化し、3つのパラメーターをこのクラスに渡します。

  1. パスワード(「nia」。上記のコードのスクリーンショットを参照):パスワードとして使用されます。
  2. Bytes2:s変数から生成された数バイトのアレイです。saltとして使用されます。
  3. 2:キーから導き出された反復処理の回数です。

この関数は別のバイトアレイ、bytes3を作成します。このアレイには、生成されたばかりのキーが含まれています。

この関数は継続的にRijndaelManagedクラスのインスタンス化を行い、パラキートとIVのパラメーターとしてそれぞれbytes3とbytesを持ったCreateDecryptor関数を呼び出します。

さらに順を追ってコードを調べていくと、難読化および暗号化されたいくつの文字列が存在することが判明しました。これらは、複数の値を持つsmethod_0()を(デリゲーション経由で)呼び出して暗号化されており、これらの値も難読化の解除と暗号の解除が必要でした。この内容を以下に示します。

値の難読化の解除と暗号の解除が行われるごとに、その値が変数のペインに表示されます。この内容を以下に示します。

このケースでは、text2には、現在実行中の実行ファイル本体へのフルパスが含まれています。

さらにコードを順を追って確認してみると、難読化と暗号が解除された別の文字列のあることがわかりました。これらの文字列を確認することで、このマルウェアの挙動をさらに詳しく理解できます。

このコードからわかるように、難読化/暗号化された文字列は、別の(しかし非常に似通った)関数、smethod_76()でカップリングされ暗号が解除されています。以下のように暗号キーが異なっており、中国語で記述されています。


Smethod_76

変数のペインをもう一度見てみましょう。暗号の解除されたパスの数が増えているのがわかります。sourceFileNameはmsbuild.exeのオリジナルのパスを表し、また、destFileNameには、ユーザーのテンポラリーディレクトリにあるsvhost.exeと呼ばれるファイルへのパスが含まれています。以下のようにこれらのことがはっきりとわかります。

このコードに戻ってみると、実際には以下のようにファイルのコピー操作が行われていることがわかりました。

つまり、テンポラリーディレクトリ内のsvhost.exeは実際にはMSBuild.exeなのです。このプロセスを積極的に解析したおかげでパスをたどることができ、本体のファイルを調べることに成功しました。そして以下のような驚くべき事実が判明したのです。

Svhost(実際にはmsbuild)は、非常に小さなバイナリを作成するために使用されます。このバイナリは攻撃の持続性を維持することを目的として、以下で説明するレジストリ値を操作します。

インジェクションを通じたプロセスのターミネート

このドロッパーの挙動にはもう1つ、興味深い点がありました。調査ツールの動作を止める仕組みがあったのです。これに気付いたのは、ProcmonとProcessHackerを実行していたときでした。デバッガがclass_14Iの2094行目を実行すると、調査ツールがすべて動作を停止してしまったのです。これでさらに興味をかき立てられました。一体何を行っているのでしょうか。なぜツールは停止してしまったのでしょうか。

通常、「調査を目的とした」プロセスをマルウェアが停止する場合、Wiresharkツールやsysinternalsツールなど、対象とするアプリケーションをリスト化し、n秒ごとにこの処理リストを照会して、これらのプロセスが動作しているのを確認した際に、単純にTerminateProcess()を呼び出します。

しかしこのケースではドロッパーの挙動が異なっていました。コード内のTerminateProcess()に対する(GetProcAddress()を経由した)直接または間接のコールはなにも特定できなかったのです。
マルウェアを解析していて気が付いたことがありました。ProcessHackerを一度停止してしまうと、悪意のある実行ファイルの動作をターミネートしない限り、再度起動できなかったのです。言うまでもなく、悪意のあるプロセスの動的解析を難しくする、なんらかの仕組みがあるようです。

このような処理を可能にする一般的な手法としては、Windows APIのCreateToolHelp32Snapshot()関数を使う方法があります。この関数は、プロセスや、そのヒープ、モジュールなどの情報について、「スナップショット」を作成します。

今回のケースでは、ドロッパーは起動しているプロセスのすべてをリスト化し、これらの個々のプロセスごとに、CreateToolHelp32Snapshot()を呼び出しています。処理の結果はアレイに保存されます。このアレイの個々のメンバーは、実行されているプロセスについての情報を保持します。


アレイ内の列挙されたプロセス

「調査を目的とした」該当するプロセスを検知すると、ドロッパーは、(ドロッパー自体にホストされている)以下に示すコードの暗号を解除し、このコードを調査ツールのプロセスにインジェクションします。そして次にこれを実行し、NtTerminateProcess()をコールして、調査プロセスをターミネートします。


コードをターミネートするプロセス

以上でこの調査の最初のフェーズは終了です。このフェーズでは、プロジェクトに対するリバースエンジニアリングの適用など、今回行われた調査の手法について説明しました。

また、発生した事象を明らかにし2つの重要な問いに答えを出すために必要としたツールやテクニックについても触れました。そして、この2つの重要な問いですが、これらは、このマルウェアがどのような理由で如何にして調査ツールをターミネートしていたのかというものでした。

次のフェーズでは、このドロッパーが実際に何を投下したのかをご説明します。また投下されたファイルを静的に分析した結果、明らかになった事柄についてもご説明します。

ホワイトペーパー「すべての組織が狙われている」

企業、組織がどんなにセキュリティを強固にしてもハッカーが悪用できる脆弱性は必ず存在します。侵入されることが避けられないことを受け入れ、新たな対策を立てる必要があります。本書で、なぜ避けられないのか、どのように対処するのかをご覧ください。
https://www.cybereason.co.jp/product-documents/input/?post_id=606

ホワイトペーパー「すべての組織が狙われている」