【テクニカル・上級編】 メモリ上のコマンド履歴の復元 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

残留する足跡:conhost.exeとPowerShellメモリ空間からのコマンド履歴復元と防衛アーキテクチャ

インシデントレスポンスの現場において、侵入者の足取りを追う作業はしばしば「消された証拠との戦い」に例えられる。敵はディスク上のログを消し、Event Logをクリアし、タイムスタンプを書き換える。しかし、彼らが揮発性メモリ上で実行した生々しいコマンドラインの履歴までは、完全に隠し通すことはできない。

ディスクフォレンジックが「過去の残骸」を拾う作業だとするなら、メモリフォレンジックは「現在進行形の思考」を覗き見る行為だ。本稿では、Windows環境におけるコンソールホスト(conhost.exe)およびPowerShellのプロセスメモリ構造に踏み込み、攻撃者が実行したコマンド履歴をいかにして抽出し、さらにこの脆弱性を突いたフォレンジック evasion(回避手法)に対して、どのようなアーキテクチャで対抗すべきかを深く掘り下げていく。

—

1. 低レイヤにおけるコマンド履歴の保持メカニズム

Windowsのコマンドラインインターフェース(cmd.exeやPowerShellなど)において、入力されたコマンドは、単にプロセスのスタック上に一時的に存在するわけではない。Windows 7以降、コンソールウィンドウの描画と入力管理は独立したプロセスである conhost.exe にオフロードされている。

conhost.exe の内部構造とヒープの闇

攻撃者がシェルを立ち上げ、難読化されたスクリプトやネイティブペイロードをダウンロード・実行するとき、その文字列は conhost.exe のプロセスヒープ上にバッファとして保持される。
ここで注目すべきは、Windowsのヒープマネージャの挙動だ。メモリが解放(HeapFree)されたとしても、その物理的な領域が即座にゼロクリアされるわけではない。フラグメント化を防ぐためのヒープ構造体(LFH: Low Fragmentation Heap など)の中に、かつて入力されたコマンド文字列の断片が生々しく残存し続ける。

特に、PowerShell(powershell.exe または pwsh.exe)環境下では、PSReadLine モジュールがデフォルトでコマンド履歴を %APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt に永続化する。しかし、熟練した攻撃者はインシデントの痕跡を消すためにこのファイルを削除、あるいは最初からメモリ上だけでコマンドを実行(インメモリ実行)する。ここにおいて、ディスク上の証拠は消失するが、conhost.exe や PowerShell のプロセスメモリ(Private Working Set)には、バッファリングされた履歴が容赦なく残されることになる。

—

2. メモリイメージからのコマンド履歴抽出:実用アプローチ

現場で取得したメモリダンプ(.raw や .dmp)から、失われたコマンド履歴をサルベージするための手法を解説する。Volatility 3 を用いた解析が王道であるが、よりプリミティブかつ確実なアプローチとして、プロセスメモリ空間から特定の構造やパターンをスキャンする手法を理解しておく必要がある。

以下の Python スクリプトは、ダンプされたメモリファイルから conhost.exe のプロセス空間をターゲットとし、Unicode(UTF-16LE)エンコードされたコマンド文字列の痕跡を正規表現ベースで効率的に抽出するための概念実証(PoC)コードの断片である。

import re
import sys

def extract_command_history(dump_file_path):
    """
    メモリダンプファイルからconhost.exeやPowerShellのプロセス空間に
    残されたUTF-16LEエンコードのコマンド文字列をヒューリスティックに抽出する
    """
    # 検索対象とするキーワードの例(攻撃者がよく使う難読化パターンやコマンドレット)
    # 実務ではより広範なヒューリスティックパターンを使用する
    pattern = re.compile(b'(?:[a-zA-Z0-9+\\-_/\\\\.:]\\\x00){4,}', re.DOTALL)
    
    print(f"[*] 解析対象のメモリダンプ: {dump_file_path}")
    
    try:
        with open(dump_file_path, 'rb') as f:
            # チャンクごとに読み込んでメモリ効率を最適化
            chunk_size = 1024 * 1024 * 10 # 10MBチャンク
            offset = 0
            
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                
                matches = pattern.finditer(chunk)
                for match in matches:
                    raw_match = match.group(0)
                    try:
                        # UTF-16LEとしてデコードを試みる
                        decoded = raw_match.decode('utf-16le', errors='ignore')
                        # 意味のあるコマンドラインの断片のみをフィルタリング
                        if len(decoded.strip()) > 3:
                            print(f"[Offset: 0x{offset + match.start():08X}] 検出文字列: {decoded.strip()}")
                    except UnicodeDecodeError:
                        continue
                        
                offset += len(chunk)
                
    except IOError as e:
        print(f"[!] ファイル読み込みエラー: {e}", file=sys.stderr)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python extract_history.py <memory_dump.raw>")
        sys.exit(1)
        
    extract_command_history(sys.argv[1])

このスクリプトは氷山の一角に過ぎない。実戦では、Volatility 3 の windows.pslist プラグインで conhost.exe のプロセスIDを特定し、windows.memmap や windows.dumpfiles を用いて当該プロセスのメモリページを切り出し、詳細なヒープ解析を行うことがアナリストの基本動作となる。

—

3. 高度な回避手法とフォレンジックの限界

攻撃者もただ無防備にコマンドを叩いているわけではない。EDR(Endpoint Detection and Response)やメモリフォレンジックの目を欺くため、彼らは以下のような高度な手法を用いてコマンド履歴の残存を阻止、あるいは攪乱する。

1. NtSuspendProcess によるスレッド凍結とメモリワイピング

洗練されたインプラント(カスタムC2エージェントなど)は、実行直後に自プロセスのメモリ空間をゼロクリア(RtlSecureZeroMemory)したり、conhost.exe とのパイプ通信を直接バイパスしてAPI呼び出し(Direct Syscalls)に移行したりする。これにより、conhost.exe のバッファにコマンドラインがそもそも生成されないように設計される。

2. アンチ・フォレンジックとしてのヒープスプレー

メモリ解析を困難にするため、ダンプファイル内に偽のコマンド文字列やノイズを意図的に大量生成し、アナリストや自動解析スクリプトを偽陽性の迷宮に引きずり込む手法も確認されている。これに対抗するには、単純なパターンマッチングを超えた、プロセスのコールスタック解析や親子関係(cmd.exe -> 不審なプロセス)の文脈的相関分析が不可欠となる。

—

4. 防御アーキテクチャの設計:メモリ上の痕跡を守る、あるいは残させない

セキュリティアーキテクトやチーフホワイトハッカーの立場から言えば、「事後的なメモリフォレンジックに頼らざるを得ない状況」そのものが、多層防御のどこかに綻びがあったことを意味する。真に強固な環境とは、攻撃者にメモリ上のコマンド履歴すら残させない、あるいは即座に検知・封じ込めるシステムデザインを持つ環境のことである。

ガードレイルとしてのアーキテクチャ要件

1. PowerShell Constrained Language Mode (CLM) の強制
不審なインメモリ実行や任意の .NET アセンブリのロードを防ぐため、環境全体で CLM をデフォルトとする。これにより、攻撃者がよく使うリフレクションや高度なコマンド実行の大部分が無効化される。
2. ScriptBlock Logging と AMSI (Antimalware Scan Interface) の統合
ディスク上の履歴ファイルに依存せず、PowerShellエンジン自体にすべてのスクリプトブロックをイベントログ(Event ID 4104)として強制的に記録させ、これをリアルタイムで SIEM / SOC に転送する。ログ転送の遅延をなくすことが、メモリフォレンジックの必要性を最小限にする。
3. エンドポイントにおけるプロセスインジェクション監視
conhost.exe に対して外部プロセスからハンドルが不正に取得される挙動や、予期せぬプロセスからの子プロセス生成をカーネルレベル(ETW Grundlagen / 弁別的EDRセンサー)でブロックする。

—

最後に:フォレンジックアナリストの執念

メモリフォレンジックは、デジタル空間における「遺体安置所」のような場所だ。そこには静まり返ったプロセスの断片と、かつてそこに存在した悪意の残響だけが転がっている。

conhost.exe のヒープの片隅に残された数バイトの文字列から、攻撃者の全体像を復元できた瞬間、アナリストの正義と執念が報われる。しかし、それに安住してはならない。私たちのゴールは、フォレンジック技術を極めることではなく、攻撃者がメモリ上にすら足跡を残せないほどの圧倒的な要塞を構築することなのだから。

コメント

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