【テクニカル・上級編】 メモリ上のネットワーク接続情報(netscan)を用いたC2通信の特定 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

ネットワークの残像を追う:メモリフォレンジックによるC2通信の特定と実務的アプローチ

インシデントレスポンスの現場において、ネットワーク機器のログやEDRのアラートだけでは全体像が見えない瞬間がある。プロセスのインジェクション、メモリ上だけで完結するファイルレスマルウェア、あるいは巧妙に難読化されたプロセスアトリビューション。これらが絡み合うとき、最後に真実を語るのは「物理メモリ(RAM)」だ。

特に、Command and Control(C2)サーバーとの通信は、攻撃者がいかに痕跡を消そうとも、OSのネットワークスタックを経由する以上、必ずメモリのどこかにその爪痕を残す。今回は、Volatility等のツールを用いた netscan プラグインの活用法を軸に、メモリ上のネットワーク接続情報から高度なC2通信を炙り出す実務的アプローチを、現場の泥臭い知見と共にお届けする。

—

1. なぜ「死んだデータ」ではなく「生きたメモリ」を見るのか

現代のサイバー犯罪グループやAPTアクターは、ディスク上のフォレンジック対策にかけてはプロフェッショナルだ。彼らはインプラントのバイナリを削除し、タイムスタンプを偽装し、ログをクリアする。しかし、CPUが実行し、OSのネットワークスタックが処理している「現在の状態」を完全に隠蔽することは、物理法則上、極めて困難である。

ネットワーク接続情報は、OSのカーネル空間におけるデータ構造(TCPコンテキストブロックやソケット構造体など)に保持されている。ディスク上からは消え去ったマルウェアであっても、メモリダンプを採取しさえすれば、以下の情報が静的なスナップショットとして浮かび上がってくる。

  • 確立された、あるいはTIME_WAIT状態のTCP/UDPセッション
  • 通信を行っているプロセスのPID(プロセスID)とプロセス名
  • ソケットを開いた正確なタイムスタンプ(OSの仕様に依存)
  • リモートIPアドレスとポート番号

これを適切に抽出・分析することで、「どのプロセスが、いつ、どの外部の闇へと繋がっていたのか」を完全に再構築できる。

—

2. Volatility 3による netscan の実戦投入

メモリ解析のデファクトスタンダードである Volatility(ここでは最新の Volatility 3 を前提とする)には、ネットワーク接続を列挙するための強力なプラグインが用意されている。Windowsのメモリダンプ(10 や 11、Server系)を対象とする場合、基本となるのは windows.netscan プラグインだ。

まずは、取得したメモリダンプからアクティブなネットワーク接続をリストアップするコマンドを実行する。

# Volatility 3を使用してメモリダンプからネットワーク接続情報を抽出する例
python3 vol.py -f suspicious_memory_dump.raw windows.netscan

このコマンドを実行すると、以下のような情報が標準出力に吐き出される。

Grid
Offset                  Proto   Local IP       Local Port   Remote IP      Remote Port   State          Owner PID   Created
------------------------------------------------------------------------------------------------------------------------------------
0xfae012345678          TCPv4   192.168.10.50  49152        185.220.101.5  443           ESTABLISHED    2416        2023-10-25 14:12:33
0xfae087654321          UDPv4   0.0.0.0        53           *              *             CLOSED         1204

ここで注目すべきは、単に「外と繋がっている」という事実だけではない。インシデントレスポンスアナリストが確認すべき観点は以下の3点に集約される。

観点①:PIDとプロセスの不一致(Ghost Processes)

Owner PID に紐づくプロセス名が、実際のバイナリパスや正当な挙動と一致しているかを確認する。例えば、PID 2416 が svchost.exe と名乗っているにもかかわらず、その親プロセスが cmd.exe や不審なパスである場合、あるいはプロセス自体がすでに終了していて netscan の結果にしか存在しない(すでにプロセスが終了しているがソケット構造体がカーネルに残存しているケース)場合、それは高確率でプロセスインジェクションやゾンビ化したC2通信の痕跡だ。

観点②:異常なポートと通信先のレピュテーション

HTTPSである 443 番ポートを使っていながら、通信先IPアドレスが既知の匿名化ネットワーク(Tor exit nodes)や、直近で侵害された脆弱なVPSプロバイダのものである場合。あるいは、DNSトンネリングを疑わせる 53 番ポートを不審な外部IPへ直接叩いている場合など、プロトコルと宛先のミスマッチを見逃さない。

—

3. 隠蔽された接続の炙り出し:netstat と netscan の違い

初心者が陥りがちな罠として、古いVolatility 2の netstat プラグインをそのまま使おうとすることが挙げられる。しかし、netstat はOSのアクティブな構造体をリストするため、マルウェアがDKOM(Direct Kernel Object Manipulation)を用いてカーネル内の構造体から自身のソケットを隠蔽した場合、検知を逃れてしまう。

一方、netscan は、メモリプール(Pool)をスキャンし、過去に存在したソケット構造体の断片(カーネルプールのタグなど)をも総当たり的に探す。そのため、すでにプロセスや接続を切断したはずの通信の残骸や、隠蔽工作(DKOM)が施されたネットワーク接続であっても検出できる可能性が跳ね上がる。

この低レイヤの仕組みを理解しているかどうかが、巧妙なアクターの痕跡を見抜けるかどうかの分水嶺となる。

—

4. 自動化とトリアージのためのPythonスクリプト活用

大規模なインシデントレスポンスでは、数台から数十台のホストのメモリダンプを迅速にトリアージする必要がある。手動でのコマンド実行だけでは到底間に合わないため、Volatilityの出力をパースし、不審な外部IPを自動で脅威インテリジェンス(API等)と照合するスクリプトを組み込んでおくことが実務では求められる。

以下は、Volatilityの出力をJSON等で受け取り、プライベートIPや既知の安全なドメインを除外して不審な外部接続をフィルタリングする実務的なPythonスクリプトの概念例である。

import ipaddress
import json
import sys

def is_private_ip(ip_str):
    """
    指定されたIPアドレスがプライベートIPアドレス(RFC 1918等)か判定する
    """
    try:
        ip = ipaddress.ip_address(ip_str)
        return ip.is_private or ip.is_loopback or ip.is_link_local
    except ValueError:
        # ワイルドカードや名前解決できない場合の例外処理
        return True

def analyze_netscan_json(json_file_path):
    """
    Volatilityの出力結果(JSON)を読み込み、外部への不審な接続を抽出する
    """
    try:
        with open(json_file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
    except FileNotFoundError:
        print(f"[-] ファイルが見つかりません: {json_file_path}")
        return

    print("[*] 不審な外部ネットワーク接続の分析を開始します...")
    
    for row in data.get('rows', []):
        # カラム構造はVolatilityのバージョンに依存するため適宜調整
        proto = row[1]
        remote_ip = row[4]
        remote_port = row[5]
        state = row[6]
        pid = row[7]

        # 外部IPかつ確立されたコネクション(ESTABLISHED)に絞る
        if remote_ip and not is_private_ip(remote_ip):
            if state == 'ESTABLISHED':
                print(f"[!] 警告: 外部への確立された接続を検出しました!")
                print(f"    - プロトコル: {proto}")
                print(f"    - 接続先: {remote_ip}:{remote_port}")
                print(f"    - オーナーPID: {pid}")
                print("-" * 50)
                # ここに外部の脅威インテリジェンスAPI(VirusTotalやAbuseIPDB等)への問い合わせ処理を実装する

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python analyze_net.py <volatility_output.json>")
        sys.exit(1)
    
    analyze_netscan_json(sys.argv[1])

このようなスクリプトをパイプラインに組み込むことで、何千行もある netscan の出力からノイズを削ぎ落とし、アナリストが真っ先に対処すべき「真のC2通信」へと最短距離で到達できる。

—

5. 次世代の脅威とメモリフォレンジックの未来

C2通信の特定手法は日々進化しているが、攻撃者側もまた進化の手を緩めていない。近年では、暗号化通信の高度化に加え、メモリ上の痕跡すら残さないプロキシ技術や、EDRのフックをバイパスするダイレクトシステムコールを用いたマルウェアが主流になりつつある。

さらに、将来的に耐量子暗号(PQC)が一般的な通信プロトコルに実装されるようになれば、キャプチャしたパケットの事後復号が不可能になるため、エンドポイントの「メモリ上における平文化された瞬間」を捉えるメモリフォレンジックの価値は、より一層高まることになる。

インシデントレスポンスの本質は、テクノロジーの表層ではなく、システムが刻んだ「生の物理的真実」を直視することにある。ネットワークログが沈黙したとき、メモリダンプを開くその手が、組織を救う最後の防壁となるのだ。

コメント

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