【実務・中級編】 Windowsメモリにおけるプロセス環境ブロック(PEB)の解析と隠蔽プロセスの特定 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

幽霊はタスクマネージャーに映らない:PEB操作によるプロセス隠蔽と「真実」の暴き方

現場でインシデントレスポンス(IR)をしていると、管理者に「サーバーが異常に重いのに、プロセスリストには何も怪しいものがない」と相談されることがよくある。これは、攻撃者がOSの「見せかけ」を悪用して潜伏している典型的なサインだ。

今日は、Windowsの心臓部であるPEB(Process Environment Block)を操作してプロセスを隠蔽する手法と、それをどうやって見つけ出すか、その泥臭い現場の知見を共有する。

1. 攻撃者の手口:PEBは「名簿」に過ぎない

Windowsにおいて、タスクマネージャーや tasklist コマンドがプロセスを表示する際、基本的にはカーネル内の EPROCESS 構造体というリストを辿っている。しかし、ここを直接改ざんするのはカーネルモードのドライバが必要でリスクが高い。

そこで攻撃者は、ユーザーモードで完結する「PEBのリンク操作」を行う。
PEB内には ActiveProcessLinks という二重連結リストが存在する。これを書き換えて、自分自身のプロセスをリストから「切り離す(Unlink)」ことで、特定のAPI(EnumProcesses 等)から存在を隠すことができるんだ。

攻撃のロジック(概念):
1. 目的のプロセスが自身のPEBにアクセスする。
2. InLoadOrderModuleList などのポインタを書き換え、リストの「前」のノードと「後ろ」のノードを直接繋ぐ。
3. これにより、リストの巡回から自分のプロセスがスキップされ、標準的な監視ツールからは「存在しない幽霊」になる。

2. どうやって暴くのか?「不整合」を突く

ここで重要なのは、「OSは複数の方法でプロセスを管理している」という事実だ。

PEB(ユーザーモード)上のリストを消しても、カーネルオブジェクト(EPROCESS)そのものまで消すことはできない。なぜなら、それをやってしまうとOSのスケジューラがCPU時間を割り当てられなくなり、プロセスそのものがクラッシュするからだ。

つまり、「PEBリストには載っていないが、カーネルのスケジューリングキューには存在する」という不整合を探せばいい。

3. Pythonで実装する「不整合検出」の足掛かり

現場では Volatility などのツールを使うのが定石だが、原理を理解するために、Pythonの pywin32 を使って「標準APIで見えるリスト」と「スレッドID(TID)やハンドルから逆引きしたリスト」を比較するプロトタイプを紹介する。

import win32process
import psutil

def detect_hidden_processes():
    # 1. psutil(標準API経由)で取得したプロセスリスト
    visible_procs = {p.pid for p in psutil.process_iter(['pid'])}
    
    # 2. システム上の全スレッドからPIDを抽出する(隠蔽されてもカーネルには残る)
    # 実際にはカーネルドライバが必要だが、ここでは概念として記載
    found_pids = set()
    # 実際の現場ではここでEnumProcessesとカーネルオブジェクトの比較を行う
    
    # 3. 比較ロジック
    # 標準APIにはないが、システム内にハンドルが存在するPIDを特定する
    for pid in found_pids:
        if pid not in visible_procs:
            print(f"[!] 警告: 隠蔽された可能性のあるPIDを発見: {pid}")
            # ここでメモリダンプを取得し、解析へ回す

4. 完全に防御するためのセキュアな運用設計

PEB操作による隠蔽を許さないためには、エンドポイントでの検知能力を強化する必要がある。以下のルールを徹底してくれ。

A. EDRの活用とカーネルコールバック

OSのAPI(EnumProcesses 等)を信頼してはいけない。EDR(CrowdStrike, SentinelOne等)を導入し、PsSetCreateProcessNotifyRoutine を使ってプロセス生成をカーネルレベルで監視している製品を選定すること。これらはPEB操作に関係なく、カーネルにプロセスが登録された瞬間に検知する。

B. Nginx/WAFでの防御(入り口を塞ぐ)

プロセス隠蔽を試みる攻撃者は、多くの場合、Webシェル経由で初期侵入を行う。まずはWebの入り口で不正なコマンド実行を遮断する。

# Nginxでのコマンド実行防止例
location /upload/ {
    # .phpなどの実行を禁止する
    location ~ \.php$ {
        deny all;
    }
    # コマンドインジェクションの兆候をフィルタリング
    if ($query_string ~* "cmd=|powershell|/bin/bash") {
        return 403;
    }
}

C. メモリフォレンジックの習慣化

定期的にメモリダンプを取得し、Volatility の psxview プラグインを実行する運用を組み込むこと。psxview は、pslist(PEBリスト)、psscan(カーネル構造体の総当たりスキャン)の結果を比較し、不整合があれば即座に教えてくれる。

最後に:エンジニアへ伝えたいこと

「リストにないから大丈夫」という考えは、インシデント現場では致命的な慢心だ。攻撃者は常にOSの「盲点」を探している。

君たちが書くコードや運用するインフラも同じだ。「正規の手順で表示されるもの」と「実際にメモリ上で起きていること」の間にギャップがないか、常に疑う姿勢を持ってほしい。その疑いこそが、最も強力なセキュリティ対策になる。

もし不審な挙動を見つけたら、まずは落ち着いてメモリダンプを取れ。そのデータが、攻撃者を追い詰める唯一の証拠になる。

コメント

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