ゴーストを暴く:PEB操作によるプロセス隠蔽と、メモリフォレンジックによる真実の追跡
現場でインシデントレスポンス(IR)を指揮していると、OSが報告する「正当な情報」がどれほど簡単に嘘をつくかを痛感させられる。タスクマネージャーや tasklist コマンドがクリーンに見えても、メモリの深淵では別の時間が流れている。
特に、攻撃者がカーネルレベルや高度なユーザーランドテクニックを用いて行う「プロセスの隠蔽」は、ログ収集だけでは決して見抜けない。今日は、Windowsのプロセス環境ブロック(PEB)を悪用した隠蔽手法と、それを見抜くためのメモリフォレンジックの真髄について語ろう。
—
1. 隠蔽のメカニズム:ActiveProcessLinks の切り離し
Windowsにおいて、カーネルは EPROCESS 構造体という巨大なデータ構造で全プロセスを管理している。OSがプロセスを列挙する際、通常は EPROCESS 内にある ActiveProcessLinks という双方向連結リスト(LIST_ENTRY)を辿る。
攻撃者はこれを利用する。自身のプロセスをこのリストから「切り離す(Unlinking)」ことで、リストを辿る標準的なAPI(EnumProcesses 等)から姿を消すわけだ。
しかし、ここで一つ重要な点がある。リストから切り離しただけでは、そのプロセスは実行できない。 スケジューラは ActiveProcessLinks を見ているわけではなく、別のスレッドスケジューリングキューで動いているからだ。つまり、リストから消えても、CPUは平然と「隠されたコード」を実行し続ける。これがゴーストの正体だ。
—
2. 不整合を突く:スレッド列挙とハンドルテーブルの監査
OSの標準的なAPIが嘘をつくなら、APIを使わない手法で真実を掘り起こすしかない。Volatilityなどのフレームワークを活用し、以下の視点でメモリを切り刻むのがプロの流儀だ。
1. psscan と pslist の結果を比較せよ
pslist は ActiveProcessLinks を辿る。対して psscan はメモリ上の EPROCESS 構造体のヘッダー(Pool Tag)をシグネチャベースで直接スキャンする。もし psscan には見つかるが pslist にはないプロセスが存在するなら、それはほぼ確実に「隠蔽されたプロセス」である。
2. スレッドからの逆引き
ActiveProcessLinks が書き換えられても、スレッドの実行コンテキストまでは隠しきれないことが多い。メモリ上の全スレッドを列挙し、その所有者(OwnerProcess)を特定する。
以下は、メモリフォレンジック時にスレッドの所有関係を調査するための Volatility 3 スクリプトの概念的なロジックだ。
# Volatility 3のプラグイン構造を意識した疑似コード
# 特定のプロセスがリストから隠蔽されているかを確認するロジック
def check_process_integrity(layer_name, symbol_table):
# 1. アクティブなリストにあるPIDを取得
active_pids = get_pids_from_active_list()
# 2. メモリ全体をスキャンしてEPROCESS構造体を拾う
all_eprocess_objects = scan_memory_for_eprocess_tags()
for proc in all_eprocess_objects:
if proc.pid not in active_pids:
# リストにはないがメモリに存在するプロセスを発見
print(f"[!] 隠蔽の疑い: PID {proc.pid} がActiveProcessLinksから消去されています")
# 3. さらにスレッドを列挙して活動状態を確認
threads = get_threads_for_eprocess(proc)
for thread in threads:
# 命令ポインタ(RIP/EIP)を確認して何を実行しているか特定する
print(f" -> 実行中スレッド: {thread.tid}, RIP: {hex(thread.rip)}")
—
3. 防衛アーキテクチャの視点:なぜ「見抜けない」のか
この手の攻撃がなぜ有効かといえば、Windowsのカーネル設計が「信頼されたコンポーネントによるリスト管理」を前提としているからだ。現代のEDR(Endpoint Detection and Response)は、これらに対抗するためにカーネルコールバック(PsSetCreateProcessNotifyRoutine)を活用する。
しかし、攻撃者はさらにその先を行き、これらのコールバックを無効化したり、パッチを当てたりする。
アーキテクトへの提言
- カーネル整合性の維持: PatchGuard(KPP)を無効化するような攻撃を検知するために、ブート時のメモリ整合性チェックを必須とすること。
- ハードウェア支援の隔離: 重要なプロセスは、VBS(Virtualization-Based Security)やHVCI(Hypervisor-Enforced Code Integrity)を活用し、ハイパーバイザー側からカーネルメモリを保護する構造を強制せよ。
- メモリダンプの自動化: インシデント発生時、OSのAPIに依存しない「ライブメモリ取得ツール」を事前に配備し、メモリの静的・動的解析を自動実行するパイプラインを組むこと。
—
結びに:泥臭い検証の重要性
「ツールが検知しなかったから安全だ」と考えるのは、インシデントレスポンスの現場では最大の禁忌だ。PEBやActiveProcessLinksの操作は、サイバー犯罪者が使う極めて古典的かつ強力な技術だ。
我々の仕事は、OSが提示する綺麗なGUIの裏側にある「メモリの砂場」に手を突っ込み、そこに残された生々しい痕跡を拾い上げることにある。暗号化技術やAIによる防御が進化しても、最終的にCPUが計算を行う物理的なメモリ空間には、攻撃の痕跡が必ず刻まれる。その「真実」を見逃さない鋭い観察眼こそが、チーフホワイトハッカーに求められる唯一の資質だ。
次回の調査では、ActiveProcessLinks だけでなく、HandleTable の不整合にも注目してほしい。隠されたゴーストは、必ずどこかでリソースを握りしめているものだから。
コメント