【実務・中級編】 メモリ上のコマンド履歴抽出:PowerShellの実行ログとスクリプトブロックの復元 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

メモリは嘘をつかない:PowerShell難読化スクリプトを「裸」にする技術

現場でインシデントレスポンスをしていると、よく耳にするセリフがある。「ログを消去したから、証拠は残っていないはずだ」。

甘い。あまりに甘い。攻撃者がどれほど巧妙にイベントログを改ざんしようが、ディスク上の痕跡を消そうが、彼らが実行した「その瞬間」のメモリには、実行されたコードの残骸が鮮明に焼き付いている。特に厄介なPowerShellの難読化スクリプトは、実行される直前に必ずメモリ上で「復号」され、素の姿を晒す。

今日は、攻撃者が最も好む「メモリ上のPowerShell実行」という盲点について、フォレンジックの視点と、それを未然に防ぐための防御策を語ろう。

—

なぜ攻撃者はメモリを狙うのか?

攻撃者がPowerShellを好む理由は、それがWindowsの標準管理ツールであり、OSに深く食い込んでいるからだ。そして、彼らが難読化(Base64エンコードやXOR暗号化など)を用いるのは、ディスク上の静的解析(ウイルス対策ソフトのシグネチャ検知)を回避するためである。

しかし、CPUがコードを実行するためには、メモリ上で難読化を解き、プレーンテキストのコマンドに変換しなければならない。我々フォレンジック担当者は、メモリダンプから PowerShell.exe のプロセス空間を掘り返し、この「解凍された瞬間のコード」を拾い上げる。これが ScriptBlockLogging をバイパスしようとする攻撃者に対する、我々の最大のカウンターだ。

—

現場で役立つ防御の鉄則:PowerShellを「透明化」する

メモリに痕跡を残す前に、あるいはメモリ上の痕跡を確実に記録するために、まずは環境の設定を見直そう。以下の設定をGPOやIntuneで強制適用しておくことが、インシデント発生時の初動調査の質を左右する。

1. Script Block Loggingの有効化(必須)

単なる実行ログではなく、実際に展開されたスクリプトの内容を記録する。

  • 設定箇所: コンピューターの構成 > 管理用テンプレート > Windows コンポーネント > Windows PowerShell
  • 設定値: PowerShell スクリプト ブロックのログ記録を有効にする を「有効」に設定。

2. Constrained Language Mode (CLM) の強制

これが最強の盾だ。CLMを有効にすると、PowerShellの強力な機能(Win32 APIの直接呼び出しなど)が制限される。攻撃者がメモリ上でシェルコードをインジェクションしようとしても、ここで弾かれる確率が飛躍的に高まる。

# CLMを強制するための環境変数設定例(検証用)
# 本番環境ではGPOの「言語モード」設定から適用することを推奨
$env:__PSLockdownPolicy = "4"

—

開発現場でやるべきこと:セキュアなコマンド実行の作法

Webアプリケーションや自動化ツールからPowerShellを呼び出す際、文字列連結でコマンドを組み立てていないだろうか?それは攻撃者に「コマンドインジェクション」という接待をしているようなものだ。

PythonからPowerShellを呼び出す際は、必ず subprocess モジュールで引数を分離し、実行ポリシーを厳格に管理すること。

Pythonによる安全なPowerShell実行サンプル

import subprocess

def execute_secure_powershell(script_path, args_dict):
    """
    スクリプトを直接実行し、引数は辞書形式で渡すことで
    インジェクションを防止する。
    """
    # 実行ポリシーを 'RemoteSigned' に制限した状態で起動
    cmd = [
        "powershell.exe",
        "-NoProfile",
        "-NonInteractive",
        "-ExecutionPolicy", "RemoteSigned",
        "-File", script_path
    ]
    
    # 引数を展開して渡す
    cmd.extend([f"-{k}", str(v)] for k, v in args_dict.items())
    
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        return result.stdout
    except subprocess.CalledProcessError as e:
        # エラー時は詳細をログに残すが、クライアントには詳細を返さない
        print(f"Error: {e.stderr}")
        return None

# 使用例
params = {"Username": "AdminUser", "Action": "Audit"}
output = execute_secure_powershell("C:\\Scripts\\Audit.ps1", params)

—

最後に:フォレンジックの心構え

もし、あなたが今、不審な挙動を見つけたなら、慌ててサーバーを再起動してはいけない。再起動はメモリ上の証拠をすべて消し去る「証拠隠滅行為」に等しいからだ。

1. ライブレスポンス: 再起動前に DumpIt や Magnet RAM Capture でメモリを取得する。
2. 文字列抽出: 取得したメモリダンプに対して strings.exe をかけ、'FromBase64String' や 'System.Management.Automation' といったキーワードを grep する。
3. 分析: 難読化されたスクリプトが展開されたブロックが見つかれば、それが攻撃の全容を語ってくれる。

技術を追いかけるだけでなく、「攻撃者はどこで足をすくわれるか」を常に意識すること。それが、エンジニアとしての生存率を上げる唯一の道だ。現場からは以上だ。また何かあればいつでも聞いてくれ。

コメント

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