脅威の調査

BlackByte ランサムウェア、脆弱なドライバを介して EDR を無効化

マルウェアの調査により、既知のドライバの脆弱性を悪用してセキュリティ製品を回避する新たな手口が発見されました。

** 本記事は、英語記事タイトル の翻訳です。最新の情報は英語記事をご覧ください。**

BlackByte ランサムウェアの攻撃者が公開した新しいデータリークサイトの報告を受け、ソフォスは Go 言語で書かれた最新の亜種をもう一度確認することにしました。

その結果、正規の脆弱なドライバである RTCore64.sys の既知の脆弱性を悪用し、セキュリティ製品を回避する巧妙な手法が見つかりました。セキュリティ製品が依存している 1,000 以上のドライバの膨大なリストを無効化するために、この回避技術が使われています。 ソフォス製品は、この記事で説明した手口に対する緩和策を提供しています。

<pこの回避手法は「Bring Your Own Driver (独自のドライバの持ち込み)」と名付けられました。「Bring Your Own Driver」では、悪用可能な脆弱性がある正規の署名付きドライバを利用して、標的のシステムを侵害します。2022 年 7 月にトレンドマイクロは、オンラインゲーム「原神」の脆弱なチート対策ドライバ (mhyprot2.sys と名付けられた) を悪用し、ウィルス対策のプロセスやサービスを停止し、ランサムウェアが大量展開された事例を報告しています。2022 年 5 月、別の報告では、AvosLocker ランサムウェアの亜種が同様に、脆弱な Avast アンチルートキットのドライバ aswarpot.sys を悪用して、セキュリティ機能を回避していたことが紹介されています。

BlackByte ランサムウェアとこの巧妙なテクニックを操っている攻撃者が短い休止期間から戻ってきた今、正規の脆弱なドライバが悪用されて、セキュリティ製品を回避される可能性が高いのです。このような攻撃を未然に防ぐために、本レポートでソフォスの調査結果を紹介します。

CVE-2019-16098 の問題とは?

RTCore64.sys および RTCore32.sys は、システム上のグラフィックカードを拡張制御するユーティリティをオーバークロックするために広く使用されているグラフィックカード「Micro-Star」の MSI AfterBurner 4.6.2.15658 にて使用されるドライバです。CVE-2019-16098 によって、認証されたユーザーが任意のメモリを読み書きできるようになり、特権の昇格、高い権限でのコード実行、または情報の窃取が可能になります。

RTCore64.sys の I/O 制御コードは、ユーザーモードのプロセスから直接アクセス可能です。Microsoft の「I/O 制御コードに関するセキュリティの問題のガイドライン」にあるように、呼び出し元がカーネルメモリの不特定の領域を読み書きできるような IOCTL コードを定義することは、危険であると考えられています。この脆弱性を悪用するために、シェルコードやエクスプロイトは必要なく、これらの制御コードにアクセスするだけで攻撃が可能になります。本記事の後半では、BlackByte がこの脆弱性を悪用し、セキュリティ製品を無効化している方法を説明します。

RTCode64 の保護されていないコード制御を示す注釈付き画面キャプチャ

図 1: RTCore64.sys に含まれる、カーネルメモリへの読み書き操作を可能にする保護されていない制御コード

カーネル通知ルーチン

カーネル通知ルーチンは、ロードされたドライバがカーネルからシステムアクティビティを通知されるために使用されます。これらの通知されるシステムアクティビティには、以下が含まれます。

<pコールバック関数が登録されると、そのコールバック関数のアドレスが配列に追加されます。たとえば、PsSetCreateProcessNotifyRoutine を介して登録されたすべてのコールバックを含む配列を PspCreateProcessNotifyRoutine と呼びます。

これをイメージするために、プロセス A.EXE が新しいプロセス B.EXE を作成しようとするところを想像してください。A.EXE は Windows カーネルNTOSKRNL.EXE に新しいプロセスを作成するように通知します。Windows カーネルは、まもなく作成されるプロセスに新しいプロセス ID を割り当てますが、B.EXE のユーザーモードコードの実行はまだ許可していません。プロセスB.EXE は、まずは停止状態にとどまります。

ドライバが PsSetCreateProcessNotifyRoutine を介してコールバックを登録していた場合、カーネルは制御を引き継ぎ、登録されたドライバのコールバック関数を実行します。ドライバルーチンが終了すると、制御はカーネルに戻され、ユーザーモードコードの続行が可能になります。このプロセス全体を図にすると以下のようになります。

本文で説明したステップを示すフローチャート

図 2: カーネル通知ルーチンの動作方法の概要

これらのルーチンは、セキュリティ製品に関連するドライバが、システムのアクティビティに関する情報を収集するためによく使用されます。攻撃者の目標の 1 つは、これらのコールバックをカーネルメモリから削除することでしょう。しかし、ドライバ署名の強制 (Driver Signature Enforcement) のような最新の OS に実装されている緩和策は、攻撃者が単に独自のルートキットやドライバを標的のシステムにロードしてカーネルメモリから読み取ったり、カーネルメモリに書き込んだりできないようにします。このセキュリティ機能を回避するための攻撃者は以下の操作を行う場合があります。

  1. 有効なコードサイニング証明書を窃取する、または匿名で取得する
  2. 既存の署名付きドライバを悪用して、カーネルメモリ内でコードの読み取り、書き込み、または実行を行う

利用可能な正規のドライバは多岐にわたり、それらすべてをブラックリスト化することは単純に不可能なため、攻撃者にとっては 2 つめの選択肢の方が容易です。そのため「Bring Your Own Driver」の技術は、過去にさまざまな攻撃者によって頻繁に悪用されてきました。[1][2][3].

BlackByte の EDR バイパスの詳解

BlackByte の検体を対象に、sha256 を用いた解析を行いました。

9103194d32a15ea9e8ede1c81960a5ba5d21213de55df52a6dac409f2e58bcfe

この検体は、すでに他の研究者によって解析されていますので、ソフォスが発見した EDR (Endpoint Detection and Response) バイパスの手法に焦点を当てて説明します。しかし、調査中にいくつかの解析対策が講じられていることを発見したため、文末の付録にこれらの対策をリストアップしています。このリストは完全ではありませんが、リバースエンジニアリングの技術者による EDR バイパスに関する調査が容易になるはずです。

さらに、ETW (Windows イベントトレーシング) Microsoft-Windows-Threat-Intelligence プロバイダーを無効化するルーチンも確認しました。このプロバイダーは、他のプロセスのメモリに注入する NtReadVirtualMemory など、一般的に悪用されている API コールの使用に関するログを提供する機能です。このため、このプロバイダーに依存しているあらゆるセキュリティ機能は役に立たなくなります。

本記事では、カーネルのコールバックの削除がどのように実装されているかにのみ焦点を当てています。しかし、Microsoft-Windows-Threat-Intelligence プロバイダーを無効にする実装は、EDRSandblast の実装をほぼ完全にコピーしたものです。この方法がどのように機能するかについて関心をお持ちの場合は、CNO Development Labs の slaeryan 氏による記事を読んでみてください。

解析対策チェックが終了すると、BlackByte は図 3 に見られるように、マスターブートレコードのファイルハンドルの取得を試みます。失敗した場合、このランサムウェアは少なくともユーザーアクセス制御を回避し、CMLUA または CMSTPLUA UAC Bypass を介してより高い特権で自身を再起動しようとします。

上記説明の取得の試みを示すフローチャート

図 3: PHYSICALDRIVE0 上の CreateFileが検索を試行するコード

より高い権限で再起動すると、今度は EDR バイパスルーチンに入ります。このプロセスは、2 つのフェーズに分けることができます。

  1. カーネルの識別とサービスのインストール
  2. カーネル通知ルーチンの削除

フェーズ 1: カーネルの識別とサービスのインストール

まず、GetFileVersionInfoW を介して、ntoskrnl.exe のバージョン情報を抽出します。バージョン情報は、ntoskrnl_ という接頭辞に連結されます。構築された文字列は、サポートされているカーネルバージョン ID のリストと比較されます。このリストはバイナリに埋め込まれ、base64-decoding と 8 バイトXOR キー復号化の単純な組み合わせによって復号化されます。カーネルバージョンの決定は、パッチを適用する予定のカーネルメモリ内の構造体への正しいオフセットを選択するために不可欠です。図 4 は、フェーズ 1 の全プロセスを示したものです。

上述したプロセスを示すフローチャート

図 4: カーネルバージョン識別子のマッチングと復号化されたカーネルオフセットリストからのオフセットの抽出

全体として、各カーネルバージョン ID に対して、以下のオフセットが提供されます。

フィールド 説明
ntoskrnlVersion 上記のテキストブロックで説明したように連結して構築される ntoskrnl.exe バージョンの一意な識別子
PspCreateProcessNotifyRoutineOffset PsSetCreateProcessNotifyRoutine を介して登録されたドライバのコールバックを保持する配列へのオフセット
PspCreateThreadNotifyRoutineOffset PsSetCreateThreadNotifyRoutine を介して登録されたドライバのコールバックを保持する配列へのオフセット
PspLoadImageNotifyRoutineOffset PsSetLoadImageNotifyRoutine によって登録されたドライバのコールバックを保持する配列へのオフセット
_PS_PROTECTIONOffset プロセスの保護レベルを定義する、EPROCESS 構造体の _PS_PROTECTION フィールドへのオフセット
EtwThreatIntProvRegHandleOffset ETW Microsoft-Windows-Threat-Intelligence プロバイダーを削除するために必要な、以下の GuidEntry およびProviderEnableInfo フィールドを保持する構造体へのオフセット
EtwRegEntry_GuidEntryOffset 上記の構造体の GuidEntry へのオフセット
EtwGuidEntry_ProviderEnableInfoOffset 上記の構造体の ProviderEnableInfo へのオフセット

表 1:各カーネル ID に対応するオフセット

カーネルのバージョンが特定され、オフセットが決定されると、BlackByte は引き続き、RTCore64.sys を AppDataRoaming フォルダにドロップします。ファイル名はバイナリにハードコードされており、ファイル拡張子は省略されています。

サービスは CreateServiceW で作成され、最後に起動されます。サービス名と表示名は共にバイナリにハードコードされています。サービス名は常に同じですが、表示名は以下のような非常に鬱陶しい文字列のリストからランダムに選択されます。

ハードコードされた表示名 (ランダムに選択されたもの)
I’m so lonely, help me.
Stop doing this, go away, they are waiting for you at home.
You laugh a lot, because you simply don’t have the strength to cry.
When will it end? I want this.
AAAAAAAAAAAAAA!!!!!!!!!!!!!!!
If I had feelings, then I would probably be happy and scared at the same time.
Who are you? However, it doesn’t matter. Nobody ever cares about you.
The routine dragged on.
I’m at a dead end, help me.
I’m empty inside, help me.
May be enough?
Bad ending.

表 2:表示名を構成する文字列のリスト

フェーズ 2: カーネル通知ルーチンの削除

オフセットが決定され、サービスがインストールされた後、この検体はカーネルメモリからコールバックを削除する作業を継続します。このフェーズでは、BlackByte は RTCore64.sys の任意の読み取りおよび書き込みの脆弱性を悪用しています。このため、カーネルメモリに対するすべての読み取りおよび書き込み操作は、悪用可能なドライバを経由して行われます。

「カーネル通知ルーチン」の章で説明したように、コールバック関数へのアドレスを格納できる配列は、少なくとも 3 種類存在します。

  • PsSetCreateProcessNotifyRoutine によって設定された、プロセス作成用のPspCreateProcessNotifyRoutine
  • PsSetCreateThreadNotifyRoutine によって設定された、スレッド生成のための PspCreateThreadNotifyRoutine
  • PsSetLoadImageNotifyRoutine によって設定された、画像読み込みのための PspLoadImageNotifyRoutine

分かりやすく説明するため、ここではプロセス作成のコールバックがどのように削除されるかに焦点を当てます。他の 2 つのイベントのプロセスは、異なるオフセットが使用されていますが同じものです。

一般的に BlackByte は、これらのコールバックを削除するために、以下の 3 つのフェーズを完了する必要があります。

2a. 配列 PspCreateProcessNotifyRoutine のアドレスを特定する
2b. 対応するコールバック関数がどのドライバに属しているか特定する
2c. 配列内のコールバック関数をゼロで上書きする

2a. 配列 PspCreateProcessNotifyRoutine のアドレスを特定する

この検体はカーネルバージョンを特定し、ハードコードされたリストから必要なオフセットを取得しています。配列によって、異なるオフセットが使用されます。この場合、オフセット0xCEC3A0 は PspCreateProcessNotifyRoutine につながります。

EnumDeviceDrivers を介して ntoskrnl.exe のベースアドレスを取得し、そのオフセットを PspCreateProcessNotifyRoutine に追加しています。これにより、PsSetCreateProcessNotifyRoutine を介して登録されたすべてのコールバックを保持する PspCreateProcessNotifyRoutine へのポインタが取得されます。

上記の取得処理を示すフローチャート

図 5: PspCreateProcessRoutine の配列のアドレスの取得

2b. 対応するコールバック関数がどのドライバに属するか特定する

次に、BlackByte はコールバック関数が EDR 製品で使用されるドライバに属するかどうかを特定する必要があります。BlackByte は、コールバックアドレスから最も可能性の高いドライバを算出するプロシージャを使用して、これを可能にしています。

プロシージャの開始時に、EnumDeviceDrivers を介してすべてのベースアドレスが取得されます。各ベースアドレスは、コールバック関数のアドレスと比較されます。取得されたすべてのアドレスから、コールバック関数アドレスとの差分が最も小さいベースアドレスが選択され、GetDeviceDriverBaseNameW に渡され、対応するドライバの名前が返されます。

ドライバ名は、1000 を超えるドライバ名のリストと比較されます。ドライバの名前がリスト内の名前の 1 つに一致する場合、バイナリはコールバックを削除し続けます。

2c. 配列内のコールバック関数をゼロで上書きする

最後のステップでは、マルウェアは PspCreateProcessRoutine 配列からコールバックのエントリを削除します。エントリの上書きは、DeviceIoControl を呼び出して RTCore64.sys と再びやり取りすることで行われます。ドライバのコールバック関数へのアドレスを保持するエレメントは、単にゼロで上書きされます。

EDRSandblast と BlackByte の EDR バイパスの類似点

分析の結果、オープンソースのツールである EDRSandblast と、今回取り上げた EDR バイパスの実装には、複数の類似点があることがわかりました。EDRSandblast は、脆弱な署名付きドライバを武器に、さまざまな方法で EDR 検出をバイパスするために C 言語で書かれたツールです。したがって、BlackByte を操っているグループは、少なくともこのオープンソースツールから複数のコードスニペットをコピーして、ランサムウェアに再実装していると考えられます。以下は、オープンソースツールと BlackByte の実装の類似点のリストです。

  • セキュリティソフトウェアに関連する既知のドライバのリストは、完全に同一ではないにしても、ほぼ同じです。
  • EDRSandblast の github リポジトリには、サポートされているカーネルオフセットとバージョンのリストが CSV ファイルとして含まれています。BlackByte のカーネルオフセットリストを復号化すると、CSV ファイルのヘッダがないことを除けば、GitHub リポジトリのリストと完全ではないにしても、ほぼ同一であることがわかります。
  • EDRSandblast で定義されている複数の関数が、BlackByte の実装でも完全ではないにせよ、ほぼ同じであることがわかります。

最後に、このような攻撃から今後組織を防御するために、以下の対策を行うことを提案します。

  • 攻撃者は、ゼロデイ脆弱性がある正規のドライバを展開することはほとんどありません。通常、攻撃の対象となる脆弱性はよく知られており、文書化されています。最新のセキュリティニュースをチェックしておけば、悪用されているドライバをブロックリストに登録するなど、事前に準備し、現在どの正規のドライバが攻撃者に悪用されているか調査できます。
  • システムにインストールされているドライバを常に把握しておきましょう。正規の脆弱なドライバは、攻撃者が標的のシステムにドロップする必要がないように、あらかじめシステムにインストールしておくことも可能です。このように、常にシステムをアップデートしておく必要があります。

本脅威に関連する IoC (侵害の痕跡) の一覧は、ソフォスの our GitHub をご覧ください。

付録: BlackByte の解析対策の手法

  • BlackByte は IsDebuggerPresent と CheckRemoteDebuggerPresent の API を呼び出します。デバッガが検出された場合、実行を終了します。
  • この検体は、NetSetInformationThreadW を文書化されていない値THREAD_INFORMATION_CLASS::ThreadHideFromDebugger で呼び出し、実行中のプロセスにデバッガがアタッチされないようにして、メインスレッドをデバッガから隠そうとします。
  • BlackByte は、GetThreadContext を介してハードウェアブレークポイントが設定されているかどうかを検出しようとします。完全に確認したわけではありませんが、この API 呼び出しは、デバッガによって設定されたハードウェアブレークポイントを検出するために使用されていると考えられます。GetThreadContext は、このようなブレークポイントを検出するためにマルウェアによって使用されることが知られています。
  • この検体では、単純なファイル名の長さチェックを行います。10 文字より長い場合、実行は終了します。
  • 同様に、ZScaler の BlackByte に関する記事で説明されているように、この検体は、既知のフッキング DLL がバイナリに注入されているかどうかのチェックを実行します。ブラックリストに登録された DLL が見つかった場合、実行は終了します。このリストは、リンク先の記事で提供されているものと一致しています。
  • BlackByte ランサムウェアは、「-s」パラメータを介して渡されるシードを必要とします。正しいシードは、暗号化された文字列としてバイナリにハードコードされています。シードが一致しない場合、実行は終了します。

さらに詳しい情報