ブログ(脅威調査)

マルウェア解析エミュレーター Speakeasyを使って 不正なシェルコードを紐解く

私たちはマルウェア・サンプルの大規模なエミュレーションを可能にするために、Speakeasyエミュレーション・フレームワークを開発しました。Speakeasyは、リバース・エンジニアが難解なマルウェア・ファミリーをトリアージするカスタム・プラグインを作成できるようにすることと、マルウェア・アナリストではないユーザーができる限り簡単に自動でトリアージ・レポートを取得できるようにすることを目指しています。

もともと Windows カーネル・モードのマルウェアをエミュレートするために作成された Speakeasy ですが、ユーザー・モードのサンプルもサポートするようになりました。このプロジェクトの主な目標は、x86およびamd64プラットフォームの動的マルウェア解析用Windowsオペレーティング・システムの高解像度エミュレーションです。同様のエミュレーション・フレームワークは、ユーザー・モード・バイナリをエミュレートするために存在します。Speakeasyは次の方法で他のエミュレーション・フレームワークとの差別化を図ります。

  • Windows マルウェアのエミュレーション周辺に特化した設計
  • rootkitのトリアージが困難な分析の際にカーネル・モード・バイナリのエミュレーションをサポート
  • 追加のツールなしでIOC (Indicator of Compromise)を抽出する手段をコミュニティに提供するため、現在のマルウェアの傾向による駆動エミュレーションとAPIサポート
  • 追加コードを必要としない、完全に設定可能なエミュレーション環境

このプロジェクトは現在、カーネル・モード・ドライバー、ユーザー・モードWindows DLL、実行ファイル、シェルコードをサポートしています。マルウェアのサンプルは自動的にエミュレートでき、後で実行処理レポートが生成されます。進行中のプロジェクト目標は、新規または一般的なマルウェア・ファミリーのサポートを追加し続けることです。

このブログの記事では、オンラインのマルウェア・リポジトリーから取得したCobalt Strike Beaconのサンプルからネットワーク・インディケータを自動的に抽出する際のSpeakeasyの有効性の例を示します。

背景

Windowsマルウェアの動的分析は、マルウェア解析プロセスにおいて、常に重要な手順とされてきました。マルウェアがWindows APIとどのように相互作用するかを理解し、重要なホストベースおよびネットワークベースのIOC (Indicator of Compromise) を抽出することは、マルウェアが影響を受けるネットワークに与える影響を評価する上で重要です。動的解析は通常、自動化された方法またはターゲットを絞った方法で実行されます。マルウェアは、サンドボックス内で実行するキューに入れてその機能を監視したり、手動でデバッグしてサンドボックス上では実行されなかったコード・パスを明らかにしたりできます。

コード・エミュレーションは、テスト、検証、さらにはマルウェア解析に使用されてきました。悪意のあるコードをエミュレートできることは、手動分析と自動分析の両方から多くの利点を得られます。CPU 命令のエミュレーションにより、最大のコード・カバレッジに対する制御フローの影響を受けるバイナリ・コードすべてのインストルメンテーションが可能になります。エミュレート中に、すべての機能を監視およびログ記録して、IOC (Indicator of Compromise)やほかの有用なインテリジェンスをすばやく抽出できます。

エミュレーションは、ハイパーバイザー・サンドボックス内での実行に比べていくつかの利点があります。主な利点はノイズ・リダクションです。エミュレート中、記録できるアクティビティーは、マルウェア作成者によって書き込まれるか、バイナリー内で静的にコンパイルされます。ハイパーバイザー内でのAPIフッキング(特にカーネルモードの観点から)は、マルウェア自体に関連付けることが困難な場合があります。たとえば、サンドボックスソリューションでは、多くの場合、マルウェア作成者がメモリを割り当てることを意図しているかどうか、または下位レベルのAPIがメモリ割り当てを実際に行っているかどうかに関係なく、ヒープ・アロケーターAPI呼び出しをフックすることがあります。

しかし、エミュレーションにも欠点があります。分析フェーズからオペレーティング・システムを削除しているため、エミュレーション中に発生するAPIコールおよびメモリ・アクセスからの期待されるインプットとアウトプットを提供する責任をエミュレーターが負います。これにより、正規のWindows システムで実行されるマルウェア・サンプルを正常にエミュレートするためにはかなりの労力が必要になります。

攻撃プラットフォームとしてのシェルコード

一般にシェルコードは、攻撃者が感染したシステムをステルス状態にしておく最適な選択肢です。シェルコードは実行可能メモリ内で実行されるため、ディスク上のどのファイルでもバッキングする必要はありません。これにより、攻撃者のコードは従来のフォレンジック分析のほとんどの形式では識別できないであろうメモリに、容易に隠蔽することができます。シェルコードを読み込む元のバイナリ・ファイルを最初に識別するか、シェルコード自体をメモリからダンプする必要があります。検知を回避するために、シェルコードを良性のローダー内に隠し、その後、別のユーザー・モード・プロセスに挿入することができます。

このブログシリーズの最初の部分では、インシデントレスポンス調査中に発生したシェルコード・マルウェアの一般的なサンプルの 1つで、エミュレーションの有効性を示します。Cobalt Strikeは、一般的に追加の符号を実行するためにステージャーを利用する商用ペネトレーション・テスト・サービスのフレームワークです。ステージャーの一例として、HTTPリクエストを介して追加のコードをダウンロードし、HTTP対応データを実行するものがあります。この場合のデータは、一般にデコード・ループで始まり、自己を反射的にロードするコードを含む有効なPEが続くシェルコードです。Cobalt Strikeの場合、実行ファイル・ヘッダーの先頭から実行でき、それ自体がメモリに読み込まれます。Cobalt Strikeにおいて、この場合のペイロードとは典型的にビーコンとして知られるインプラントのことです。ビーコンは、感染したWindowsシステム上でC2サーバとの接続を維持するために使用されるメモリ常駐バックドアとして設計されています。これは、コードの修正なしにCobalt Strikeフレームワークを使用して構築され、そのコア機能とコマンドおよび制御情報を修正するよう簡単に構築することができます。

これらすべてにより、攻撃者は、侵害されたネットワーク上にビーコン埋め込みの新しいバリアントを迅速に構築し、展開することができます。したがって、ビーコンの可変コンポーネントを迅速に抽出するツールが必要であり、マルウェア・アナリストの貴重な時間を費やさないことが理想的です。

Speakeasyの設計

Speakeasyは現在、x86およびamd64アーキテクチャ用のCPU命令をエミュレートするためにQEMUベースのエミュレーター・エンジンUnicornを採用しています。Speakeasyは、将来的に任意のエミュレーション・エンジンをアブストラクション・レイヤー経由でサポートするように設計されていますが、現在はUnicornに依存しています。

Windowsのすべてを汎用的にエミュレートすることはやや不可能であるため、すべてのサンプルを分析するには、OSの完全サンドボックス化が常に必要になる可能性があります。サンドボックス化は、オンデマンドでスケールすることが困難な場合があり、サンプルの実行に時間がかかることがあります。ただしこの例のビーコンなど、特定のマルウェア・ファミリーを確実にエミュレートすることで、リバース・エンジニアリング・バリアントの必要性を迅速に減らすことができます。高レベルのトリアージ・レポートを自動化された方法で生成できることは、多くの場合、マルウェア・バリアントに必要なすべての分析ができるということです。これにより、マルウェア・アナリストは、より深い分析を必要とする可能性のあるサンプルに集中することができます。

シェルコードまたはWindows PEは、エミュレートされたアドレス空間にロードされます。Windowsカーネル・モードとユーザー・モードの基本的なエミュレーションを容易にするために必要なWindowsデータ構造は、マルウェアのエミュレーションを試みる前に作成されます。プロセス、ドライバー、デバイス、ユーザー・モード・ライブラリは、リアルに近い実行環境をマルウェアに提供するために「偽造」されます。マルウェアは、エミュレートされたファイル・システム、ネットワーク、レジストリと対話できます。これらすべてのエミュレーション・サブシステムは、各エミュレーション・ランに提供されるコンフィギュレーション・ファイルで設定できます。

Windows APIはPython APIハンドラーによって処理されます。これらのハンドラーは、マルウェアサンプルが想定される実行パスを継続できるように、これらのAPIから想定される出力をエミュレートしようとします。APIハンドラーを定義する場合、必要なのはAPIの名前、APIが想定する引数の数、およびオプションの呼び出し規則の仕様だけです。呼び出し規則が指定されていない場合は、stdcall が使用されます。現在、サポートされていないAPI呼び出しが試行されると、SpeakeasyはサポートされていないAPIをログに記録し、次のエントリー・ポイントに移動します。kernel32.dllによってエクスポートされたWindows HeapAlloc関数のハンドラーの例を図1に示します。


図1:Windows HeapAlloc関数のハンドラーの例

すべてのエントリー・ポイントはデフォルトでエミュレートされます。たとえば、DLLの場合はすべてのエクスポートがエミュレートされ、ドライバの場合はIRPの主要な機能がそれぞれエミュレートされます。さらに、実行時に検出された動的エントリー・ポイントに従います。動的エントリー・ポイントの例には、作成されるスレッドや登録されるコールバックなどがあります。マルウェア感染の影響を特定しようとする場合、活動を特定のエントリー・ポイントに割り当てることが全体像を確認する上で重要になることがあります。

レポート

現在では、エミュレーターによってキャプチャーされたすべてのイベントがログに記録され、JSONレポートによって表されるため、簡単に後処理ができます。このレポートには、エミュレーション中に記録される対象のイベントが含まれています。ほとんどのエミュレーターと同様に、すべてのWindows API呼び出しは引数とともにログに記録されます。すべてのエントリー・ポイントがエミュレートされ、対応するAPIリストでタグ付けされます。APIトレースに加えて、ファイル、レジストリ、ネットワーク・アクセスなど、その他の特定のイベントも呼び出されます。すべてのデコードまたは「メモリ常駐」文字列は、静的文字列分析で見つからない有用な情報を明らかにするために、レポートにダンプおよび表示されます。図2は、Speakeasy JSONレポートに記録されるファイル読み取りイベントの例を示しています。


図2:Speakeasyレポートのファイル読み取りイベント

速度

フレームワークは Pythonで記述されているため、速度が懸念されるのは明らかです。UnicornとQEMUはCで書かれ、非常に高速なエミュレーション速度を提供します。ただし、作成する API ハンドラーとイベント・ハンドラーは Python で記述されます。ネイティブコードとPython間の移行は非常にコストがかかるため、できるだけ少なくする必要があります。したがって、Pythonコードが絶対に必要な場合にのみ実行することを目指します。デフォルトでは、Pythonで扱うイベントはメモリ・アクセス例外かWindows API呼び出しだけです。Windows API呼び出しをキャッチし、Pythonでそれらをエミュレートするために、インポート・テーブルは無効なメモリ・アドレスでドープされるため、インポート・テーブルにアクセスしたときにのみPythonに切り替わります。同様の手法は、シェルコードがマルウェアのエミュレートされたアドレス空間内にロードされたDLLのエクスポート・テーブルにアクセスする場合にも使用されます。できるだけ少ないPythonコードを実行することで、ユーザーがフレームワークの機能を迅速に開発できるようにしながら、許容できるスピードを維持できます。

メモリ管理

Speakeasyは、エミュレーター・エンジンのメモリ管理の上に軽量なメモリ・マネージャを実装します。マルウェアによって割り当てられたメモリの各チャンクは、意味のあるメモリ・ダンプを取得できるように追跡され、タグ付けされます。特定のメモリ・チャンクに活動を属性付けできることは、アナリストにとって非常に有用であると証明できます。メモリの読み取りと書き込みを機密データ構造に記録すると、API呼び出しログによって明らかにされないマルウェアの真の意図が明らかになる可能性があります。これは、rootkitなどのサンプルに特に役立ちます。

Speakeasyは、サンプルが示すすべてのメモリ・アクセスを記録するオプションの「メモリ・トレース」機能を提供します。これにより、すべての読み取り、書き込み、実行がメモリに記録されます。エミュレーターは、割り当てられたすべてのメモリ・チャンクにタグ付けするため、このデータからはるかに多くのコンテキストを収集することができます。マルウェアが重要なデータ構造をフックしたり、動的にマップされたメモリに実行をピボットした場合、これが明らかになり、デバッグやアトリビューションに役立ちます。ただし、この機能は非常に高速なコストで提供されるため、デフォルトでは有効になっていません。

マルウェアに提示されるエミュレート環境には、エクスポートされたWindowsシステム関数を見つけて実行するためにシェルコードが使用する一般的なデータ構造が含まれています。Win32 APIを呼び出すには、エクスポートされた機能を解決する必要があるため、ターゲット・システムにわかりやすく影響します。ほとんどの場合、ビーコンが含まれるこれらの関数は、プロセス環境ブロック(一般にPEBと呼ばれます) を歩くことによって配置されます。PEBから、シェルコードはプロセスの仮想アドレス空間内にロードされたすべてのモジュールのリストにアクセスできます。

図3は、ビーコン・シェルコード・サンプルのエミュレーションから生成されたメモリレポートを示しています。ここでは、kernel32.dllのアドレスを見つけるためにPEBをたどっているマルウェアをトレースできます。その後、マルウェアは"VirtualAlloc"APIの関数ポインタを手動で解決して呼び出し、デコードに進み、新しいバッファにコピーして実行をピボットします。


図3:メモリ・トレース・レポート

構成

Speakeasyは高度に設定可能で、ユーザーは独自の「実行プロファイル」を作成できます。個々のユースケースを最適化するために、さまざまなレベルの分析を指定できます。最終的な目標は、コードを変更せずに、ユーザが設定オプションを簡単に切り替えられるようにすることです。構成プロファイルは現在、JSONファイルとして構造化されています。利用者からプロファイルが提供されない場合は、フレームワークによってデフォルト設定が提供されます。個々のフィールドは、Speakeasyプロジェクト内でドキュメント化されています。

図4に、ネットワーク・エミュレーターの構成サブセクションのスニペットを示します。ここで、ユーザーは、DNSルックアップが発生したときに返されるIPアドレスや、ビーコン・サンプルの場合は、テキスト・レコード・クエリ中に返されるバイナリー・レコードを指定できます。HTTP対応にはカスタム対応が設定されています。


図4:ネットワーク構成

多くのHTTPステージャーは、HTTP GET要請を使用してWebリソースを取得します。多くの場合、Cobalt StrikeやMetasploitのステージャーなどでは、このバッファはすぐに実行されるため、次のステージを開始できます。この対応は、Speakeasy構成で簡単に構成できます。図4のコンフィギュレーションでは、オーバーライドされない限り、フレームワークは参照先default.binファイルに含まれるデータを提供します。このファイルには現在、デバッグ割り込み命令(int3)が含まれているため、マルウェアがデータを実行しようとすると終了し、レポートに記録されます。これを使用して、追加のコードをダウンロードするダウンローダーとしてマルウェアに簡単にラベルを付けることができます。設定フィールドは、ファイル・システムおよびレジストリ・エミュレーションにも存在します。ファイルとレジストリ・パスも同様に、ライブWindowsシステムで実行されるサンプルにデータを返すように構成できます。

制限

前述のように、エミュレーションにはいくつかの課題があります。エミュレートするシステムと同等の機能を提供することは、目下の課題です。ただし、マルウェアを制御し、イントロスペクション・オプションを強化するための独自の機会を提供します。

エミュレーションが完全に完了しない場合でも、エミュレーション・レポートとメモリ・ダンプを生成して、できるだけ多くのデータを収集できます。たとえば、バックドアは持続的メカニズムを正常にインストールできても、C2サーバーへの接続に失敗する場合があります。この状況では、重要なホストベースのインディケータは引き続きログに記録され、アナリストに値を提供できます。

不足しているAPIハンドラーは、これらの状況を処理するためにエミュレーターにすばやく簡単に追加できます。多くのAPIハンドラーでは、単純に成功コードを返すだけで、マルウェアが実行を継続できるようになります。マルウェアのすべての部分の完全なエミュレーションは実現不可能かもしれませんが、特定のマルウェア・ファミリーの機能をターゲットにすることで、同じファミリーのリバース・エンジニア・バリアントの必要性を大幅に減らすことができます。

利用方法

Speakeasyは、GitHubで提供を開始しました。付属のPythonインストーラスクリプトと一緒にインストールすることも、提供されているリリース・ファイルを使用して今回のリリースコンテナ内にインストールすることもできます。プラットフォームに依存せず、Windows、Linux、またはMacOSでWindowsマルウェアをエミュレートするために使用できます。詳細については、プロジェクトのREADMEを参照してください。

インストールすると、Speakeasyをスタンドアロンライブラリとして使用したり、提供されているrun_speakeasy.pyスクリプトを使用して直接呼び出したりできます。このブログの記事では、コマンド・ラインからマルウェア・サンプルを直接エミュレートする方法を紹介します。Speakeasyをライブラリーとして使用する方法については、プロジェクトのREADMEを参照してください。

付属のスクリプトは、単一のサンプルをエミュレートし、ログに記録されたイベントを含むJSONレポートを生成することを目的としています。run_speakeasy.pyのコマンド・ライン引数を図5に示します。


図5:run_speakeasy.pyのコマンド・ライン引数

Speakeasyでは、カスタム・プラグインを作成するための豊富な開発とフック・インターフェイスも提供されています。これについては、後のブログ記事で詳しく説明します。

ビーコン・インプラントのエミュレーション

この例では、7f6ce8a8c2093eaf6fea1b6f1ef68a957c1a06166d20023ee5b637b5f7838918のSHA-256というハッシュを持つビーコン・インプラント・バリアントをデコードして実行するシェルコードをエミュレートします。まず、サンプルのファイル形式を検証します。このサンプルは、ローダーによって起動されるか、エクスプロイト・ペイロードの一部として使用されることが想定されます。


図6:マルウェア・サンプルの16進ダンプ

図6では、ファイルがPEファイル形式ではないことがはっきりわかります。多くのシェルコード・サンプルを見てきたアナリストは、最初の2バイト:"0xfc0xe8"に気付くかもしれません。これらのバイトは、Intelアーキテクチャのアセンブリ命令"cld"と"call"に逆アセンブルされます。"cld"命令は、シェルコードを挿入して、システムDLLのエクスポート・テーブルから簡単に文字列データをパースできるようにディレクション・フラグをクリアするための一般的な導入部です。次の呼び出し命令は、"pop" 命令で続けて現在のプログラム・カウンターを取得するために、シェルコードによって使用されます。これにより、マルウェアはメモリ内で実行されている場所を検出できます。

このサンプルがシェルコードであることは確実であるため、図7に示すコマンド・ラインを使用してSpeakeasyを呼び出します。


図7:マルウェア・サンプルのエミュレーションに使用するコマンド・ライン

ここでは、オフセット・ゼロからのサンプルをx86シェルコードとしてエミュレートするようにSpeakeasyに指示します。Speakeasyはコードをエミュレートしていて実際に実行していなくても、これらは攻撃者が生成したバイナリであることをよく注意してください。ネイティブのCPUエミュレーションエンジンが使用されている場合でも、脆弱性が検出された場合は、仮想マシン内で悪質なコードをエミュレートすることをお勧めします。

エミュレーション後、「report.json」という名前のレポートが生成されます。さらに、エミュレーション環境のフル・メモリ・ダンプが圧縮され、「memory_dump.zip」に書き込まれます。マルウェアは、偽のコンテナ・プロセス内のエミュレートされたメモリにロードされ、シェルコードが実行されると予想される実際の実行環境をシミュレートします。エミュレーションが開始されると、エミュレートされたAPI呼び出しは、その引数と戻り値とともに画面に記録されます。図8は、ビーコンが自身をコピーする新しいメモリ・バッファを割り当てるサンプルを示しています。その後、マルウェアは実行する必要があるエクスポートを手動で解決し始めます。


図8:ネットワーク構成

追加のデコードとセットアップの後、マルウェアはC2サーバーへの接続を試みます。図9では、Wininetライブラリを使用し、HTTPを使用してC2サーバに接続しデータを読み取るマルウェアを確認できます。


図9:C2に接続するWininet API呼び出し

マルウェアは、C2サーバーから送られてくるはずのデータを受信するまで、無限にループします。Speakeasyは、事前定義された時間が経過するとタイムアウトし、JSONレポートを生成します。


図10:ネットワークC2イベント

ネットワーク・インディケータは、生成されたレポートの「network_events」セクションと「トラフィック」セクションに要約されます。図10では、IPアドレス、ポート番号、そしてこの場合はマルウェアによって行われた接続に関連付けられたHTTPヘッダーを確認できます。

この例では、サンプルをエミュレートするときに、エミュレートされたアドレス空間のメモリ・ダンプを作成するようにSpeakeasyに指示します。ZIPアーカイブは、各メモリのアロケーションとその周辺のコンテキストを作成します。このコンテキストには、メモリ割り当てが何に対応するかを識別するためにエミュレータによって割り当てられるベース・アドレス、サイズ、およびタグが含まれます。図11に、エミュレーション中に作成されたメモリ・ダンプ・ファイルのスニペットを示します。ファイル名には、各メモリ割り当てに関連付けられたタグとベース・アドレスが含まれます。


図11:エミュレーションから取得した個々のメモリ・ブロック

これらのメモリ・ダンプで文字列を実行すると、図12に示すビーコン設定データとともに、興味深い文字列をすばやく見つけることができます。


図12:マルウェアの構成文字列データ

トリアージ・レベル解析では、既知のファミリーのマルウェア・バリアントに対するIOCのみが対象となる場合があります。しかし、サンプルの完全なリバース・エンジニアリングが必要な場合、ビーコン・マルウェアのデコードされたバージョンをDLL形式で回復することもできます。「MZ」マジックバイトに対して、ただgrepを実行するだけで、オリジナルのサンプルの割り当てに関連するメモリ・ダンプと、マルウェアが自身をコピーする仮想的に割り当てられたバッファのみがヒットします(図13)。.


図13:デコードされたマルウェアを含むメモリ・ダンプ

元のシェルコード・バッファのバイトを見ると、コピーされる前にデコードされていて、オフセット0x48でダンプできる状態のメモリにあることがわかります。これで、デコードされたビーコンDLLをIDA Proに正常にロードして、完全な分析を行うことができます(図14)。


図14:IDA Proに正常にロードされたデコード済みマルウェア

結論

このブログの記事では、Speakeasyエミュレーション・フレームワークを使用してビーコン・マルウェア・サンプルを自動的にトリアージする方法を示しました。これを用いて、貴重なネットワーク・インディケータを発見し、メモリからその構成情報を抽出、さらなる分析のためにデコードされたビーコンDLLを取得しました。

GitHubで提供しているSpeakeasyをぜひお使いになってください。エミュレーションを使用したカーネル・マルウェア解析を次回のブログ記事で紹介しますのでご期待ください。


本ブログは、米FireEyeが公開したAugust 26, 2020 「Emulation of Malicious Shellcode With Speakeasy」(英語)の日本語抄訳版です。

日本語版:Reviewed by Toru Tanimura