メモリの「亡霊」を暴け:Reflective DLL Injectionの痕跡と実務的防衛術
現場でインシデント対応をしていると、「ログには何も残っていないのに、プロセスが異常な通信をしている」という相談をよく受ける。これこそが、メモリ上で完結する攻撃、いわゆる「ファイルレス攻撃」の典型だ。
特に、Reflective DLL Injection(リフレクティブDLLインジェクション)は、ディスク上に不正なDLLファイルを一切残さず、メモリ空間へ直接コードを注入して実行する。攻撃者は、Windowsの標準的なローダーを通さずにDLLをロードするため、LoadLibraryといったAPIのフックもすり抜ける。
今日は、この「メモリの亡霊」をどう検出し、そしてコードレベルでどう防ぐかについて、実務的な話をしよう。
—
1. なぜ「Reflective DLL Injection」が見抜けないのか
通常のDLLロードは、WindowsのローダーがPEB(Process Environment Block)のInLoadOrderModuleListといったリストを更新する。しかし、リフレクティブな手法では、攻撃者が自前のローダーをメモリ上に展開し、リストを更新せずに実行する。
そのため、管理ツールや一般的なEDRが見る「ロード済みモジュールリスト」には現れない。調査官がメモリダンプを解析する際、以下の「不整合」を探すのが定石だ。
- VAD(Virtual Address Descriptor)の不整合: メモリ領域の属性が
PAGE_EXECUTE_READWRITE(RWX) になっている。正規のDLLならPAGE_EXECUTE_READが基本だ。 - PEヘッダーの欠落: メモリ上の先頭にあるはずの
MZシグネチャやPEヘッダーが、本来のマッピングルールに従っていない。 - 未リンクのモジュール:
InLoadOrderModuleListにエントリがないにもかかわらず、実行権限を持つコードがメモリ上に存在する。
—
2. インジェクションを許さない「防御の鉄則」
この攻撃を完全に防ぐには、そもそも「メモリへの書き込みと実行」をアプリケーションレベルで許可しないことが重要だ。Webアプリ開発者やSREが意識すべきは、「不必要な権限の剥奪」に尽きる。
例えば、攻撃者がメモリにコードを注入する際、多くは VirtualAllocEx で PAGE_EXECUTE_READWRITE の領域を確保する。これをシステム側で制限する「防御的コーディング」が必要だ。
Pythonによる「メモリ保護」の検証スクリプト
Pythonなどのバックエンドでメモリ管理を行うことは稀だが、例えばカスタムの実行環境やサンドボックスを作る場合、以下のように「実行可能な領域に書き込み権限を与えない」設計が基本となる。
import ctypes
# 攻撃者が好む「書き込み可能かつ実行可能なメモリ」を防ぐ概念コード
def secure_allocate(size):
# PAGE_READWRITE (0x04) で確保し、データ書き込み後に
# VirtualProtect で PAGE_EXECUTE_READ (0x20) に昇格させる
# 決して PAGE_EXECUTE_READWRITE (0x40) を直接使わない
MEM_COMMIT = 0x1000
PAGE_READWRITE = 0x04
addr = ctypes.windll.kernel32.VirtualAlloc(None, size, MEM_COMMIT, PAGE_READWRITE)
# ... データ書き込み処理 ...
old_protect = ctypes.c_ulong()
PAGE_EXECUTE_READ = 0x20
ctypes.windll.kernel32.VirtualProtect(ctypes.c_void_p(addr), size, PAGE_EXECUTE_READ, ctypes.byref(old_protect))
return addr
—
3. WAFとサーバー設定で「入り口」を塞ぐ
メモリインジェクションは、多くの場合、Webアプリの脆弱性(RCE)から始まる。PHP環境において、system() や exec() を無効化することは、攻撃者がペイロード(シェルコード)をメモリに展開する前段階で止める最強の防壁だ。
Nginx + PHP-FPM のセキュア設定
php.ini での関数制限は必須だ。これを設定せずにWebサーバーを公開するのは、玄関の鍵を開けたまま外出するようなものだ。
; php.ini で危険な実行関数を殺す
; これにより、シェルコードの実行や外部DLLの呼び出しを未然に防ぐ
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
; アップロードディレクトリへの実行権限を剥奪
; Nginxのコンテキストで設定する
# Nginx設定例:特定のディレクトリでのスクリプト実行を禁止
location /uploads/ {
# ファイルのアップロード先ではPHPスクリプトを一切動かさない
location ~ \.php$ {
deny all;
}
}
—
4. 最後に:DFIR担当者からのアドバイス
「ツールを入れたから大丈夫」という考えは、インシデントレスポンスの現場では通用しない。攻撃者は、そのツールの検知ロジックをバイパスするよう進化し続けているからだ。
もしサーバーの挙動が怪しいと感じたら、Volatility などのツールを使い、以下のコマンドで不審な領域を炙り出してみてほしい。
malfind: 前述した RWX 属性のメモリ領域を特定する。ldrmodules: PEB上のリストと、実際のVADメモリマップを比較し、食い違いを指摘してくれる。
技術は常に攻撃者とのいたちごっこだが、「メモリ保護の原則(W^X: Write XOR Execute)」を理解し、設定を突き詰めるエンジニアこそが、最後には会社を守ることになる。泥臭いログの確認と、堅牢な設定の積み重ねこそが、最高の実戦的防御だと肝に銘じておいてほしい。
コメント