【実務・中級編】 WindowsカーネルのDirect Kernel Object Manipulation (DKOM)攻撃 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

おい、聞いてくれ。昨日の夜、あるクライアントのEDR(Endpoint Detection and Response)から「深刻な異常」のアラートが上がった。CPU使用率が異常に跳ね上がり、特定のサービスが応答しない。現場のエンジニアがタスクマネージャーや tasklist コマンドでプロセスを確認したが……奇妙なことに、そこには「何もいない」。まるで幽霊だ。

だが、ネットワークのトラフィックをキャプチャすると、外部のC2(コマンド&コントロール)サーバーへ怪しい通信がビュンビュン飛んでいる。プロセス一覧にいないのに、裏でしっかりと動いている。これこそが、今回深掘りする DKOM(Direct Kernel Object Manipulation)攻撃 の実態だ。

表面上のOSのAPIをいくら叩いても見えない。なぜなら、攻撃者はWindowsの心臓部であるカーネルメモリを直接書き換え、プロセス管理のリストから自分自身を「切り離して」隠蔽しているからだ。

今日は、この厄介なDKOMの仕組みと、現場のフォレンジック調査でどうやってその尻尾を掴むのか、そしてシステム運用者や開発者がこの脅威に対してどう向き合うべきかを、俺の経験を交えて叩き込んでやる。

—

1. 幽霊プロセスを生み出すDKOMのメカニズム

Windowsは、プロセスやスレッド、ドライバなどのシステムリソースを管理するために、カーネル空間内に「EPROCESS」と呼ばれる巨大なデータ構造(オブジェクト)を保持している。

通常のプロセス一覧表示ツール(タスクマネージャーやProcess Explorerなど)は、このEPROCESS構造体の中にある ActiveProcessLinks という双方向連結リスト(Linked List)を先頭から順に辿って(トラバーサルして)画面にリストアップしている。

なぜプロセスが消えるのか?

DKOM攻撃を行うマルウェア(あるいはカーネルモードで動作するRootkit)は、自分自身のドライバをロードし、特権モード(Ring 0)で直接メモリを書き換える。

具体的に何をするかというと、隠蔽したいプロセスの ActiveProcessLinks ポインタを書き換え、「自分の前のプロセス」と「自分の後ろのプロセス」を直接繋ぎ直してしまうのだ。

[正常な状態]
プロセスA <---> プロセスB (隠したい奴) <---> プロセスC

[DKOM攻撃後]
プロセスA <----------------------------> プロセスC
        (プロセスBは孤立しているが、メモリ上には存在し続ける)

この操作が行われると、WindowsのAPI(EnumProcesses など)はこのリストを辿るため、プロセスBを完全にスキップしてしまう。結果として、タスクマネージャーからはプロセスが消えたように見える。しかし、OSのスケジューラ自体は、まだプロセスBのEPROCESS構造体やページテーブルを指しているため、CPU時間を割り当てて実行し続けるというわけだ。恐ろしい話だろ?

—

2. メモリダンプにおける不整合の特定(フォレンジックの現場から)

では、OSのAPIが騙されるなら、私たちDFIRアナリストはどうやってこの幽霊を見つけ出すのか?

答えは簡単だ。「カーネルの別の場所と突き合わせる(クロスビュー解析)」ことだ。
OSは、プロセスを管理するのに ActiveProcessLinks 以外にもいくつかのデータ構造やテーブルを使っている。例えば、すべてのカーネルオブジェクトを管理する 句ハンドルテーブル(Handle Table) や、プロセスの例外的なリストなどだ。

メモリフォレンジックツール(Volatiliyなど)を使って、以下の不整合を探す。

1. ActiveProcessLinksからの逸脱:
プロセスリスト(pslist プラグインなど)には現れないのに、プロセスのハンドルテーブルをスキャンするプラグイン(hollowfind や handles など)には引っかかる。
2. KTHREAD(カーネルスレッド)の監査:
プロセスが隠されていても、CPUで実際に動いているスレッドは存在する。CPUコアのプロセッサブロック(PRCB)が指すスレッド情報を辿ると、どのEPROCESSに紐づいているかが丸わかりになる。リストから外されたEPROCESSが、実際にはアクティブなスレッドを持っているという「矛盾」を見つけるのだ。

現場では、ライブレスポンスでメモリダンプ(.dmp や物理メモリのrawイメージ)を安全に取得し、オフライン環境でこの矛盾を暴いていく。

—

3. アプリケーション開発者・インフラエンジニアが知るべき防衛策

「おいおい、カーネルレベルの攻撃なんて、OSのバグだろ? アプリやインフラエンジニアの俺たちに関係あるのか?」と思ったそこのお前。大間違いだ。

多くの場合、カーネルモードでコードを実行するRootkit(DKOMの実行犯)は、最初の足がかり(Initial Access)として、Webアプリケーションの脆弱性や、管理者権限を持った脆弱なサードパーティ製ドライバのインポート(Bring Your Own Vulnerable Driver: BYOVD攻撃)を悪用する。

つまり、お前らが書いたコードの脆弱性や、杜撰な権限管理が、最終的にカーネルを乗っ取られる引き金になるのだ。

堅牢な設計のためのPythonによる設定・監査サンプル

例えば、インフラの構成管理や、システムの整合性を定期的にチェックする監査スクリプトの例を示そう。実務では、このようなスクリプトをCI/CDパイプラインやEDRの拡張として組み込み、不審なカーネルモジュール(ドライバ)のロードを監視する。

以下は、システムにロードされたドライバのデジタル署名やハッシュを検証し、既知の脆弱なドライバ(BYOVDに悪用されるもの)が勝手に配置されていないかをチェックするPythonスクリプトの実装例だ。

import os
import hashlib
import subprocess
import sys

# 既知の脆弱なカーネルドライバのSHA-256ハッシュリスト(ブラックリスト)
# ※実際の運用では最新の脅威インテリジェンスフィードと連携させます
KNOWN_VULNERABLE_DRIVER_HASHES = {
    "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", # サンプルハッシュ
    "5d41402abc4b2a76b9719d911017c5922143748cc84122932481033e0fb80113", # サンプルハッシュ
}

# 監視対象となるドライバの格納ディレクトリ
TARGET_DRIVER_DIRS = [
    r"C:\Windows\System32\drivers",
    r"C:\Windows\SysWOW64\drivers"
]

def calculate_sha256(file_path):
    """ファイルのSHA-256ハッシュを効率的に計算する"""
    sha256_hash = hashlib.sha256()
    try:
        with open(file_path, "rb") as f:
            # チャンクごとに読み込んでメモリ消費を抑える
            for byte_block in iter(lambda: f.read(4096), b""):
                sha256_hash.update(byte_block)
        return sha256_hash.hexdigest()
    except PermissionError:
        print(f"[-] アクセス拒否: {file_path} を読み込めません。管理者権限を確認してください。")
        return None
    except Exception as e:
        print(f"[-] エラー発生 ({file_path}): {e}")
        return None

def audit_kernel_drivers():
    """システム内のドライバをスキャンし、既知の脆弱性や不正な改ざんを検知する"""
    print("[*] カーネルドライバの整合性監査を開始します...")
    threat_detected = False

    for driver_dir in TARGET_DRIVER_DIRS:
        if not os.path.exists(driver_dir):
            continue
        
        print(f"[*] スキャン中: {driver_dir}")
        for root, _, files in os.walk(driver_dir):
            for file in files:
                if file.lower().endswith(".sys"):
                    file_path = os.path.join(root, file)
                    file_hash = calculate_sha256(file_path)
                    
                    if not file_hash:
                        continue
                    
                    # ブラックリストとの照合
                    if file_hash in KNOWN_VULNERABLE_DRIVER_HASHES:
                        print(f"[!] 【警告】既知の脆弱なドライバを検知しました!")
                        print(f"    パス: {file_path}")
                        print(f"    ハッシュ: {file_hash}")
                        threat_detected = True
                    else:
                        # 冗長なログ出力を避け、詳細モードでのみ出力する場合はコメントアウト
                        pass

    if threat_detected:
        print("\n[!] 警告: システムに不正または脆弱なドライバが存在します。即座にインシデントレスポンス手順に移行してください。")
        sys.exit(1)
    else:
        print("[+] 既知の脆弱なカーネルドライバは検出されませんでした。")
        sys.exit(0)

if __name__ == "__main__":
    # スクリプトの実行には管理者権限が必須
    if os.name == 'nt':
        audit_kernel_drivers()
    else:
        print("[-] このスクリプトはWindows環境専用です。")
        sys.exit(1)

現場で実践すべき防御の鉄則

1. HVCI(Hypervisor-Protected Code Integrity)の有効化:
最新のWindows ServerやWindows 10/11では、仮想化ベースのセキュリティ(VBS)を利用したHVCIを必ず有効にしろ。これにより、カーネルモードで実行されるコードの整合性が厳格にチェックされ、未署名あるいは信頼されていない脆弱なドライバ(BYOVD)のロードをハードウェアレベルでブロックできる。
2. 最小権限の原則(Principle of Least Privilege)の徹底:
Webアプリケーションのプロセスやコンテナが、万が一乗っ取られたとしても、OSの管理者権限やシステム権限(NT AUTHORITY\SYSTEM)に昇格できないような設計にしろ。コンテナ環境であれば、特権コンテナ(--privileged)の安易な使用は厳禁だ。
3. EDRの導入と定期的なメモリフォレンジック訓練:
シグネチャベースの従来のアンチウイルスでは、DKOMのようなメモリ上の構造体書き換えは検知しにくい。カーネルの振る舞いを監視できるEDRを導入し、定期的に異常なプロセスの隠蔽がないかを検証するハンズオン(フォレンジック訓練)をチームで行うことだ。

セキュリティとは、魔法の盾を探すことじゃない。システム全体の細かな「ほころび」を地道に塞ぎ、攻撃者にコストを払わせ続けることだ。今日のこの知識を、お前たちの現場のコードレビューやインフラ設計に必ず活かしてくれ。頼んだぞ。

コメント

タイトルとURLをコピーしました