【実務・中級編】 メモリフォレンジックにおけるDLLインジェクションの痕跡特定(Reflective DLL Injection) – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

メモリの「亡霊」を暴け: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)」を理解し、設定を突き詰めるエンジニアこそが、最後には会社を守ることになる。泥臭いログの確認と、堅牢な設定の積み重ねこそが、最高の実戦的防御だと肝に銘じておいてほしい。

コメント

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