The Emotet-ion Game (Part 3) by Ratnesh Pandey 2019年5月28日より
この投稿は、Emotetバンキング系トロイの木馬に関する連続ブログの続きです。「身代金」を要求するため感染が明示的なWannaCryとは異なる問題を生み出すEmotet。これまで、Emotetが仕込まれるメカニズムとその振舞いについて動的分析をしてきました。Emotetから得られたホストとネットワークに関するデータから、それ自身をサービスとして登録することで権限を昇格させ、ファイルシステムの様々な場所に存続し、最後にドロッパーファイルを削除することがわかっています。
今回の投稿では、メインのペイロードがどのように解凍され、その後実行を開始するのかをデバッガ上で説明します。Emotetの実行は、いくつもの段階の難読化されてメモリ内にロードされる実行プログラムで構成され、異なるメモリ領域全体に巧妙なコードフローを持たせます。これは解析者や静的解析エンジン、逆アセンブラーなどが、このトロイの木馬を解析するのを難しくするように設計されているのです。
また、解凍されたコードの中で、Emotetが特定のレジストリキーの存在をどれだけ早い段階でチェックするかについても記述します。そのレジストリキーにアクセスできなければ、ペイロードは解凍されず、実行もしません。キーに対する読み取りアクセスを遮断することが、Emotetペイロードの実行を妨げる効果的な方法であることがわかります。
Part 2では、Emotetがどのように他のマルウェア群のドロッパーとして働くのか、そして、これがどのように、攻撃者がサービスとしてのマルウェアのビジネスモデルを採り入れていることを示しているかを述べました。15.exeと呼ばれるEmotetの実行ファイルのひとつが、最終的にTrickbot という他のバンキング系トロイの木馬を拡散させたことが解析でわかりました。2019年4月(5月号)の「Bromium脅威情勢レポート」で特集したように、Emotetの数はあいかわらず膨大であり、Bomiumが隔離した中でもトップの脅威であることがわかっています。
Bromium(ブロミアム)は従来のマルウェア対策製品とは全く異なる発想で、外部からの脅威に対して防御・検知・分析します。
Bromiumをインストールしたコンピュータでは、Emotetは隔離されたマイクロ仮想マシンの中で実行し、システムの統合性を損なうことはありません。マルウェアは活発に活動するため、侵害の痕跡(IOC)を残すため、セキュリティチームはこれを使ってネットワークの他の資産を守ることができます。
EMOTETのパッカー
パッカーの主な目的は、ポータブル実行(PE)ファイルを圧縮し、暗号化することです。暗号化されたペイロードは実行時に解凍され、次に解凍コードが新たに解凍されたコードの実行に移ります。マルウェアの作成者側からみて、パッカーは、バイナリの静的解析をより困難にすることによって検出を回避するのに役立つのです。Emotetのパッカーは多態型なので、シグネチャーベースの検出ツールでは、パッカーの痕跡に基づいたサンプル分析がさらにややこしくなります。
- ファイル名:15.exe
- サイズ:428808バイト
- MD5:322F9CA84DFA866CB719B7AECC249905
- SHA1:147DDEB14BFCC1FF2EE7EF6470CA9A720E61AEAA
- SHA256:F2F82ADF716209CD5BA1C98D0DCD2D9A171BB0963648BD8BD962EDB52761241
PEファイルを見てみましょう。そのリソース(.rsrc)部分は、全ファイルサイズのかなりの部分を占めていて(51%)、これが、マルウェアが圧縮されていることの印になります。
図1 – リソース部分がバイナリの半分以上を占めている
リソース部分を見ると、EXCEPTとCALIBRATEと呼ばれる2つの特異なリソースがあるのがわかります。EXCEPT のエントロピー値が高く、サイズが大きいことから、これが暗号化されたペイロードであることが推測されます。このリソースを出力してみると、暗号化されたデータがあることが確認できます。いくつかのサンプルでは、復号化されたPEファイルが、.dataの部分から投入されたことがわかりました。
図2 – EXCEPTおよびCALIBRATEと呼ばれる特異なリソース
図3 – EXCEPT内の暗号化データ
Emotetはモジュール形式であり、解析を回避するために、サンドボックス検出などの解析への対策技法を使っています。解凍されたEmotetのバイナリには、数百にのぼる関数が入っています。疑わしい圧縮サンプルをGhidraのような逆アセンブラーで開いても、ほんの一握りの関数しか特定できません。これが、バイナリが圧縮されているという、もうひとつの印です。
図4 – 圧縮されたEmotetのサンプル内で、Ghidraが特定した関数のリスト
パッカーレジストリチェック
パッカーコードの解析中、ある文字型配列を生成し、Conditional While(true)の無限ループを持つ関数に気づきました。私たちはこれを見つけて、無限ループをきっかけにして解凍コードの実行を食い止め、それによってメインのEmotetのペイロードを実行させないようにすることができないかと興味を持ちました。この関数は、RegOpenKeyA への呼び出しを通じてWindows レジストリ キーを読み取ることで動作します。このキーが見つからなければ、マルウェアは無限ループに入ります(図5)。
図5 – レジストリで“ interface \ {aa5b6a80-b834-11d0-932f-00a0c90dcaa9}”の存在を確認する関数
関数FUN_00401a90が値「interface\{aa5b6a80-b834-11d0-932f-00a0c90dcaa9}」でストリングを解読し、これがパラメータとしてRegOpenKeyA に渡されます。このレジストリキーは、Windowsのスクリプトエンジン・インタフェース、IActiveScriptParseProcedure32の動作に必要です。具体的には、このインタフェースが、与えられたコード手順を構文解析し、その手順を名前空間に追加するのです。
図6 – RegOpenKeyAパラメータ
他のEmotetのサンプルにも同様の関数がないかを調べてみました。面白いことに、すべてのサンプルを実行すると、このレジストリキーがなければ、メインスレッドはEXIT、つまり強制終了するか、無限ループに入るのでした。
- ファイル名:891.exe
- VirusTotalへの最初の送信:2019年5月8日
- MD5:BD3B9E60EA96C2A0F7838E1362BBF266
- SHA1:62C1BEFA98D925C7D65F8DC89504B7FBB82A6FE3
- SHA256:28E3736F37222E7FBC4CDE3E0CC31F88E3BFC16CC5C889B326A2F74F46E415AC
図7 – メインスレッドはレジストリキーがないと無限ループに入る
- ファイル名:448.exe
- VirusTotalへの最初の送信:2019年3月7日
- MD5:193643AB7C0B289F5DE3963E4ADC1563
- SHA1:B14290BFAE015D37EBA7EDD8F5067AD5E238CC68
- SHA256:D9E5C47F9AEB47F5E720D42DD4B8AD231EE3BA5270E3FBD126FC8C6F399D243
図8 – メインスレッドはレジストリキーがない場合にEXITする
デバッガによるバイナリ解析
15.exeが圧縮されたEmotetのバイナリであるかもしれないという疑いを確かめるためにこれをx64dbgの中で開き、マップされたメモリ領域をチェックしてみましょう。15.exeの任意のヘッダーで、アドレススペースのレイアウトrandomisation(ASLR)が無効となっていますが、これは、可能であればモジュールにとって好ましいベースアドレス0x00400000でメモリにロードされることを意味します。
ステージ1
インポートされた15.exeの関数のひとつに、VirtualAllocEx があります。この関数は、メモリをリモートプロセスに割り当てるために使われ、マルウェアがプロセス侵入手段として使うこともよくあります。まずブレークポイントをVirtualAllocEx の戻り値アドレスに入れてみましょう。
図9 – 15.exeのメモリマップされたセクション
ブレークポイントまで実行すると、Emotetが0x00220000Ifでメモリの割当てをしているのがわかります。 Emotetはその後、0x00422200(ファイルオフセット0x0001FE00)のマップされたイメージの.data領域から、コードスタブを新たに割当てられたメモリ領域にコピーし、それに対する制御を行います。
図10 – 0x00220000でのメモリの割り当て
Emotetは次に、0x00220000にコピーされたコードからAPIとDLLの名前の難読化を解除します。
図11 – LoadLibraryExAとkernel32.dllの難読化解除
図12 – VirtualAllocを難読化解除
その後、kernel32.dllからGetProcAddressを呼び出し、デコードされたAPI名のアドレスを取得します。
図13 – マップされたkernel32.dllのイメージからエクスポートされたAPIのアドレスを取得するために、 0x00220000 でコードスタブから呼び出されたGetProcAddressのAPI
まず、このようにLoadLibraryExAのアドレスが取得されます。次に、このアドレスを使って、0x766D0000のアドレス領域に、kernel32.dll をロードします。その後、これを手がかりにロードされたモジュールに行き、下記の関数リストのGetProcAddressを呼び出します。
- LoadLibraryExA
- GetProcAddress
- VirtualAlloc
- SetFilePointer
- LstrlenA
- LstrcatA
- VirtualProtect
- UnmapViewOfFile
- GetModuleHandleA
- WriteFile
- CloseHandle
- VirtualFree
- GetTempPathA
- CreateFileA
図14 -LoadLibraryExAのアドレスを取得するためにGetProcAddressを呼び出す
図15 – LoadLibraryExAを呼び出してkernel32.dllをメモリにロードする
図16 – アドレスが解析されているAPI名の難読化を解除する
ひとつ注目に値する興味深いことがありますが、それは、Emotetが mknjht34tfserdgfwGetProcAddress と呼ばれる無効なAPIのためにGetProcAddressを呼び出していることです。これは無効なので、この関数が0000007F (ERROR_PROC_NOT_FOUND)のエラーコードと共に無効の値を返します。私たちが調べたEmotetのサンプルでは全て、この無効な関数名のためにGetProcAddressに呼び出しが行われていました。
図17 – 無効なAPIのためのGetProcAddress呼び出し
図18 – GetProcAddressを呼び出して、GetProcAddressのアドレスを取得
図19 – スタックに保存されたAPIの関数アドレス
コードスタブが関数アドレスを取得すると、.rsrcセクションからではなく、15.exeの.dataセクションから判読されたPEファイルを書き込む別のメモリ領域を割り当てるためにVirtualAllocが呼び出されます。
図20 – アドレス0x00240000でのメモリの割り当て
図21 – スタブはPEファイルをアドレス0x00240000に書き込みます。
0x00240000から出力されたEmotetバイナリ
- ファイル名:emotet_dumped_240000.exe
- MD5:D623BD93618B6BCA25AB259DE21E8E12
- SHA1:BBE1BFC57E8279ADDF2183F8E29B90CFA6DD88B4
- SHA256:01F86613FD39E5A3EDCF49B101154020A7A3382758F36D875B12A94294FBF0EA
- Bromium Cloudでの分類:Win32.Trojan.Emotet
実行ファイルを出力して調べてみると、また別の圧縮されたEmotetバイナリがあり、その中にメインのペイロードが入っているのがわかります。いくつかのEmotetのサンプルでは、最初にマップされた解読済みの実行ファイルは、メモリからこれを出力した後で直接実行することができませんが、このサンプルは実行できました。
Pestudioが、インポートがないこと、パッカーのシグネチャー「Stranik 1.3 Modula/C/Pascal」の検出、そしてこのファイルに他のファイルが入っているかどうかなど、このファイルに関する疑わしい特徴をいくつか指摘します。
図22 – pestudioによって識別されたemotet_dumped_240000.exeに関する疑わしい指標。
図23 – emotet_dumped_240000.exeのBromium Controllerのプロセス相互作用グラフ。自ら動作を開始し、15.exeで示されるふるまいに非常に近い、いわゆる「iprommini」というサービスを作り出します。
図24 – emotet_dumped_240000.exeについて検出された重大度の高いイベントのBromium Controllerビュー
ステージ2
0x00240000で実行ファイルを書き込み、暗号化した後、コードスタブはVirtualAllocExを使って0x00260000のアドレスに他のメモリ領域を割当てます。メモリ割当て後、メモリ領域0x00240000からペイロードを読み込み、それを0x00260000に書き込みます。
図25 – VirtualAllocExを呼び出して、0x00260000にメモリを割り当てる
図26 – スタブはメインEmotetペイロードを0x00260000に書き込みます。
0x00260000でメインのEmotetペイロードを書きこんだ後、コードスタブはフックとJMP命令をコードに挿入します(図28)。Emotetはこうして、コード解析をより煩雑にし、逆アセンブラーを混乱させるのです。
フックが挿入されると、ペイロードは他のメモリ領域に依存して実行するようになるので、PEファイル領域のアライメントや列オフセットを固定した後でも、ディスクへの出力によってそれを実行することはできません。
図27 – 仮想アドレス0x00260000から出力されたPEファイルに関する実行エラー
図28 – 0x00260000 に割当てられた実行可能ファイルのコードスタブによる変更
ステージ3
ペイロードが0x00260000で変更され、準備が整うと、スタブは0x00400000からunmap 15.exe へUnmapViewOfFileを呼び出しますが、これは、最初にEmotetのイメージがロードされたメモリ領域です。 その後、0x00260000のペイロードと同サイズ(15000バイト)のメモリ領域が新たに0x00400000に割当てます。新たなメモリ領域が割当てられた後、変更されたペイロードが0x00400000にコピーされます。これはプロセス侵入テクニックであり、マルウェアはメモリ内で自身のバイナリを変更して上書きします。
図29 – Emotetは0x00400000から、ロードされたイメージ、15.exeのマップを無効化する
図30 – image15.exe のマップ無効化の後のメモリマップ画面
図31 -0x00400000に新たに割当てられたメモリ-
図32 – 0x00400000への割当て後のメモリ画面
図33 – 0x00260000から0x00400000へのペイロードのコピー
ステージ4
ペイロードを0x00400000にコピーした後、Emotet はAPI名を解析し、実行フローをペイロードに転送します。この場合、実行フローを転送するのは0x0040C730ですが、これは次に、API名と一致するハッシュを解析する関数を呼び出します。
マルウェアの相関性を見ることができるストリング法が難読化されているので、メインのEmotetペイロードによって、解析者がコードフローを追跡するのが難しくなります。
図34 – 名前解決のためにAPIハッシュのリストを難読化解除関数に渡す。
図35 – ntdll.dllとkernel32.dllからのAPI名の解決
API名の解析後、Emotet実行プロセスのプロセスID(PID)を取得するためにGetCurrentProcessIdが呼び出されます。その後、Emotetはすべての実行プロセスを反復し、自身のモジュール名と親PIDを探します。自身の親PIDを見つけたら、PEM%X 形式で2つのミューテックスを作成します。ミューテックスの一方は親プロセスID(PEM[PRID])を使って作成され、もう一方は自身のPID (PEM[PID])を使います。
これらのミューテックスを作成した後、CreateEventW を呼び出し、PEE%X 形式でイベントを作成しますが、ここで%Xは親PIDです。ミューテックスが2つともうまく作成できたら、再び同じパスから15.exeを開始します。子プロセスを開始した後、PEE%Xイベントで WaitForSingleObject を呼び出します。
Emotetのサンプルのいくつかで、コマンドラインスイッチで子プロセスを開始していることがわかりました。このコマンドラインスイッチは、Emotetプロセスが子プロセスとして開始されていること、指定されたタスクを実行しなければならないことを示す指標です。
開始された子プロセスは、上記の2つのミューテックスを作成するかどうか判断できるまで、まったく同じことを行います。今度は、ミューテックスPEM[PPID]のためのCreateMutexの呼び出しは、「ERROR_ALREADY_EXISTS」のエラーを返して失敗します。 子プロセスでのミューテックス作成が失敗した後、イベントPEE[PPID]の信号を親プロセス15.exeに送ります。親プロセスは、待機状態からExitし、みずから終了します。
図36 – CreateMutexとCreateEventへの呼び出しに基づき子プロセスの開始の決定を示す制御フローグラフ
図37 – Emotetの子プロセス15.exe(1352または0x548)および親PID(3520または0xDC0)のPID
図38 – ミューテックスオブジェクト名PEMDC0に対するCreateMutex呼び出し。ここで、0xDC0は親PID
図39 – ミューテックスオブジェクト名PEM548でのCreateMutex呼び出し。ここで、0x548はEmotetプロセス15.exeのPIDです。
図40 – イベントオブジェクト名PEE548に対するCreateEventW呼び出し。ここで、0x548はEmotetプロセス15.exeのPID。
次に、起動された子プロセスは「ipropmini」というサービスを作成し、コマンド・アンド・コントロール(C&C)通信を確立します。
概要
図41 – ペイロード開梱手順の要約図
- 投下されたEmotetバイナリ(15.exe)は、実行許可によって新たなメモリ領域を割当てし、そこにコードスタブを書き込みます(図41、メモリ領域1)。
- このスタブが埋め込まれたPEファイルをイメージの.data部分から解読し、これを新たなメモリ領域に書き込みます(図41、メモリ領域2)。
- メモリ領域2に書き込まれたファイルは、別のEmotetバイナリである有効なPEファイルで、再割当てする必要がなく実行されます。
- メモリ領域1のスタブは、実行許可によって新しい領域を割当てます(図41、メモリ領域3)。
- スタブは、埋め込まれたペイロードをメモリ領域2から読み取り、これをメモリ領域3に書き込みます。
- ペイロードをメモリ領域3に書き込んだ後、新しいコードとトランポリンを挿入することによって、これを変更します。
- メモリ領域3でペイロードの準備ができると、15.exeイメージのマップが無効化されます。
- イメージのマップ無効化の後、実行許可によってメモリ領域3と同じ大きさの新しい領域が割当てられ、この新しく割当てられた領域に、メモリ領域3からペイロードがコピーされます(図41、メモリ領域4)。
- 次にスタブはメモリ領域4に実行を移し、そこでメインのEmotetペイロードが開始されます。
結論
Emotetは、メインのペイロードを解凍するために多層アプローチを使っています。マップされたイメージを変更した後、自己侵入テクニックを用いてメモリ内でペイロードを実行します。ペイロードはファイルシステムに書き込まれることがなく、フックとJMP命令があるため、アンチウィルスや動的コード解析に基づいた検出を簡単に回避することができるのです。
侵害の痕跡
Emotetのドロッパーは、下記のレジストリキーへの読み取りアクセスを遮断することによって食い止められます。ドロッパーは無限ループに入るか、プロセスを終了するので、ペイロードが実行される前に条件分岐が発生し、システムの統合性に害を及ぼしません。
- 32ビットシステムの場合
- HKEY_CLASSES_ROOT \インターフェイス\ {AA5B6A80-B834-11D0-932F-00A0C90DCAA9}
- 64ビットシステムの場合
- HKEY_CLASSES_ROOT \ Wow6432Node \ Interface \ {AA5B6A80-B834-11D0-932F-00A0C90DCAA9}
Emotetドロッパーは、次の方法で検出できます。
- %USERPROFILE%や%TEMP%など、グローバルに書き込み可能なディレクトリから起動されたプロセスによるレジストリキー“ Interface \ {AA5B6A80-B834-11D0-932F-00A0C90DCAA9}”への読み取りアクセスの監視。
- モニタリングAPIは、関数名「mknjht34tfserdgfwGetProcAddress」に対してGetProcAddressを呼び出します。
- GetProcAddressへのAPI呼び出しのシーケンスを監視することは、ヒューリスティックとして使用できます。
本和訳文の著作権は株式会社ブロードに帰属します。