2023年8月30日

コンテナに隠れよう: Windowsコンテナ分離フレームワークを使用した検知回避手法

はじめに

このブログは、2023年8月11日(金)にラスベガスで開催されたDEF CON 2023で発表した講演「 Contain Yourself: Staying Undetected Using the Windows Container Isolation Framework.」に基づいています。

コンテナの使用は、リソース効率の高いセキュアな環境のために不可欠な要素です。Windows Server 2016から、Microsoftは独自バージョンのソリューションとしてWindowsコンテナをリリースし、プロセスとHyper-Vの分離モードを提供しています。この講演では、Windowsコンテナの基本をカバーし、そのファイルシステム分離フレームワークを分解し、その主要なミニフィルタ・ドライバをリバースエンジニアリングし、複数のドメインでEDR製品をバイパスするために悪意のある攻撃者によってどのように利用でき、操作されるかを詳述しました。

Windowsコンテナが実行できる2つの「分離モード」:

  • プロセス分離モード(Windows Server コンテナとも呼ばれる): コンテナがホストカーネルと直接対話するユーザー モードの分離。各コンテナインスタンスは名前空間とリソース制御によってホストから分離される。Linuxコンテナをイメージ
  • Hyper-V分離モード(Hyper-Vコンテナとも呼ばれる):各コンテナに独自のHyper-V仮想マシンを提供するカーネルレベルの分離。仮想マシンが存在することで、各コンテナとコンテナ ホストとの間にハードウェア レベルの分離を提供

どちらの場合も、効率的なファイルシステムの分離が必要で、各コンテナはシステムファイルにアクセスでき、ホストに影響を与えない範囲で変更を書き込める必要があります。コンテナ起動ごとにメインボリュームをコピーするのは、ストレージ効率が悪く、現実的ではありません。

このテクノロジーに注目した理由:

  • コンテナや仮想化ソリューションはどこにでもあり、その内部構造は十分に文書化されていない
  • 悪質な攻撃者はコンテナから逃れる方法を探している。セキュリティ製品を回避するために意図的にコンテナに侵入するというアイデアは、まだ検討されていない
  • このフレームワークは前提条件を必要とせず、最新のWindowsイメージ(少なくとも悪用されている部分)にはデフォルトで入っている
  • 私たちはリバース エンジニアリングが大好きだから!
Windowsコンテナの仕組み

フレームワークの内部を掘り下げる前に、Windowsがどのようにしてコンテナ間の分離を実現しているのかを探ってみましょう。

ジョブ

ジョブ オブジェクトはWindows Server 2003の時代から存在しています。これらのオブジェクトは複数のプロセスをグループ化し、1つのユニットとして管理するように設計されています。これにより、システムはジョブに関連するすべてのプロセスの属性を制御することができます。例えば、CPU使用量、I/Oバンド幅、仮想メモリ使用量、ネットワークアクティビティを制限することができます。マルチプロセスのアプリケーションでは、子プロセスをより簡単に管理するためにこれらのオブジェクトを使用することがよくあります("Nested Job "として知られています)。

図1:ジョブを使用して子プロセスを管理するGoogle Drive(プロセスハッカー2より引用)
図1:ジョブを使用して子プロセスを管理するGoogle Drive(プロセスハッカー2より引用)

しかし、ジョブだけではコンテナに必要な分離を提供するには不十分です。

サイロ

サイロはジョブの拡張と考えることができます(一種の「スーパージョブ」です)。従来のジョブと同様に、これらのオブジェクトは追加の機能を持つプロセスのグループ化に使用されます。コンテナは 「サーバーサイロ」と呼ばれるタイプのサイロを使用します。これらは基本的なジョブ機能と、レジストリ、ネットワーキング、オブジェクト マネージャのような様々なシステムリソースのリダイレクトを提供します。

Windowsカーネルは、PsIsCurrentThreadInServerSiloやPsIsProcessInSiloのようなAPIを使用して、サイロに割り当てられたプロセスを検出します。

図2: IopUnloadDriver - カーネルはサーバー サイロ内のプロセスによるドライバーのアンロードを拒否
図2: IopUnloadDriver - カーネルはサーバー サイロ内のプロセスによるドライバーのアンロードを拒否

リパースポイントを使用したファイルシステムのリダイレクト

リパースポイントは、ファイルまたはディレクトリに指定できるMFT属性です。これらの属性はユーザー定義のデータを格納し、I/O 要求をインターセプトしてそれに応じて処理するファイルシステム ミニフィルタ ドライバによって解析されます。各リパース・ポイントには、格納されているデータを一意に識別するためのタグも含まれています。

これらの属性の良い例は、ジャンクションやシンボリックリンク - 別のディレクトリへのシンボリックリンクとして機能し、正しいリンク先へのパスを含むリパースポイントを背後に持つディレクトリ - で見ることができます。I/Oマネージャーは、これらのタグを含むファイル/ディレクトリへのI/Oリクエストを処理し、リダイレクトします。

図3:C:˶UsersAll UsersフォルダはシンボリックリンクでC:˶ProgramDataを指す
図3:C:˶UsersAll UsersフォルダはシンボリックリンクでC:˶ProgramDataを指す

これから説明するように、コンテナはこれらのポイントを使用して、使い捨てのボリュームとホストを分割します。

注:リパースポイントとショートカット(.lnkファイルなど)を混同しないでください。

コンテナのファイルシステム分離

コンテナがどのように初期化され、実行中にどのように動作するかについては、Alex Ilgayev氏とJames Forshaw氏による素晴らしい記事ですでに詳述されているため、このブログ記事では深入りはしません:
Playing in the “Window” Sandbox
Who Contains the Containers?

その代わりに、OSがどのようにファイルシステムを各コンテナからホストに分離し、システムファイルの重複を避けるかに焦点を当てます。

OSファイルの追加コピーを避けるため、各コンテナは動的に生成されたイメージを使用しており、リパースポイントを使用してオリジナルを指しています。

図4:動的に生成されるイメージ(Microsoft公式ドキュメントより)
図4:動的に生成されるイメージ(Microsoft公式ドキュメントより)

その結果、実際のデータは格納されていませんが、システム上の別のボリュームを指す「ゴーストファイル」を含むイメージが生成されます。このリダイレクト・メカニズムを使ってファイル・システム操作を難読化し、セキュリティ製品を混乱させたらどうでしょうか?

これはコンテナ内部からエスケープするのではなく、ホスト上で実行中に意図的にこの機能を使用します。

Figure 5: Mounted
Figure 5: Mounted "ghost image" courtesy of Alex Ilgayev (https://research.checkpoint.com/2021/playing-in-the-windows-sandbox/)

ミニフィルターの背景

ミニフィルタドライバは、開発者のI/Oフィルタリング処理をより簡単にするために設計されました。レガシー・フィルター・ドライバーをゼロから実装するのは難しいので、マイクロソフトはフィルター・マネージャーという形で解決策を提供しました。このレガシー・フィルターは、他の「ミニ」フィルター・ドライバを管理し、デバイス・スタックへの挿入、無関係なリクエストの無視、マルチプラットフォームのサポートなど、重い仕事をすべて引き受けてくれます。また、これらのドライバが使用する一般的な操作(Flt API)を実装するミニフィルタ専用APIも公開されています。

各ミニフィルターは、マネージャーによって1つ以上のボリュームにアタッチすることができ、「ミニフィルターインスタンス」と呼ばれるものを作成します。レガシーフィルタと同様に、ミニフィルタインスタンスは、Create、Read、Writeといった多数のI/O関数のPreおよびPostオペレーションをインターセプトすることができきます。

フィルター マネージャーが実装しているもう一つの重要なコンセプトは、ミニフィルターの高度システムです。各ミニフィルターは、マネージャーへの登録時にフィルターの高度(20000から429999の間の値)を指定する必要があります。

この範囲はグループに分割され、各グループは特定のタイプのミニフィルターに関連付けられています。例えば、320000 - 329999はセキュリティベンダードライバの範囲で、140000 - 149999は暗号化関連ドライバの範囲です。

フィルターマネージャーは、その高度に従ってミニフィルター操作コールバックを呼び出します。高度の高いドライバは、その下のドライバより前の操作と後の操作を処理します。

Figure 6: Mini-filter architecture, courtesy of James Forshaw
図6:James Forshaw氏提供のミニフィルター アーキテクチャ (https://googleprojectzero.blogspot.com/2021/01/hunting-for-bugs-in-windows-mini-filter.html)

Wcifs.sys

注:ここから先に提供されるすべての情報はMicrosoftによって文書化されておらず、ドライバーをリバースエンジニアリングすることによって収集したものです。

Windows Container Isolation FS (wcifs)ミニフィルター ドライバーは、Windowsコンテナーとそのホスト間のファイルシステム分離を担当します。これはゴースト ファイルのリダイレクトを処理するドライバで、添付されているリパース ポイントを解析することでこれを行います。

調査中、このドライバが、サーバーを含むWindows 10以降のすべてのWindows OSでデフォルト ロードされていることに驚きました。これは、Windowsの機能メニューで「コンテナ」オプションがオフになっていても同じです。

図7:新しくインストールされたWindows 10システムにロードされたwcifs.sys
図7:新しくインストールされたWindows 10システムにロードされたwcifs.sys

このドライバーに関連する主なリパース タグは、IO_REPARSE_TAG_WCI_1とIO_REPARSE_TAG_WCI_LINK_1で、Windowsのドキュメントによると「Windowsコンテナ分離フィルターによって使用されます。サーバー側の解釈用のみであり、ワイヤーを介してはの用途ではありません」とあります。

これら2つのタグのリパース ポイントデータ構造は以下のようになります:


struct WcifsReparseDataBuffer
{
/*0*/ ULONG Version;
/*4*/ ULONG Reserved;
/*8*/ GUID Guid; // hardcoded value (8264f677-40b0-4ca5-bf9a-944ac2da8087)
/*24*/ USHORT PathStringLength;
/*26*/ wchar_t PathStringBuffer[100];
};

struct ReparseDataBuffer
{
/*0*/ ULONG ReparseTag;
/*4*/ USHORT ReparseDataLength;
/*6*/ USHORT UnparsedNameLength;
/*8*/ WcifsReparseDataBuffer InternalBuffer;
};

ドライバがこれらの点をどのように処理するか見てみましょう。

注:このドライバは、複数のコンポーネントを含む広範なフレームワークの中で、小さな役割を担っています。伝統的なコンテナ操作のもとでこれらのタグがどのように動作するかはリサーチせず、これらの特定のケースに対するこのドライバーの生の実装のみをリサーチします。

ミニ・フィルターとリパース・ポイント

一般的なリパースポイントの解析フローは以下のとおりです:

  1. I/OマネージャーがIRP_MJ_CREATEリクエスト・パケットを作成し、対応するファイルシステムのデバイススタックを下ってくる
  2. ファイルはファイルシステムから読み取られ、リクエストは逆方向にスタックを上がってくる
  3. ファイルシステム ドライバは、リパースポイントを持つファイルがオープンされたことを認識し、リクエストのステータスをSTATUS_REPARSEに変更し、さらなる処理のためにデバイススタック上の他のドライバに委ねる
  4. リクエストは最終的にフィルターマネージャードライバーに到達する。フィルター マネージャードライバーは、登録されているミニフィルターのPOST_CREATE コールバックを、その高度に従って、下から上に呼び出す
  5. そのPOST_CREATEコールバックで、リパースタグを担当するミニフィルターは、MFT属性からリパースデータ自体を読み取るFSCTL_GET_REPARSE_POINT制御コードでFltFsControlFileを呼び出す
    1. リパースデータ ヘッダーに位置するリパースタグが関連付けられていない場合、それは要求を無視し、その上のドライバに任せる
    2. I関連付けられている場合、ミニフィルターは通常、IoReplaceFileObjectName と FltSetCallbackDataDirty を使ってリクエストのファイルオブジェクトを置き換える。これにより、I/Oマネージャはファイルオブジェクトの名前を「リパース」し、 リクエストを正しい値で受け渡す

wcifs.sys も同じです。POST_CREATEで、ドライバはSTATUS_REPARSEで返されたリクエストを解析して処理します。

最初のステップは、ミニフィルターをメインボリュームにアタッチし、そのタグの一つでファイルをオープンしようと試み、POST_CREATEコールバックでどのように解析されるかを見ることです。残念ながら、このドライバをデバッグしているとき、ドライバがボリュームに正しくアタッチされていても、このコールバックを呼び出すことができませんでした。

POST_CREATE関数が実行されるには、ミニフィルタがPRE_CREATE関数でFLT_PREOP_SUCCESS_WITH_CALLBACKまたはFLT_PREOP_SYNCHRONIZEのいずれかを返す必要があります。いくつかの条件が満たされなかったようで、フィルタは最初にそれを検査した後、リクエストの処理を続行しないことにしたようです。

PRE関数を素早く調べた結果、それらの条件を含むWcUnionsExistsForInstanceという関数を発見しました:

図8:wcifsのPRE_CREATE_CALLBACK関数
図8:wcifsのPRE_CREATE_CALLBACK関数

この関数が失敗すると、FLT_PREOP_SUCCESS_NO_CALLBACKコールバックステータスが返され、フィルタマネージャはこのドライバのPOST_CREATEコールバックを無視します。

内部に入ると、満たす必要のある2つの要件が見えます。この関数は、現在のスレッドが「ホスト・サイロ」(ホストOSに相当)に関連しているかどうかをチェックします。言い換えれば、ドライバは現在のスレッドがサーバー・サイロで実行されているかどうかをチェックし、そうでなければ終了するということです。

単にサーバー サイロ内で実行されているだけでは不十分で、2番目の要件は、このサイロがドライバー内部のコレクションに登録されたユニオン コンテキストを持っているかどうかです(チェックが現在のスレッド自体ではなく、ファイル オブジェクトに対して実行されていることに注意してください。この動作については、この記事で説明しています):

Figure 9: WcUnionsExistForInstance
Figure 9: WcUnionsExistForInstance

コンテキスト管理はフィルターマネージャーが提供するもう一つの機能です。ミニフィルターは、ユニオンコンテキストと呼ばれるカスタム定義のデータを作成し、Flt APIを使用してファイル、インスタンス、サイロなどのオブジェクトにリンクすることができます。

要約すると、このドライバでCreateFile要求を処理するには、以下を実行する必要があります:

  1. サイロを作成し、そこにプロセスを挿入
  2. サイロがコンテナを表していることをドライバに通知して、ユニオン・コンテキストを作成し、それに従ってコンテナを参照
コンテナの登録

最初の要件は非常に簡単です。CreateJobObjectWを使用してジョブを作成し、JobObjectCreateSiloクラスを使用してSetInformationJobObjectを使用してサイロに変換し、AssignProcessToJobObjectを使用して現在のプロセスを割り当てればよいのです。

2つ目は少しトリッキーです。ミニフィルターとの通信はFltSendMessage関数を介して行われ、ユーザーモードクライアントからドライバーにカスタムデータ構造を送信します。便利なことに、wcifsドライバはクライアントにいくつかの関数を提供しており、その1つがSetUnionと呼ばれるオプションです。(MessageCode = 0)というオプションがあり、これはコンテナを登録します。

ドライバが期待する構造は次のようなものです:

struct WcifsPortMessageSetUnion
{
/*0*/ DWORD MessageVersionOrCode;
/*4*/ DWORD MessageSize;
/*8*/ DWORD NumberOfUnions;
/*12*/ wchar_t InstanceName[50];
/*112*/ DWORD InstanceNameLength;
/*116*/ DWORD ReparseTag;
/*120*/ DWORD ReparseTagLink;
/*124*/ DWORD Unknown;
/*128*/ HANDLE SiloHandle;
/*136*/ char UnionData[];
};

struct WcifsPortMessage
{
/*0*/ DWORD MessageCode;
/*4*/ DWORD MessageSize;
// While MessageCode=0, MessageData should be WcifsPortMessageSetUnion
/*8*/ char MessageData;
};

UnionData[]フィールドには、コンテナが扱うソースボリュームとデスティネーションボリュームに関する情報が含まれます:

//The UnionData[] field holds one VolumeUnion & ContainerRootId per volume

struct ContainerRootId
{
/*0*/ USHORT Size;
/*2*/ USHORT Length;
/*4*/ USHORT MaximumLength;
/*6*/ wchar_t Buffer[23];
};

struct VolumeUnion
{
/*0*/ GUID Guid; // hardcoded value (8264f677-40b0-4ca5-bf9a-944ac2da8087)
/*16*/ BOOL IsSourceVolume;
/*20*/ DWORD OffsetOfVolumeName; // This points to a ContainerRootId structure
/*24*/ WORD SizeOfVolumeName;
/*26*/ WORD GuidFlags;
};

正しく構築されると、サイロが登録され、コンテナに関するデータを格納するサイロコンテキストが作成され、PRE_CREATEでのチェックが通過してPOST_CREATEが呼び出されます。

有効な構造体の例は次のようになります:

struct WcifsPortMessage
{
/*0*/ DWORD MsgCode = SetUnion; // SetUnion = 0
/*4*/ DWORD MsgSize = sizeof(WcifsPortMessage);
/*8*/ WcifsPortMessageSetUnion Message;
};

struct WcifsPortMessageSetUnion
{
/*0*/ DWORD MessageVersionOrCode = 1;
/*4*/ DWORD MessageSize = sizeof(WcifsPortMessageSetUnion);
/*8*/ DWORD NumberOfUnions = 2;
/*12*/ wchar_t InstanceName[50] = L"wcifs Instance";
/*112*/ DWORD InstanceNameLength;
/*116*/ DWORD ReparseTag = IO_REPARSE_TAG_WCI_1;
/*120*/ DWORD ReparseTagLink = IO_REPARSE_TAG_WCI_LINK_1;
/*124*/ DWORD Unknown;
/*128*/ HANDLE SiloHandle;
/*136*/ VolumeUnion SourceVolumeUnion;
/*164*/ VolumeUnion TargetVolumeUnion;
/*192*/ ContainerRootId SourceVolumeContainerRootId;
/*244*/ ContainerRootId TargetVolumeContainerRootId;
};

次に、ドライバがどのようにタグを解析するかを見てみましょう。

IO_REPARSE_TAG_WCI_LINK_1

前述したように、このドライバが扱うタグのひとつに IO_REPARSE_TAG_WCI_LINK_1 があります。その名前が示すように、このタグは2つのファイル間の通常のリンクとして機能します。ドライバは、WcifsReparseDataBuffer.PathStringBuffer でパスを読み取り、IoReplaceFileObjectName 関数を使用してコンテナが指示するボリュームでそのパスにリダイレクトします。例えば、コンテナが \DeviceHarddiskVolume5 から \DeviceHarddiskVolume3 にリダイレクトし、 \DeviceHarddiskVolume5 sourcefile.txt が IO_REPARSE_TAG_WCI_LINK_1 タグのリパースポイントを保持し、\Device\HarddiskVolume3\dest\file.txtに設定し、これが返されたハンドルが参照するファイルになります。

IO_REPARSE_TAG_WCI_1

つ目のタグは、もっと興味深いです。IO_REPARSE_TAG_WCI_1タグに遭遇すると、ドライバはファイル・オブジェクトのコンテキストにリパース データを保存し、さらにリクエストを処理するワークアイテムを起動します。ドライバのシンボルによると、この作業項目はファイルとディレクトリの 「展開 」を担当します。

「展開 」は、このドライバによる 「コピー・オン・オープンプロテクション」の定義である。コンテナ内のプロセスがこのタグを持つファイルにアクセスすると、ドライバは自動的にそれをソースボリューム(つまりコンテナの「ゴーストイメージ」)にコピーするので、(ファイルのデータを上書きすることによって)オリジナルではなくファイルのコピーを編集することになります。

このコピーは、FltReadFile と FltWriteFile を介して行われます:

図 10:WcExpansionWorker から呼び出された WcCopyStreamData
図 10:WcExpansionWorker から呼び出された WcCopyStreamData

先ほどの例で説明すると、ⅳDeviceHarddiskVolume5sourcefile.txt のタグを IO_REPARSE_TAG_WCI_1 に交換して開こうとすると、ドライバによってⅳDeviceHarddiskVolume3destfile.txt の内容がコピーされ、コピーされたファイルのハンドルが返されます。

このタグに関するもう 1 つの注意点は、宛先ファイルが見つからずに展開に失敗すると、ドライバは FltPerformSynchronousIo を使用して新しい I/O 操作を開始し、ソースファイルを削除することです:

図11:STATUS_OBJECT_NAME_NOT_FOUNDが返された場合、wcifsはソースファイルを削除
図11:STATUS_OBJECT_NAME_NOT_FOUNDが返された場合、wcifsはソースファイルを削除

ファイルのコピー

FltSendMessage 関数を使ってドライバがクライアントに提供するもうひとつの機能は、ファイルのコピー&ペーストです。

次のような構造体を作り、MessageCode=4でドライバに送信すると、ドライバはコピー元のファイルを読み、コピー先に書き込みます(ここでもFltReadFileとFltWriteFileを使用します):

struct WcifsPortMessageCopyFileHandler
{
/*0*/ DWORD MessageVersionOrCode;
/*4*/ DWORD MessageSize;
/*8*/ wchar_t InstanceName[50];
/*108*/ DWORD InstanceNameLength;
/*112*/ DWORD ReparseTag;
/*116*/ DWORD OffsetToSourceContainerRootId;
/*120*/ DWORD SizeOfSourceContainerRootId;
/*124*/ DWORD OffsetToTargetContainerRootId;
/*128*/ DWORD SizeOfTargetContainerRootId;
/*132*/ DWORD OffsetToSourceFileRelativePath;
/*136*/ DWORD SizeOfSourceFileRelativePath;
/*140*/ DWORD OffsetToTargetFileRelativePath;
/*144*/ DWORD SizeOfTargetFileRelativePath;
/*148*/ char UnionData[]; // 2*ContainerRootId + source & target relative paths
};

注意: IO_REPARSE_TAG_WCI_1リパース・タグを使ったコピー操作では、ターゲット ファイルが存在する必要がありますが、ここでは、ターゲット ファイルはファイル システム上には存在してはなりません(そうでなければ、操作はSTATUS_NAME_COLLISIONで失敗します)。

フレームワークの利用

さて、これで捏造されたコンテナ内で実行されるプロセスと、変わった方法でI/Oリクエストを処理するミニフィルターができました。次は何でしょう?

前のセクションでは、以下のカーネル プリミティブを使ってファイルを作成、読み取り、書き込み、削除するために、wcifsドライバをどのように悪用できるかを見てきました: FltCreateFile、FltReadFile、FltWriteFile、FltPerformSynchronousIoです。これらの関数がバックエンドで動作する方法によって、カーネル自体からこれらの操作を実行する隠れた利点があることが判明しました。

これら4つの関数のMSDNドキュメントには、次のような記述があります:

リクエストは、開始インスタンスより下にアタッチされたミニフィルタ ドライバ インスタンスとファイルシステムに送信されます。指定されたインスタンスとその上にアタッチされたインスタンスは、リクエストを受け取りません。

私たちのドライバは、セキュリティ・ベンダーのドライバ(320000 - 329999)よりも低い高度(189900、手動で変更するとさらに低くできる可能性がある)にあります。

セキュリティベンダーのミニ フィルターがどのように使われ、何が迂回できるかを見てみましょう:

ランサムウェア/ワイパー検出アルゴリズムのバイパス

ファイルシステムの書き込み保護は、EDRが提供しなければならない必須の機能です。 ランサムウェアは組織全体を麻痺させ、被害者に何百万ドルもの損害を与える可能性があり、ワイパーマルウェアは(ロシアとウクライナの紛争に見られるように)戦争時に重要なインフラを無効にする効果的な方法として証明されています。

これらの脅威に対抗するため、セキュリティベンダーは、システムのI/Oアクティビティを監視する独自のミニフィルター ドライバーを使用する傾向があります。このログソースに基づくアルゴリズムは、ファイルシステムベースのマルウェアを検出するために特定のパターンを探し、取り返しのつかない被害が出る前に防ぎます。例えば、多くの既存ファイルを開き、それらに書き込むプロセスは、書き込まれたデータによってランサムウェア/ワイパーに分類されます。

ここで我々が作成したドライバの出番です。ウイルス対策ドライバに検出されることなく、IO_REPARSE_TAG_WCI_1リパースタグを使ってファイルを上書きすることができるため、ウイルス対策ドライバの検出アルゴリズムは全体像を受け取らず、トリガされません。

ドライバを使った簡単なワイプ・アルゴリズムの例は、以下のようになります:

  1. ターゲットファイルとなる空のファイルを作成。そこにゼロ/ランダムデータのバッファを書き込む
  2. システム上のすべてのファイルをトラバースし、ターゲットファイルを指すIO_REPARSE_TAG_WCI_1リパース・ポイントを設定
  3. サイロを作成して現在のプロセスを割り当て、ソースボリュームとターゲットボリュームの両方がメインとなるwcifsにコンテナとして登録 (\Device\HarddiskVolume3).
  4. システム上のすべてのファイルを再度トラバースし、CreateFileを使用して各ファイルを開く。ファイルは、wcifsドライバによってターゲット・ファイルのデータで上書きされる

ランサムウェアのアルゴリズムも同様です:

  1. システム上の各ファイルをトラバースし、それぞれを開く:
    1. システム上の各ファイルを走査し、それぞれについて:内容を読み取り、メモリ内で暗号化
    2. ターゲットファイルを作成し、そこに暗号化されたデータを書き込む - データは新しいファイルに書き込まれ、既存のコンテンツを上書きしないので、セキュリティ ミニフィルターによって無視される
    3. ソースファイルに、ターゲットファイルを指す IO_REPARSE_TAG_WCI_1 リパースポイントを設定
  2. サイロを作成して現在のプロセスを割り当て、ソースボリュームとターゲットボリュームの両方がメインとなるwcifsにコンテナとして登録 (\Device\HarddiskVolume3).
  3. システム上のすべてのファイルを再度トラバースし、CreateFileを使用して各ファイルを開く。ファイルはwcifsドライバによってターゲットファイルデータで上書きされる
DLP バイパス - 読み取り専用デバイスとディレクトリへの書き込み

セキュリティベンダー製品のもう1つの機能として、特定のディレクトリ/ボリュームへの書き込み操作をブロックするものがあります。例えば、組織では、データの流出を避けるためにリムーバブル デバイスの読み取り専用ポリシーを決定したり、機密データを含むフォルダへのファイル書き込みをブロックしたりすることがよくあります。

この書き込み保護は、(ご想像の通り)ミニフィルタ ドライバによって実装されています。wcifsのコピー&ペースト機能を使えば、この保護もバイパスできます。

ミニフィルターをバイパスする以外にも、I/O操作を実行する際に従来のルートを通らないことによる副次効果があります:

ETWベースの相関バイパス

ETW(Event Tracing for Windows)は、Windowsオペレーティングシステムに組み込まれた強力で効率的なロギングメカニズムです。Windowsカーネルは、ファイルシステムに関連するものを含む、広範囲のシステム操作をキャプチャする重要なログプロバイダとして機能します。セキュリティベンダーは、潜在的な脅威を分析し特定するためにこれらのイベントを活用し、多くの場合、相互参照によって攻撃フローを作成します。

IO_REPARSE_TAG_WCI_1 タグの上書き処理に戻ると、読み取りと書き込みの操作はカーネルのワークアイテム内で発生します。カーネルスレッドであるワークアイテムから実行すると、ETWログはこれらの操作を実際の担当プロセスではなく、システムプロセス(PID 4)に帰属させます。これは、Microsoft-Windows-Kernel-File プロバイダーから 15 番(Read)と 16 番(Write)のイベントを消費するベンダーの誤報につながり、これらのイベントに基づく脅威探索の相関をバイパスします。

ETW ベースの Windows 組み込み機能の例として、SACL があります。Windowsは、システムアクセスコントロールリスト(SACL)として知られるファイルシステムオブジェクトの監査ポリシーを確立する機能を提供します。これは、指定されたオブジェクト上で実行されたすべてのI/O操作の広範なロギングを可能にします。

ETWベースのWindowsツールは、システムから発信されたログは意図的に無視するように設計されています。このアプローチは、不必要なオーバーヘッドを避けるために、システムを監視するユーザーには通常無関係なそのようなログが含まれないことを保証します。

この結果として、我々のI/Oリクエストはログからも完全に姿を消します。

https://www.deepinstinct.com/ransomware-protection-solution
https://www.deepinstinct.com/ransomware-protection-solution

CreateProcessNotifyRoutine バイパス

Windowsカーネルは、プロセスの作成/破棄の通知を興味のあるドライバーに送る機能を提供しています。これにより、ドライバーはシステム内のプロセスを追跡することができ、セキュリティ製品のドライバーの場合は、作成されたプロセスをスキャンし、脅威を与えないことを確認することができます。

コールバック ルーチンのプロトタイプは以下のとおりです:

void
PcreateProcessNotifyRoutineEx(
_Inout_ PEPROCESS Process,
_In_ HANDLE ProcessId,
_Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo
);

CreateInfoパラメータには、作成されるプロセスのイメージファイル名とコマンドラインが含まれます。

ドライバに話を戻すと、カーネルはプロセスを作成するために3種類のシステムコールを提供しています: NtCreateProcess、NtCreateProcess、およびNtCreateUserProcessです。NtCreateProcess、NtCreateProcess、NtCreateUserProcessです。3つともntdll.dllのエクスポートで、ユーザーモードのプログラムから直接呼び出すことができます。最初の2つはほとんど同じで、与えられたセクションハンドルを使用してプロセスを作成できますが、3番目のNtCreateUserProcessは少し異なります:

NTSTATUS
NTAPI
NtCreateUserProcess(
_Out_ PHANDLE ProcessHandle,
_Out_ PHANDLE ThreadHandle,
_In_ ACCESS_MASK ProcessDesiredAccess,
_In_ ACCESS_MASK ThreadDesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ProcessObjectAttributes,
_In_opt_ POBJECT_ATTRIBUTES ThreadObjectAttributes,
_In_ ULONG ProcessFlags,
_In_ ULONG ThreadFlags,
_In_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters,
_Inout_ PPS_CREATE_INFO CreateInfo,
_In_ PPS_ATTRIBUTE_LIST AttributeList
);

この関数では、ProcessParameter 引数に新しいプロセスのイメージファイルパスを指定するオプションが提供され、オープンセクションハンドルの代わりにカーネル自体からオープンされます。

IO_REPARSE_TAG_WCI_LINK_1リパースポイントを含むファイルへのパスを与えることで、すべてのプロセス生成コールバックに送られるCreateInfoパラメータにそのパスとコマンドラインが保持され、実際にオープンされるファイルはリパースポイントがリダイレクトするファイルになります。

イベントの流れは以下のようになります:

  1. IO_REPARSE_TAG_WCI_LINK_1リパースポイントを、悪意のあるファイルを指す良性のファイルに設定
  2. サイロを作成し、現在のプロセスを割り当てて、ソースボリュームとターゲットボリュームの両方がメイン・ボリュームであるwcifsにコンテナとして登録 (\Device\HarddiskVolume3).
  3. NtCreateUserProcess を使用して、良性ファイルイメージのファイルパスで新しいプロセスを作成
  4. すべての登録済みドライバのプロセス作成通知コールバックがトリガーされ、良性ファイルのイメージパスとコマンドラインが含まれる
  5. カーネルは良性ファイルを開き、wcifsは解析されたリクエストをインターセプトして悪性ファイルにリダイレクト
  6. プロセスは悪意のあるファイルイメージを使用して作成
前提条件と制限
  • wcifsドライバとの通信には管理者権限が必要
  • WRITE プリミティブのないファイルにリパースポイントを設定することはできない
  • サイロ内でリパースポイントを設定することはできない
  • CreateFile + IO_REPARSE_TAG_WCI_1の呼び出しが成功するには、プロセストークンがSeManageVolumePrivilege特権を保持している必要がある
  • wcifsを使用してファイルをコピーする場合、ターゲットファイルがファイルシステム上には存在しないこと(つまり、この方法を使用してファイルを上書きすることはできません)
  • ドライバは、動作するボリューム(ソースまたはターゲット)にアタッチされている必要がある
対処・軽減策

wcifsの悪意ある使用を検出するために、セキュリティベンダーが取るべき方法はいくつかあります:

  • DeviceIoControl + FSCTL_SET_REPARSE_POINT + IO_REPARSE_TAG_WCI_1 / への呼び出しを検出し、PRE_WRITE コールバック内の IO_REPARSE_TAG_WCI_1 タグをチェックします。ファイルが変更されていなくても、PRE_CLEANUP 関数でそのタグを持つファイルをスキャン
  • wcifsの通信ポートが、有効なシステムプロセスではないプロセスによってオープンされていないかを確認
  • ソースボリュームとデスティネーションボリュームが等しいコンテナがオープンされていないかを確認
  • wcifsがシステムではなくユーザープロセスによってアタッチされていないか、またはコンテナ機能が無効化されているときにアタッチされていないかを確認
マイクロソフトの対応

マイクロソフトはこの調査について報告を受け、以下のように回答しました:

"これはマルウェア検出の回避テクニックであり、セキュリティアップデートで対応するセキュリティ脆弱性ではないと判断されました。"

GitHub Repo

POCツールのソースコードは、こちらの this GitHub repoからアクセスできます。

ソース