姿なきマルウェアを炙り出せ!Windowsメモリ解析で学ぶ「プロセス隠蔽」のカラクリと検知術
みなさん、こんにちは!セキュリティサイバー領域の調査・分析(DFIR)を担当しているSOCアナリストです。
突然ですが、みなさんがパソコンの動きが重いと感じたとき、真っ先に開くツールは何でしょうか? おそらく多くの人が Ctrl + Shift + Esc を押して「タスクマネージャー」を開きますよね。
実は、高度な攻撃を行うサイバー犯罪者やマルウェアは、「タスクマネージャーに見えないように自分自身の姿を消す」という特殊な透明化の魔法(技術)を使ってきます。「タスクマネージャーに載っていないから安心!」……そう思っていたシステムが、裏ではすでに乗っ取られていたというインシデント現場に、私は何度も遭遇してきました。
今回は、新人のIT担当者やセキュリティに興味を持ち始めた開発者のみなさんに向けて、攻撃者がどうやってWindowsの目をごまかすのか、そして私たちがどうやってその「隠された悪魔」をメモリの中から見つけ出しているのかを、身近な例えを交えながら一歩ずつ解説していきますね!
—
1. プロセス管理の仕組み:クラスの「手をつなぐ列」
攻撃者の隠蔽工作を理解するために、まずはWindowsがどうやってプログラム(プロセス)を管理しているのか、基本の仕組みから覗いてみましょう。
WindowsOSの中では、常に何百ものプログラムが動いています。OSはこれらを管理するために、メモリ上に「プロセスの名簿」と「プロフィールカード」を作成しています。
- EPROCESS構造体(カーネル側の公式名簿):OSの心臓部(カーネル)が管理する、最も重要なプロセスの基本データです。
- PEB(Process Environment Block / プロセス環境ブロック):プログラム自身(ユーザーモード)が持っている「自分のプロフィールカード」です。実行ファイルのパスや環境変数などが書かれています。
これだけだと少し難しく聞こえますよね。学校の体育の授業をイメージしてみてください。
体育の授業と「手をつなぐ列」の例え
先生(OSのタスクマネージャー)が「今、グラウンドに何人の生徒がいるか」を確認するとき、生徒全員に「前後の人と手をつないで一列になりなさーい!」と指示を出します。
この「前後の人と手をつないでいる状態」こそが、Windowsの ActiveProcessLinks(アクティブプロセスリンク) と呼ばれる双方向リスト構造(数珠繋ぎの仕組み)です。
[プロセスA] <---> [プロセスB] <---> [プロセスC] <---> [プロセスD]
タスクマネージャーなどの管理ツールは、この「手をつないだ列」を端から順番にたどっていくことで、「今はA君、B君、C君、D君がいるな」と画面に表示しているのです。
—
2. 攻撃者の罠:DKOMによる「列からの脱出」
では、悪い生徒(マルウェア)が「先生にバレずにグラウンドで好き勝手暴れたい」と考えたらどうするでしょうか?
答えは簡単です。「両隣の子の手をそっと離して、列から抜け出す」のです。
これが、カーネルメモリの構造体を直接書き換えてプロセスを隠す DKOM(Direct Kernel Object Manipulation) という攻撃手法です。
【正常な状態】
[プロセスA] <---> [悪質プロセスB] <---> [プロセスC]
【DKOMでBが隠蔽された状態】
[プロセスA] <-------------------------> [プロセスC]
[悪質プロセスB] (ポインタを切断して孤立化)
攻撃者は、カーネル権限を使って ActiveProcessLinks のリンク(ポインタ)を書き換え、悪質プロセスBの前後の手をつなぎ直してしまいます。
すると、どうなるでしょうか?
1. 先生(タスクマネージャー)が列をたどると:プロセスAの次はプロセスCに飛ぶため、悪質プロセスBは存在しないように見える。
2. しかし、CPU(グラウンドの監督者)は:手をつないでいなくても「B君が存在しているアドレス」を知っているため、バックグラウンドで処理を実行し続けることができる。
これが、「タスクマネージャーには一切表示されないのに、裏で元気に動いているマルウェア」のカラクリです。非常にずる賢い手口ですよね。
—
3. PEB(プロセス環境ブロック)と「整合性の破綻」
攻撃者は ActiveProcessLinks を切り離してタスクマネージャーを騙しますが、ここに大きな盲点(痕跡)が残ります。それが今回のテーマである PEB(プロセス環境ブロック) です。
プロセス自身が持つプロフィールカード(PEB)の中にも、実は「自分が読み込んでいるDLL(部品)のリスト」などが数珠繋ぎ(InLoadOrderModuleList など)で保持されています。
- OSの管理名簿(EPROCESSのActiveProcessLinks) = 列から消されている
- プログラムの自己主張(PEBのモジュールリスト) = メモリのどこかに痕跡が残っている
つまり、メモリ全体を精査したときに、「公式名簿(ActiveProcessLinks)には載っていないのに、メモリの底にはPEBやプロセスの名残が存在している」という不自然なズレ(乖離)が生じるのです。
デジタルフォレンジックを担当する私たちは、この「ズレ」を逃さずキャッチします!
—
4. 実践!メモリダンプから隠蔽プロセスを炙り出す
ここからは、実際にSOCアナリストがインシデント調査で使うオープンソースのメモリ解析フレームワーク Volatility 3 を使った調査の考え方を解説します。
Volatilityには、プロセスを探すためのコマンドが複数用意されています。この違いを知ることが、隠蔽検知の第一歩になります。
プロセス検出コマンドの違い
1. windows.pslist (標準的な列の走査)
- 仕組み:
ActiveProcessLinks(手をつないだ列)を順番にたどります。 - 結果: DKOMで隠蔽されたプロセスは見つけることができません。
2. windows.psscan (メモリ全体のシグネチャスキャン)
- 仕組み: 手をつないだ列を無視して、メモリ空間全体(グラウンド全体)をローラー作戦で探し、
EPROCESS構造体の特徴(プールタグ等)を直接探します。 - 結果: 列から外された隠蔽プロセスも発見できます。
3. windows.pstree (親子関係の可視化)
- 仕組み: プロセスの親子関係をツリー状に表示します。
Pythonによる簡易PEB/EPROCESS不整合検知ロジック(概念コード)
メモリダンプから「隠蔽されたプロセス」を特定する自動解析スクリプトのロジック(イメージ)をPythonコードで書いてみました。
※実際のフォレンジックツール内部でどのように比較が行われているか、雰囲気を掴んでみてくださいね。
# ==============================================================================
# メモリダンプ解析における隠蔽プロセス(DKOM)検知の概念コード
# ==============================================================================
def detect_hidden_processes(pslist_processes, psscan_processes):
"""
ActiveProcessLinks経由で取得したリスト(pslist)と、
メモリ直接スキャンで取得したリスト(psscan)を比較して隠蔽プロセスを特定します。
"""
# pslistで取得できたPID(プロセスID)のセットを作成
visible_pids = {p['pid'] for p in pslist_processes}
hidden_processes = []
# メモリ全体から見つかったプロセスを一つずつ確認
for process in psscan_processes:
pid = process['pid']
name = process['name']
peb_address = process['peb_address']
# メモリ上には存在するのに、標準リスト(pslist)に載っていない場合
if pid not in visible_pids:
# PEBアドレスが存在するか確認(実体が存在する証拠)
if peb_address != 0:
hidden_processes.append({
'pid': pid,
'name': name,
'peb_address': hex(peb_address),
'reason': 'ActiveProcessLinksから切断されていますが、PEB構造体が確認されました (DKOMの可能性極大)'
})
return hidden_processes
# --- テスト用サンプルデータ ---
# pslist: タスクマネージャー等が見ている正規のリスト
pslist_data = [
{'pid': 4, 'name': 'System'},
{'pid': 416, 'name': 'smss.exe'},
{'pid': 1000, 'name': 'explorer.exe'}
]
# psscan: メモリ全体を直接スキャンして見つけたリスト
psscan_data = [
{'pid': 4, 'name': 'System', 'peb_address': 0x0},
{'pid': 416, 'name': 'smss.exe', 'peb_address': 0x7fffffd0000},
{'pid': 1000, 'name': 'explorer.exe', 'peb_address': 0x7fbf0000000},
{'pid': 6666, 'name': 'malware_evil.exe', 'peb_address': 0x7fa12345000} # 隠蔽された敵!
]
# 検知ロジックの実行
results = detect_hidden_processes(pslist_data, psscan_data)
# 調査結果の出力
for item in results:
print(f"[!] 警告: 隠蔽プロセスを発見しました!")
print(f" プロセス名 : {item['name']} (PID: {item['pid']})")
print(f" PEBアドレス: {item['peb_address']}")
print(f" 詳細 : {item['reason']}\n")
このコードを実行すると、pslist_data(手をつないだ列)には存在しない malware_evil.exe (PID: 6666) が、psscan によって見事炙り出されるわけです!
—
5. 防御ヘッダーと最新セキュリティの視点
「こんな風にメモリを書き換えられたら、どう防げばいいの?」と不安になりますよね。
現代のWindows(Windows 10 / 11やWindows Server)では、こうしたDKOMのような凶悪な攻撃を防ぐために、以下のような高度な防御メカニズムが標準で組み込まれています。
1. Kernel Patch Protection(KPP / 通称 PatchGuard)
64bit版Windowsに搭載されている仕組みです。カーネル内の重要構造体(ActiveProcessLinks を含む)が改ざんされていないかを定期的・ランダムにチェックしています。もし改ざんを検知した場合、OSはシステムを保護するために即座にブルーシステム(BSOD)を発生させて強制停止します。
2. HVCI(Hypervisor-protected Code Integrity / メモリ整合性)
仮想化技術(Hyper-V)を利用して、カーネルモードのメモリ領域を保護する仕組みです。たとえ管理権限を奪われても、署名のない不正なコードがカーネル内で実行されたり、メモリ構造体が書き換えられたりするのを強力に阻止します。
—
まとめ:一歩ずつ対策を学んでいきましょう!
最後に、今回のポイントを振り返ってみましょう。
1. タスクマネージャーは「手をつないだ列(ActiveProcessLinks)」を見ているだけ。
2. 攻撃者はDKOMの手法を使い、列から手だけを離してプロセスを隠蔽(透明化)する。
3. しかし、メモリ全体を直接探せば、PEBやEPROCESSの痕跡から必ず見つけることができる!
セキュリティの技術は一見すると非常に複雑で、難解な英単語や構造体の名前がたくさん出てきます。ですが、今回のように「体育の授業の列」や「名簿」といった日常の仕組みに置き換えて考えてみると、攻撃者がやっていることのロジックがスッキリ見えてきますよね。
「敵がどうやって隠れるか」を知ることは、強力な防御策を築くための第一歩です。焦らず、一歩ずつ知識を深めて、安全なシステム作りを目指していきましょう!
何か分からないことや気になった点があれば、いつでも記事のコメントなどで教えてくださいね。次回も現場のリアルな知見を分かりやすくお届けします!
コメント