ブログ(脅威調査)

Speakeasyカーネル・モード・ルート・キット・エミュレーションを使ったWinntiマルウェアの解析

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)のため、Windowsの最新の64ビット版では機能しません。

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


図1:悪意のあるドライバのエミュレーションに使用するコマンド・ライン

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


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

ドライバ・オブジェクトの非表示

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


図3:tcprelay.sysを表すメモリ書き込みイベント

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

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


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

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

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


図5:tcprelay.sys ルートキットがユーザー・モードのエクスポートを解決するために使用するログ

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


図6:services.exeへのユーザー・モード・インプラントのコピー中にキャプチャされたメモリ書き込みイベント

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

APCがキューイングされユーザー・モードで実行するために、マルウェアは「アラート可能」の状態でスレッドを見つけなければなりません。スレッドは、自身の実行クォンタムをカーネル・スレッド・スケジューラに放棄し、APCをディスパッチできることをカーネルに通知すると、アラート可能であると言われています。マルウェアは、services.exeプロセス内のスレッドを検索し、アラート可能なスレッドを検出すると、DLLにメモリを割り当ててからAPCをキューに入れて実行できるようにします。
Speakeasyは、このプロセスに関連するすべてのカーネル構造、特にWindowsシステム上のすべてのスレッドに割り当てられているエグゼクティブ・スレッド・オブジェクト (ETHREAD)構造をエミュレートします。マルウェアは、この不透明な構造を介して、スレッドのアラート可能フラグがいつ設定されているか(つまりAPCが有効な候補) を識別しようとする場合があります。

図7は、Winntiマルウェアがアラート可能であることを確認するためにservices.exeプロセスでETHREAD構造を手動で解析したときに記録されたメモリ読み取りイベントを示しています。この書き込みの時点で、エミュレータ内のすべてのスレッドは、デフォルトでアラート可能なものとして存在します。


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

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


図8 APC経由でユーザー・モードにインジェクトするためのtcprelay.sysルートキットを使用した:APIログ

ネットワーク・フック

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

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


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

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


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

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

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


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

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


図12:IDA Proに表示されるSSDTパッチ適用機能を隠すファイル

これらのフックを設定すると、システム・スレッドは終了します。ドライバ内の他のエントリ・ポイント(IRPハンドラーやDriverUnloadルーチンなど)はあまり興味深いものではなく、ほとんどがボイラープレート・ドライバ・コードを含んでいます。

インジェクト・ユーザー・モード・インプラントの取得

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


図13:Speakeasy memory dumpからインジェクト・ユーザー・モードDLLが回復

結論

このブログの記事では、Speakeasyがカーネル・モード・バイナリからルートキット活動を自動的に識別するのにどのように効果的かを議論しました。Speakeasyは、本来ならば動的に分析するのが難しいかもしれないカーネル・バイナリを迅速にトリアージするために使用できます。詳細やコードを確認するには、GitHubリポジトリを参照してください。

 

本ブログは、米FireEyeが公開した January 20, 2021「Emulation of Kernel Mode Rootkits With Speakeasy」(英語)の日本語抄訳版です。

日本語版:Reviewed by Toru Tanimura