Speakeasy を使用したカーネル モードのルートキットのエミュレーション

Command line used to emulate the malicious driver news

2020 年 8 月に、 Speakeasy エミュレーション フレームワークを使用してシェルコードなどのユーザー モード マルウェアをエミュレートする方法に関するブログ投稿をリリースしました。機会がなかった場合は、今日この投稿を読んでください

ユーザー モードのエミュレーションに加えて、Speakeasy はカーネル モードの Windows バイナリのエミュレーションもサポートしています。マルウェアの作成者がカーネル モードのマルウェアを使用する場合、多くの場合、感染したシステムを完全に侵害することを最終目標とするデバイス ドライバーの形になります。ほとんどの場合、マルウェアはハードウェアと対話せず、代わりにカーネル モードを利用してシステムを完全に侵害し、隠れたままにします。

カーネル マルウェアを動的に分析する際の課題

理想的には、逆アセンブラーなどのツールを使用して、カーネル モードのサンプルを静的に元に戻すことができます。ただし、バイナリ パッカーは、ユーザー モードのサンプルと同じくらい簡単にカーネル マルウェアを難読化します。さらに、静的分析は多くの場合、費用と時間がかかります。私たちの目標が、同じマルウェア ファミリの多くの亜種を自動的に分析することである場合、悪意のあるドライバー サンプルを動的に分析することは理にかなっています。

カーネル モードのマルウェアの動的分析は、ユーザー モードのサンプルよりも複雑になる可能性があります。カーネル マルウェアをデバッグするには、適切な環境を作成する必要があります。これには通常、2 つの個別の仮想マシンをデバッガーとデバッグ対象としてセットアップすることが含まれます。その後、マルウェアはオンデマンドのカーネル サービスとして読み込まれ、WinDbg などのツールを使用してドライバーをリモートでデバッグできます。

フックまたはその他の監視手法を使用するサンドボックス スタイルのアプリケーションがいくつか存在しますが、通常はユーザー モード アプリケーションを対象としています。カーネル モード コードに対して同様のサンドボックス監視作業を行うには、システム レベルの深いフックが必要になり、重大なノイズが発生する可能性があります。

ドライバー エミュレーション

エミュレーションは、悪意のあるドライバーの効果的な分析手法であることが証明されています。カスタム設定は不要で、ドライバーを大規模にエミュレートできます。さらに、最大のコード カバレッジは、サンドボックス環境よりも簡単に達成できます。多くの場合、ルートキットは、I/O 要求パケット (IRP) ハンドラー (またはその他のコールバック) を介して悪意のある機能を公開する可能性があります。通常の Windows システムでは、これらのルーチンは、他のアプリケーションまたはデバイスが入出力要求をドライバーに送信したときに実行されます。これには、ある種の機能を実行するためのデバイス I/O コントロール (IOCTL) の読み取り、書き込み、ドライバーへの送信などの一般的なタスクが含まれます。

エミュレーションを使用すると、これらのエントリ ポイントをドープされた IRP パケットで直接呼び出すことができ、ルートキットの機能を可能な限り識別できます。最初の Speakeasy ブログ投稿で説明したように、追加のエントリ ポイントが検出されるとエミュレートされます。ドライバーの DriverMain エントリ ポイントは、I/O 要求を処理するために呼び出される関数ディスパッチ テーブルの初期化を担当します。 Speakeasy は、メイン エントリ ポイントが完了すると、ダミーの IRP を提供してこれらの各機能をエミュレートしようとします。さらに、作成されたシステム スレッドまたは作業項目は、できるだけ多くのコード カバレッジを取得するために順次エミュレートされます。

カーネル モード インプラントのエミュレート

このブログ投稿では、Winnti という名前で公開されている実際のカーネル モード インプラント ファミリをエミュレートする際の Speakeasy の有効性の例を示します。このサンプルは、いくつかの従来のルートキット機能を透過的に実装するため、古いにもかかわらず選択されました。この投稿の目的は、マルウェア自体の分析について議論することではありません。マルウェア自体はかなり時代遅れです。むしろ、エミュレーション中にキャプチャされるイベントに焦点を当てます。

分析する Winnti サンプルには、SHA256 ハッシュ c465238c9da9c5ea5994fe9faf1b5835767210132db0ce9a79cb1195851a36fb と元のファイル名tcprelay.sysがあります。この投稿の大部分では、Speakeasy によって生成されたエミュレーション レポートを調べます。注: この 32 ビット ルートキットで採用されている多くの手法は、重要なカーネル データ構造の変更から保護するカーネル パッチ保護 (PatchGuard) により、最新の 64 ビット バージョンの Windows では機能しません。

まず、図 1 に示すコマンド ラインを使用して、カーネル ドライバーをエミュレートするよう Speakeasy に指示します。後でメモリを取得できるように、Speakeasy にフル メモリ ダンプを作成するよう指示します (「-d」フラグを使用)。マルウェアによって実行されたすべてのメモリの読み取りと書き込みを記録するメモリ トレース フラグ (「-m」) を提供します。これは、フックや直接カーネル オブジェクト操作 (DKOM) などを検出するのに役立ちます。

Command line used to emulate the malicious driver
図 1: 悪意のあるドライバーをエミュレートするために使用されるコマンド ライン

その後、Speakeasy はマルウェアの DriverEntry 関数のエミュレートを開始します。ドライバーのエントリ ポイントは、デバイスの追加、削除、およびアンロードに使用されるコールバックだけでなく、ユーザー モードの i/o 要求にもサービスを提供するパッシブ コールバック ルーチンの設定を担当します。マルウェアの DriverEntry 機能のエミュレーション レポート (JSON レポートで「entry_point」の「ep_type」で識別) を確認すると、マルウェアが Windows カーネルのベース アドレスを見つけていることがわかります。マルウェアは、ZwQuerySystemInformation API を使用してすべてのカーネル モジュールのベース アドレスを特定し、「ntoskrnl.exe」という名前のモジュールを探します。その後、マルウェアは手動で PsCreateSystemThread API のアドレスを見つけます。次に、これを使用してシステム スレッドを起動し、実際の機能を実行します。図 2 は、マルウェアのエントリ ポイントから呼び出される API を示しています。

tcprelay.sys エントリ ポイントの主な機能
図 2: tcprelay.sys エントリ ポイントの主な機能

ドライバー オブジェクトを非表示にする

マルウェアは、メイン システム スレッドを実行する前に、自分自身を隠そうとします。マルウェアは、最初に自身の DRIVER_OBJECT 構造の「DriverSection」フィールドを検索します。このフィールドは、ロードされたすべてのカーネル モジュールを含むリンク リストを保持し、マルウェアは、ロードされたドライバをリストする API から身を隠すために自身のリンクを解除しようとします。図 3 に示す Speakeasy レポートの「mem_access」フィールドでは、DriverSection エントリへの 2 つのメモリ書き込みが、リンク リストから削除される前と後に確認できます。

非表示にするために自身のリンクを解除しようとする tcprelay.sys マルウェアを表すメモリ書き込みイベント
図 3: tcprelay.sys マルウェアが自分自身を非表示にするためにリンクを解除しようとしていることを表すメモリ書き込みイベント

元のSpeakeasy ブログ投稿で述べたように、実行時にスレッドまたはその他の動的エントリ ポイントが作成されると、フレームワークはエミュレーションのためにそれらに従います。この場合、マルウェアはシステム スレッドを作成し、Speakeasy はそれを自動的にエミュレートしました。

新しく作成されたスレッド (「system_thread」の「ep_type」で識別) に移ると、マルウェアが実際の機能を開始することがわかります。マルウェアはまず、ホスト上で実行中のすべてのプロセスを列挙し、services.exe という名前のサービス コントローラー プロセスを探します。エミュレートされたサンプルに返されるプロセス リストは、実行時に提供される JSON 構成ファイルを介して構成できることに注意することが重要です。これらの設定オプションの詳細については、 GitHub リポジトリの Speakeasy README を参照してください。この構成可能なプロセス リストの例を図 4 に示します。

Speakeasy に提供されるプロセス リスト設定フィールド
図 4: Speakeasy に提供されるプロセス リストの構成フィールド

ユーザーモードへのピボット

マルウェアが services.exe プロセスを見つけると、プロセス コンテキストにアタッチし、ユーザー モード メモリの検査を開始して、エクスポートされたユーザー モード機能のアドレスを特定します。マルウェアがこれを行うのは、エンコードされたメモリ常駐 DLL を後で services.exe プロセスに挿入できるようにするためです。図 5 は、ルートキットがユーザー モードのエクスポートを解決するために使用する API を示しています。

ユーザー モード インプラントのエクスポートを解決するために tcprelay.sys ルートキットによって使用される記録された API
図 5: tcprelay.sys ルートキットがユーザー モード インプラントのエクスポートを解決するために使用するログに記録された API

エクスポートされた関数が解決されると、ルートキットはユーザー モード DLL コンポーネントを挿入する準備が整います。次に、マルウェアはメモリ内の DLL を services.exe プロセスのアドレス空間に手動でコピーします。これらのメモリ書き込みイベントがキャプチャされ、図 6 に示されています。

ユーザー モード インプラントを services.exe にコピーするときにキャプチャされたメモリ書き込みイベント
図 6: ユーザー モード インプラントを services.exe にコピーする際にキャプチャされたメモリ書き込みイベント

ルートキットがユーザー モード コードを実行するために使用する一般的な手法には、非同期プロシージャ コール (APC) と呼ばれる Windows 機能が含まれます。 APC は、指定されたスレッドのコンテキスト内で非同期に実行される関数です。 APC を使用すると、カーネル モード アプリケーションはコードをキューに入れ、スレッドのユーザー モード コンテキスト内で実行できます。 Windows 内の一般的な機能 (ネットワーク通信など) の多くは、より簡単にアクセスできるため、マルウェアはユーザー モードに侵入することがよくあります。さらに、ユーザー モードで実行することにより、マシン全体のバグ チェックで問題のあるコードが発生した場合に検出されるリスクが少なくなります。

ユーザー モードで起動する APC をキューに入れるために、マルウェアは「アラート可能な」状態にあるスレッドを見つける必要があります。スレッドは、実行クォンタムをカーネル スレッド スケジューラに放棄し、APC をディスパッチできることをカーネルに通知するときに、アラート可能であると言われます。マルウェアは、services.exe プロセス内のスレッドを検索し、アラート可能なスレッドを検出すると、DLL が挿入するメモリを割り当て、それを実行するために APC をキューに入れます。

Speakeasy は、このプロセスに関係するすべてのカーネル構造、特に Windows システムのすべてのスレッドに割り当てられるエグゼクティブ スレッド オブジェクト ( ETHREAD ) 構造をエミュレートします。マルウェアは、スレッドのアラート可能フラグが設定されている (したがって、APC の有効な候補である) 時期を特定するために、この不透明な構造をくぐり抜けようとする場合があります。図 7 は、Winnti マルウェアが services.exe プロセスのETHREAD構造を手動で解析してアラート可能であることを確認したときにログに記録されたメモリ読み取りイベントを示しています。この記事の執筆時点では、エミュレーター内のすべてのスレッドは、デフォルトでアラート可能として表示されます。

tcprelay.sys マルウェアがスレッドがアラート可能であることを確認したときにログに記録されたイベント
図 7: tcprelay.sys マルウェアがスレッドがアラート可能であることを確認したときにログに記録されたイベント

次に、マルウェアは、このスレッド オブジェクトを使用して、任意のユーザー モード コードを実行できます。ドキュメントに記載されていない関数KeInitializeApcKeInsertQueueApcは、ユーザー モード APC をそれぞれ初期化および実行します。図 8 は、マルウェアがユーザー モード モジュールを services.exe プロセスに挿入するために使用する API セットを示しています。マルウェアはシェルコード スタブを APC のターゲットとして実行し、APC は挿入された DLL のローダーを実行します。これらはすべて、メモリ ダンプ パッケージから復元して、後で分析できます。

tcprelay.sys ルートキットが APC 経由でユーザー モードに挿入するために使用するログに記録された API
図 8: tcprelay.sys ルートキットが APC 経由でユーザー モードに挿入するために使用する記録された API

ネットワークフック

ユーザー モードに挿入した後、カーネル コンポーネントはネットワーク難読化フックのインストールを試みます (おそらくユーザー モード インプラントを非表示にするため)。 Speakeasy は、エミュレーション スペース内のすべてのメモリを追跡してタグ付けします。カーネル モード エミュレーションのコンテキストでは、これにはすべてのカーネル オブジェクトが含まれます (例: Driver オブジェクトと Device オブジェクト、およびカーネル モジュール自体)。マルウェアがユーザー モード インプラントを挿入するのを観察した直後に、カーネル コンポーネントをフックしようとし始めていることがわかります。これは、ネットワークの隠蔽に使用される静的分析中に確認されました。

エミュレーション レポートのメモリ アクセス セクションでは、マルウェアがnetio.sysドライバー、具体的にはNsiEnumerateObjectsAllParametersExという名前のエクスポートされた関数内のコードを変更したことが明らかになりました。この関数は、システムのユーザーが「netstat」コマンドを実行したときに最終的に呼び出されます。感染したシステムで接続されているネットワーク ポートを隠すために、マルウェアがこの関数をフックしている可能性があります。このインライン フックは、図 9 でキャプチャされたイベントによって識別されました。

ネットワーク接続を隠すためにマルウェアによって設定されたインライン関数フック
図 9: ネットワーク接続を隠すためにマルウェアによって設定されたインライン関数フック

さらに、マルウェアはTcpipドライバー オブジェクトをフックして、追加のネットワーク隠蔽を実現します。具体的には、マルウェアはTcpipドライバーのIRP_MJ_DEVICE_CONTROLハンドラーをフックします。ユーザー モード コードは、アクティブな接続を照会するときに、この関数に IOCTL コードを送信する場合があります。このタイプのフックは、図 10 に示すように、重要なカーネル オブジェクトへのメモリ書き込みを探すことで、Speakeasy で簡単に識別できます。

Tcpip ネットワーク ドライバーをフックするために使用されるメモリ書き込みイベント
図 10: Tcpip ネットワーク ドライバーをフックするために使用されるメモリ書き込みイベント

システム サービス ディスパッチ テーブル フック

最後に、ルートキットは、システム サービス ディスパッチ テーブル (SSDT) のパッチ適用という、ほぼ古来からの手法を使用して、自分自身を隠そうとします。 Speakeasy は偽の SSDT を割り当てて、マルウェアが操作できるようにします。 SSDT は、カーネル機能をユーザー モード コードに公開する関数テーブルです。図 11 のイベントは、SSDT 構造が実行時に変更されたことを示しています。

Speakeasy によって検出された SSDT フック
図 11: Speakeasy によって検出された SSDT フック

IDA Pro でマルウェアを調べると、マルウェアが、ファイル システムとレジストリの分析から自身を隠すために使用するZwQueryDirectoryFileおよびZwEnumerateKey API の SSDT エントリにパッチを適用していることを確認できます。 SSDT パッチ機能を図 12 に示します。

IDA Proに表示されたファイル隠蔽SSDTパッチ機能
図 12: IDA Pro に表示されたファイル隠蔽 SSDT パッチ機能

これらのフックを設定した後、システム スレッドは終了します。ドライバーのその他のエントリ ポイント (IRP ハンドラーや DriverUnload ルーチンなど) はあまり重要ではなく、ほとんどがボイラープレート ドライバー コードで構成されています。

注入されたユーザー モード インプラントの取得

これで、ドライバーがシステム上で自分自身を隠すために何をするかがわかったので、Speakeasy によって作成されたメモリ ダンプを使用して、前述の挿入された DLL を取得できます。エミュレーション時に作成した zip ファイルを開くと、図 6 で参照されているメモリ タグを見つけることができます。図 13 に示すように、メモリ ブロックに有効な PE ヘッダーがあり、IDA Pro に正常に読み込まれていることがすぐに確認できます。

挿入されたユーザー モード DLL が Speakeasy のメモリ ダンプから復元されました
図 13: Speakeasy メモリ ダンプから復元された、挿入されたユーザー モード DLL

結論

このブログ投稿では、Speakeasy がカーネル モード バイナリからルートキット アクティビティを自動的に識別する方法について説明しました。 Speakeasy を使用すると、他の方法では動的に分析することが困難なカーネル バイナリを迅速にトリアージできます。詳細とコードを確認するには、 GitHub リポジトリにアクセスしてください。

参照: https://www.mandiant.com/resources/blog/emulation-of-kernel-mode-rootkits-with-speakeasy

Comments

Copied title and URL