Speakeasy による悪意のあるシェルコードのエミュレーション

Example handler for Windows HeapAlloc function news

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

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

  • Windows マルウェアのエミュレーションに特化した設計
  • トリアージが困難なルートキットを分析するためのカーネル モード バイナリのエミュレーションをサポート
  • 現在のマルウェアの傾向に基づいたエミュレーションと API のサポートにより、追加のツールを使用せずに侵害の兆候を抽出する手段をコミュニティに提供します。
  • 追加コードを必要としない、完全に構成可能なエミュレーション環境

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

このブログ投稿では、オンライン マルウェアの集合体から取得した Cobalt Strike Beacon サンプルからネットワーク インジケーターを自動的に抽出する Speakeasy の効果の例を紹介します。

バックグラウンド

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

コード エミュレーションは、歴史的にテスト、検証、さらにはマルウェア分析にも使用されてきました。悪意のあるコードをエミュレートできることは、手動分析と自動分析の両方から多くの利点をもたらします。 CPU 命令のエミュレーションにより、バイナリ コードの完全なインストルメンテーションが可能になり、制御フローに影響を与えてコード カバレッジを最大化できます。エミュレート中は、すべての機能を監視してログに記録し、侵害の兆候やその他の有用なインテリジェンスをすばやく抽出できます。

エミュレーションには、ハイパーバイザー サンドボックス内での実行よりもいくつかの利点があります。主な利点はノイズリダクションです。エミュレート中に記録できる唯一のアクティビティは、マルウェア作成者によって記述されたか、バイナリ内で静的にコンパイルされたものです。ハイパーバイザー内での API フック (特にカーネル モードの観点から) は、マルウェア自体に起因するとは考えにくい場合があります。たとえば、サンドボックス ソリューションは、多くの場合、マルウェアの作成者がメモリを割り当てることを意図していたのか、それとも下位レベルの API がメモリ割り当てを担当していたのかを知らずに、ヒープ アロケーター API 呼び出しをフックします。

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

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

一般に、シェルコードは、攻撃者が感染したシステムでステルスを維持するための優れた選択肢です。シェルコードは実行可能メモリ内で実行され、ディスク上のファイルによってサポートされる必要はありません。これにより、従来のフォレンジック分析のほとんどの形式では識別に失敗するメモリ内に攻撃者コードを簡単に隠すことができます。シェルコードをロードする元のバイナリ ファイルを最初に識別するか、シェルコード自体をメモリからダンプする必要があります。検出を回避するために、シェルコードは無害なように見えるローダー内に隠され、別のユーザー モード プロセスに挿入される可能性があります。

このブログ シリーズの第 1 部では、インシデント対応の調査中に遭遇するシェルコード マルウェアのより一般的なサンプルの 1 つを使用して、エミュレーションの有効性を示します。 Cobalt Strike は商用ペネトレーション テスト フレームワークで、通常はステージャーを使用して追加のコードを実行します。ステージャーの例は、HTTP 要求を介して追加のコードをダウンロードし、HTTP 応答データを実行するものです。この場合のデータは、一般にデコード ループで始まるシェルコードであり、その後に反射的に自身をロードするコードを含む有効な PE が続きます。 Cobalt Strike の場合、これは、実行可能なヘッダーの先頭から実行でき、それ自体をメモリにロードすることを意味します。 Cobalt Strike フレームワーク内では、この場合のペイロードは通常、ビーコンと呼ばれるインプラントです。 Beacon は、感染した Windows システム上でコマンド アンド コントロール (C2) を維持するために使用されるメモリ常駐バックドアとして設計されています。これは、コードを変更せずに Cobalt Strike フレームワークを使用して構築されており、コア機能とコマンドおよび制御情報を変更するように簡単に構築できます。

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

スピークイージー デザイン

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

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

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

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

Example handler for Windows HeapAlloc function
図 1: Windows HeapAlloc 関数のハンドラーの例

デフォルトでは、すべてのエントリ ポイントがエミュレートされます。たとえば、DLL の場合はすべてのエクスポートがエミュレートされ、ドライバーの場合は IRP の主要な機能がそれぞれエミュレートされます。さらに、実行時に検出される動的エントリ ポイントが追跡されます。動的エントリ ポイントの例には、作成されたスレッドや登録されたコールバックが含まれます。マルウェア感染の影響を特定しようとする際に、全体像を把握するには、アクティビティを特定のエントリ ポイントに関連付けることが重要になる場合があります。

報告

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

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

スピード

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

メモリ管理

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

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

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

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

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

構成

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

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

ネットワーク設定
図 4: ネットワーク構成

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

制限事項

前述のように、エミュレーションにはいくつかの課題があります。エミュレートされているシステムと同等の機能を維持することは、進行中の戦いです。ただし、マルウェアを制御する独自の機会と、より優れたイントロスペクション オプションを提供します。

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

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

使用法

Speakeasy は、GitHub ですぐに利用できます。付属の Python インストーラー スクリプトを使用してインストールするか、提供されている Dockerfile を使用して Docker コンテナー内にインストールできます。プラットフォームにとらわれず、Windows、Linux、または MacOS で Windows マルウェアをエミュレートするために使用できます。詳細については、プロジェクトのREADMEを参照してください。

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

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

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

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

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

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

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

図 6 では、ファイルが PE ファイル形式ではないことがはっきりとわかります。多くのシェルコード サンプルを見たアナリストは、最初の 2 バイト「0xfc 0xe8」に気付くかもしれません。これらのバイトは、インテルのアセンブリ命令「cld」および「call」に逆アセンブルされます。 「cld」命令は、マルウェアがシステム DLL のエクスポート テーブルから文字列データを簡単に解析できるようにする方向フラグをクリアするため、独立したシェルコードを配置するための一般的な前奏曲です。次の呼び出し命令は、「pop」命令を続けて現在のプログラム カウンターを取得するためにシェルコードでよく使用されます。これにより、マルウェアはメモリ内のどこから実行されているかを検出できます。

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

マルウェアのサンプルをエミュレートするために使用されるコマンド ライン
図 7: マルウェアのサンプルをエミュレートするために使用されるコマンド ライン

これにより、サンプルをオフセット 0 から x86 シェルコードとしてエミュレートするよう Speakeasy に指示します。注: コードをエミュレートしていて実際には実行していませんが、これらは依然として攻撃者が生成したバイナリです。使用されているネイティブ CPU エミュレーション エンジンに脆弱性が発見された場合は、仮想マシン内で悪意のあるコードをエミュレートするのが賢明な場合があります。

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

ネットワーク設定
図 8: ネットワーク構成

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

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

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

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

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

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

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

これらのメモリ ダンプに対して文字列を実行するだけで、図 12 に示すように、ビーコン構成データと共に興味深い文字列をすばやく見つけることができます。

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

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

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

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

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

結論

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

GitHub にアクセスして、今日から Speakeasy の使用を開始してください。エミュレーションを使用したカーネル マルウェア分析のデモンストレーションを行う次のブログ投稿にご期待ください。

参照: https://www.mandiant.com/resources/blog/emulation-of-malicious-shellcode-with-speakeasy

Comments

Copied title and URL