Skip to content
Go back

【CAPEv2】マルウェア解析基盤構築でハマった「KVMの沼」と解決策

現在、マルウェア検知の研究のために、CAPE Sandboxを用いて大量の検体を動的解析する基盤を構築しています。

物理メモリ32GBのマシンにKVMでWindows 10のVMをいくつか立てて並列解析しようとしたところ、「VMが勝手にPausedになる」「ホストOSごとフリーズする」「設定を変えても直らない」 という泥沼にハマりました。

その理由は気が付けば大したことではなかったのですが、システムを安定稼働させるための知見が得られたので、備忘録としてまとめます。

環境

発生した問題

解析を開始すると、最初は順調に動くものの、数分経過すると以下の現象が発生し、システムが停止しました。

  1. VMが次々と paused 状態になる virsh list で確認すると、稼働中のVMが勝手に一時停止してしまう。
  2. SSHが応答しなくなる ターミナル操作すら受け付けなくなる。
  3. 設定変更が効かない メモリを減らしたり設定を変えても、解析を再開すると元の重い設定に戻っている。

原因調査:何が起きていたのか

調査の結果、原因は複合的でしたが、最大のボトルネックは 「ディスクI/Oのパンク」「KVMスナップショットの仕様」 だったと思います。

1. paused (migrating) の正体

停止したVMの状態を詳しく調べるため、以下のコマンドを実行しました。

virsh -c qemu:///system domstate win10_2 --reason
# 出力結果: paused (migrating)

これは「スナップショットからのメモリ復元(ロード)中」であることを示しています。 数台のVMが一斉にメモリ内容をディスクから読み込もうとした結果、NVMeの帯域が飽和。タイムアウト時間内に読み込みが終わらず、KVMが処理を中断(Pause)させていたことが主な原因だったと推測しています。

2. KVMスナップショットの「設定巻き戻し」

ドジすぎるのですが、これが一番のハマりポイントでした。 I/O負荷を下げるために virsh edit でメモリを4GB→2GBに減らしたのですが、解析が走るとなぜか4GBに戻って動作していました。

原因: KVMのスナップショットは、ディスクの状態だけでなく、「取得時点のXML設定(メモリ割当など)」も保存・復元します。 つまり、いくら設定ファイルを書き換えても、CAPEが解析開始時にスナップショットをロードした瞬間、設定もろとも「過去の状態」にタイムスリップしていたのです。(当たり前すぎる)

解決策:安定稼働のためのチューニング

以下の3つの対策を行うことで、現在は4並列で安定して解析を回せています。

対策1:VM設定の軽量化とI/O直結

ホストOSのメモリ不足(OOM)を防ぎ、I/Oを安定させるためにVMの設定をいくつか変更しました。

変更点:

  1. メモリ: 4GB → 2GB
  2. ディスクキャッシュ: cache='none' を追加
<driver name='qemu' type='qcow2' discard='unmap' cache='none'/>

対策2:スナップショットの「正しい」更新手順

設定変更を永続化させるためには、スナップショット自体を撮り直す必要がありました。 全VMに対して以下の手順を実施しました。

  1. VM停止: virsh destroy win10_x
  2. 設定変更: virsh edit win10_x (メモリ2GB化 & cache=‘none’)
  3. VM起動: virsh start win10_x
  4. 待機: デスクトップが表示され、Agent(Python)が起動するまで待つ。
  5. 新スナップショット作成: GUIでスナップショット作成

この「撮り直し」をして初めて、軽量化設定が解析時に反映されるようになりました。

対策3:CAPE側の「設定変更」 (最重要)

恐らくこの対策が最も重要だったと思います。 ハードウェアリソース(特にディスクI/O)の限界を超えないよう、cuckoo.conf で制御をかけました。

# /opt/CAPEv2/conf/cuckoo.conf

[cuckoo]
# 解析の並列数(メモリ容量に合わせて調整)
# 32GBメモリならOS分を残して5〜6台が安全圏
max_machines_count = 5

# 【最重要】VMの起動(復元)を同時に何台行うか
# デフォルトのままだと一斉に復元が始まりディスクが死ぬので「1」にする
max_vmstartup_count = 1

[timeouts]
# I/O待ちでタイムアウトしないよう長めに確保
default = 300
vm_state = 300

特に max_vmstartup_count = 1 が効果絶大でした。 「5台同時に解析を実行する」としても、「起動(スナップショット復元)は1台ずつ順番に行う」 ことで、ディスクへのアクセス集中を分散させ、paused (migrating) エラーを完全に根絶できました。

まとめ

大規模な動的解析基盤を構築する際の教訓は以下の通りです。

  1. 設定変更時はスナップショットも撮り直す KVMにおいて「設定変更」と「スナップショット更新」はセットで行うこと。
  2. 「同時実行数」と「同時起動数」は別物 ストレージがボトルネックになる場合、max_vmstartup_count を絞ることで、全体の処理速度を落とさずに安定性を劇的に向上できる。
  3. ホストメモリのキャッシュを過信しない VMが大量に書き込みを行う場合、cache='none' でホストOSを守る挙動の方が安定する可能性がある。

同じようなエラー(paused やフリーズ)に苦しんでいる方の参考になれば幸いです。


Share this post on:

Previous Post
【Ubuntu】NetplanでIPアドレスを固定する手順(パーミッション警告の対処法付き)
Next Post
【DVWA】DockerでDVWA環境を構築してコマンドインジェクションを試す