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

おい、ちょっと手を止めてこっちを向いてくれ。

今朝、クライアントのEDR(Endpoint Detection and Response)コンソールから不穏なアラートが上がった。CPU使用率が異常に跳ね上がっているにもかかわらず、タスクマネージャーや一般的なプロセス一覧取得コマンド(Get-Processやpsなど)では、その犯人らしいプロセスが綺麗さっぱり姿を消していたそうだ。

「チーフ、プロセスリストにいないのに、裏で何か動いてます……これって幽霊プロセスですか?」

後輩の焦った顔が目に浮かぶが、お化けの正体なんていない。いるのは、Windowsの内部構造を熟知し、OSの目をあざむく巧妙な侵入者だけだ。
こういう時、表側の綺麗なGUIツールや標準コマンドをいくら叩いても無駄足に終わる。私たちがやるべきは、OSの足元――カーネルメモリの深部を覗き込み、隠された足跡を物理的に暴き出すことだ。

今日は、メモリフォレンジックの現場で最もエキサイティングであり、かつ攻撃者の常套手段である「プロセス隠蔽(Process Unlinking)」のからくりと、それをメモリの整合性から炙り出す実務的なアプローチについて話をしよう。

—

1. なぜタスクマネージャーは「消されたプロセス」を見落とすのか?

Windows上で動作する通常のアプリケーションや管理ツールは、OSが提供するAPI(EnumProcessesやCreateToolhelp32Snapshotなど)を使ってプロセスの一覧を取得している。これらのAPIは、内部でカーネル空間にあるプロセスリストを参照して動いている。

攻撃者はここを突く。
彼らはカーネル空間(または高度な権限奪取後)で、Windowsのプロセス管理の根幹である EPROCESS 構造体の中にある ActiveProcessLinks という名の双方向リンクリスト(LIST_ENTRY)のポインタを書き換える。

想像してほしい。電車の車両が連結器(リンク)でガッチリ繋がっている状態を。
攻撃者は、隠したいプロセスの車両(EPROCESS)の連結器を外し、その前後の車両同士を直接繋ぎ直してしまうのだ。結果として、車掌(API)が前方から点呼を取って歩いても、「あれ、この車両だけ数に入ってないな?」とはならず、最初から存在しなかったかのようにスルーされてしまう。これが、Direct Kernel Object Manipulation (DKOM) を用いたプロセスの隠蔽だ。

さらにタチが悪いのは、プロセス名やイメージパスを格納している プロセス環境ブロック(PEB: Process Environment Block) 自体を偽装(PEB Spoofing)し、親プロセスを正当なシステムプロセス(svchost.exe や explorer.exe など)に見せかける手口まで組み合わせてくる点だ。表向きは完全に「安全な正規プロセス」の顔をして、裏でマルウェアが通信を確立している。

—

2. 隠蔽を見破るための「3つの視点」とフォレンジックの極意

じゃあ、この「消えたプロセス」をどうやって見つけ出すのか?
答えは簡単だ。「表のリスト」が見えないなら、「別の独立したリストやテーブル」と突き合わせ(クロスビュー解析) を行えばいい。

私たちDFIRチームが現場で必ず使う3つのアプローチを教えよう。

1. スレッドリスト(_ETHREAD)との照合
プロセスそのものはリンクリストから外されていても、その中で動いているスレッドはCPUにスケジューリングされなければならない。PsActiveProcessHead から外されたプロセスであっても、グローバルなスレッドリストや各プロセスのスレッドヘッドをスキャンすると、所属不明の浮遊スレッド(あるいはおかしな親を持つスレッド)がボロを出す。

2. ハンドルテーブル(Handle Table)の総当たり調査
システム上の他のプロセスが、その「隠されたプロセス」へのハンドル(開かれた扉)を保持している場合がある。ハンドルテーブルをダンプして逆引きすれば、リストから消されたゾンビの姿が浮かび上がってくる。

3. VAD(Virtual Address Descriptor)ツリーの解析
プロセスのメモリマップを管理するVADツリーは、 EPROCESS のリンクリストとは別の構造で保持されていることが多い。ここを舐め回すことで、メモリ上に不自然に展開されたPEヘッダを発見できる。

—

3. 実務で使える!メモリダンプ解析の自動化スクリプト(Python)

インシデントレスポンスの現場では、メモリイメージ(.raw や .dmp)を抽出し、Volatilityなどのフレームワークを使って解析するのが鉄則だ。
ここでは、取得したメモリダンプからプロセス情報を抽出する際、標準のプロセスリストとスレッドリストの不整合(隠蔽の兆候)を検出するための、実務で役立つPythonスクリプトの概念的なアプローチ(Volatility 3 APIをベースにしたカスタム解析スクリプトのイメージ)を共有する。

# -*- coding: utf-8 -*-
"""
インシデントレスポンス用メモリフォレンジック補助スクリプト
プロセス隠蔽(ActiveProcessLinksのリンク外し)の検出ロジックサンプル
"""

import sys
# 注: 実際の環境ではVolatility 3のライブラリやシンボルファイルをインポートします
# from volatility3.framework import contexts, interfaces, renderers
# from volatility3.plugins.windows import pslist, threads

def detect_unlinked_processes(context, layer_name, symbol_table):
    """
    PsActiveProcessHeadによる標準プロセスリストと、
    スレッドベースのプロセス探索結果を比較し、隠蔽プロセスを暴く
    """
    print("[*] メモリイメージからのプロセス隠蔽チェックを開始します...")
    
    # 擬似的なロジック表現
    standard_process_ids = get_standard_pslist(context, layer_name)
    thread_derived_pids = get_pids_from_threads(context, layer_name)
    
    # 双方の差分を計算(標準リストにいないが、スレッドが存在するPID)
    hidden_pids = thread_derived_pids - standard_process_ids
    
    if hidden_pids:
        print(f"[!] 警告: 隠蔽(Unlinked)された可能性のあるプロセスを検知しました!")
        for pid in hidden_pids:
            print(f"    -> 検出された不審なPID: {pid}")
            # 詳細なVADツリーやハンドルテーブルの調査をトリガーする
            dump_process_artifacts(context, pid)
    else:
        print("[+] 深刻なプロセス隠蔽の兆候は検出されませんでした。")

def get_standard_pslist(ctx, layer):
    # ActiveProcessLinksを辿る通常のリスト取得(タスクマネージャー相当)
    # 実装ではVolatilityの pslist プラグインのロジックを使用
    return {1000, 1240, 3402} # サンプルとしてのPIDセット

def get_pids_from_threads(ctx, layer):
    # ETHREAD構造体を総当たりし、そこから所属するEPROCESSを逆引きする
    # リンクリストが外されていても、スレッドが存在すればここで引っかかる
    return {1000, 1240, 3402, 9999} # 9999が隠蔽されている想定

def dump_process_artifacts(ctx, pid):
    print(f"    [+] PID {pid} のメモリ領域をダンプし、YARAMatchを実行します...")
    # ここにフォレンジック用のダンプ処理コードが入る

if __name__ == "__main__":
    # コマンドラインからの実行を想定
    print("DFIR Automation Tool v1.0 - Process Anomaly Detector")
    # detect_unlinked_processes(...)

このスクリプトのキモは、「表向きのリスト」と「裏の構造(スレッド)」の数の不一致を機械的に検知する点にある。攻撃者がどれだけ上手にリンクを外そうとも、OS上で命令を実行させ続ける以上、スレッドの存在を完全に消し去ることはカーネルアーキテクチャ上極めて困難だからだ。

—

4. そもそも「隠させない」ための堅牢なインフラ設計

フォレンジックでインシデントを暴くのはアナリストの醍醐味だが、プロとして最も重要なのは「そもそもそんな高度な攻撃を成功させない環境」を作ることだ。
EDRの導入はもちろんだが、システム設計やサーバー構築の段階で以下の鉄則を死守してほしい。

1. 最小権限の原則(The Principle of Least Privilege)の徹底
プロセスインジェクションやDKOM(ActiveProcessLinksの書き換え)を行うには、原則として SeDebugPrivilege などの特権(Administrator / SYSTEM権限)が必要だ。Webアプリケーションの脆弱性(RCEなど)から直接カーネル空間をいじられないよう、Webサーバーの実行ユーザーには最小限の権限しか与えないこと。IISの AppPoolIdentity や、Linux/Windowsを問わずサービスアカウントの権限分割をサボらないこと。

2. カーネル保護機能(HVCI / VBS)の有効化
現代のWindows ServerやWindows 10/11では、仮想化ベースのセキュリティ(VBS) や ハイパーバイザーコード完全性(HVCI) を必ず有効に設定すること。これらが有効であれば、仮に攻撃者がローカル管理者権限を奪取したとしても、カーネルメモリへの不正な直接書き込みやドライバの悪用(BYOVD: Bring Your Own Vulnerable Driver)がハードウェアレベルでブロックされる。

3. クラウド環境におけるIAMの厳格化
クラウド(AWS/Azure/GCP等)上の仮想マシンであれば、インスタンスメタデータサービス(IMDS)へのアクセス制限や、過剰な権限を持ったIAMロールの付与を防ぐことが、ラテラルムーブメント(横展開)による特権昇格を防ぐ最大の防御壁となる。

—

チーフからのメッセージ

「プロセスが見えない」という現象に直面したとき、初心者はパニックを起こすか、OSのバグだと勘違いして再起動して証拠を消してしまう。
だが、私たちセキュリティエンジニアは違う。画面の裏側で何が起きたのか、OSの構造を頭に浮かべ、メモリの隅々まで這いつくばって真実を暴き出す。

インシデントレスポンスは、泥臭くて、地道で、そして何よりロジカルなパズルだ。
次に「消えたプロセス」に出くわしたときは、今日の話を思い出してくれ。表の顔に惑わされず、裏の構造を疑え。それがプロの仕事だ。さて、次のチケットに取りかかろうか。

コメント

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