メモリは嘘をつかない: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. 分析: 難読化されたスクリプトが展開されたブロックが見つかれば、それが攻撃の全容を語ってくれる。
技術を追いかけるだけでなく、「攻撃者はどこで足をすくわれるか」を常に意識すること。それが、エンジニアとしての生存率を上げる唯一の道だ。現場からは以上だ。また何かあればいつでも聞いてくれ。
コメント