こんにちは!
社内技術横断支援組織「Backbeat」の山田のてっちゃんです!
2/1(日)に開催された「防衛省サイバーコンテスト」に参加してきました。
Claude Codeを使ってCTF用AIエージェントを作成し、午前中には30/31問を解き、瞬間最高3位にもなりましたが、最終的には50位(個人・チーム含む)となり悔しい結果でした…。
まだまだ改善点はありますが、どのようなAIエージェントを構成して戦ったかをご紹介します。
並列化/サブエージェント活用の汎用的なTipsになりうる部分もありますので、ぜひ読んでいってください!
本記事は「CTFの解法(Writeup)」ではなく、Claude Codeでのマルチエージェント運用(設計と反省)にフォーカスします。
この記事でわかること
- 防衛省サイバーコンテストで、Claude Code+Kali LinuxでCTF用マルチエージェント(Opus/Sonnet/Haiku)を組んで運用した構成と方針
- サブエージェント並列実行が「最遅待ち」で詰まる原因と、run_in_background+TaskOutputでの解消パターン
- 「モデルの役割分担」と「Human-in-the-Loop」を運用ルールとして入れると、難問・詰まりの時間ロスを減らすアイディア
作ったもの(方針と制約)
CTFなので、Kali Linux上にClaude Codeをインストールして、各種ハッキング用ツールをエージェントに使わせる構成にしました。
また、複数の問題を同時に解いていく必要があるため、単一エージェントだと効率が悪く、サブエージェント運用を前提に設計しました。
ただし参加環境はプライベートの普通のノートPCで、Kali LinuxをVMで起動していたためメモリ8GBという制約がありました。
メモリがパツパツの状態で負荷の高いツールを回されてVMがハングすると致命的なので、以下を指示しています。
- サブエージェントは無限に起動せず、状況(メモリ等)を見て起動判断
- 基本は Auto-approve で解析・実行を進める
- ただし競技用サーバに負荷がかかりうる操作(例:ポートスキャン等)は ユーザーに確認を取る
この方針を踏まえて、Claude自身に CLAUDE.md とサブエージェント定義ファイルを出力させ、例題を数問解かせたのちそのまま大会へ突入しました。時間が取れずほぼ手直ししなかったのですが、唯一、例題を解いたときにまさかのサブエージェントを使わないというムーブをかましてきたので、「ちゃんとメンバーに指示して動かせるようにならなきゃね?」というお説教をして、サブエージェントをしっかり使うように修正しました。
最終的なエージェント構成
mod-ctf-claudecode/
├── .claude/
│ ├── CLAUDE.md # メイン設定
│ └── agents/
│ ├── crypto-solver.md # 暗号
│ ├── web-hacker.md # Web
│ ├── network-analyst.md # Network
│ ├── forensics-analyst.md # Forensics
│ ├── pwn-exploiter.md # Pwnable
│ ├── prog-solver.md # Programming
│ └── trivia-expert.md # Trivia
├── tools/
│ └── memcheck.sh # メモリ監視
├── setup.sh # セットアップ
└── README.md
- メインエージェント:Opus
- サブエージェント:Sonnet
- Trivia用:Haiku
ジャンルごとにサブエージェントの戦略(使うツール、進め方、諦め方など)を分けて、問題ごとに最適な動きをさせる作戦です。
その他の工夫:CTFd APIで「取得〜提出」を自動化
今回の大会はCTFdで出題されており、問題文や添付ファイル、フラグ提出にAPIが使えました。
事前にトークン払い出しも可能だったので、問題取得→攻略→フラグ提出までClaude自身にやらせる運用に寄せました。
「自分は何もしてないのに点数が積み上がっていく」瞬間があり、ちょっと疎外感すら覚えました。
振り返り
サブエージェント戦略自体はそれなりにハマり、スピード感を持って正答を重ねられました。
一方でチューニングが足りず、イマイチな動き(詰まり・無駄待ち・人間に聞けば一瞬系)が見えて反省も多かったです。
うまくいったところ
- サブエージェントは概ね狙い通り動作
- 最大3並列で、個別に問題を解いていく様子は確認できた
- CTFdの問題取得とフラグ提出も機能していた
- なお
memcheck.shはどうやら 一切使っていませんでした(ここは要改善)
課題(マルチエージェント戦略に絞る)
- サブエージェントの並列実行効率の低下(ブロッキング)
- モデル選択と役割分担の最適化不足(Sonnetが「全部やる」)
- 人間への介入依頼の不足(Human-in-the-Loop)
以降、順に書きます。
課題1:サブエージェント並列実行が「最遅に引きずられて」ブロックされた
観測された現象
3つのサブエージェントを並列起動した際、早く終わったA/Bの結果が出ているのに、Cが終わるまで待たされました。
[T+0秒] Agent A 起動 (Crypto)
[T+0秒] Agent B 起動 (Forensics)
[T+0秒] Agent C 起動 (Web)
[T+45秒] Agent A 完了 → 結果待機
[T+60秒] Agent B 完了 → 結果待機
[T+180秒]Agent C 完了 → ようやく全結果返却
原因(Claude Codeの挙動)
同一メッセージ内で複数の Task を呼ぶと、実行自体は並列でも 「結果が揃うまで次ターンに進めない」 形になり、最遅のタスクがボトルネックになります。
解決策:run_in_background: true を使う
Task をバックグラウンドで起動し、完了したものから TaskOutput で回収します。
# 従来(ブロッキング)
Task(subagent_type="crypto-solver", prompt="...")
# 改善後(ノンブロッキング)
Task(subagent_type="crypto-solver", prompt="...", run_in_background=True)
# => task_id / output_file を受け取り即次へ
改善後の運用イメージ:
[T+0秒] A/B/C を background 起動(task_id取得)
[T+1秒] メインは自由に動く(監視・次の割当・提出など)
[T+45秒] TaskOutput(A) → 回収して次のAgent起動
[T+60秒] TaskOutput(B) → 回収して次のAgent起動
[T+180秒] TaskOutput(C) → 回収
「空いた枠に次を流し込む」 ができるようになり、スループットが上がることが期待されます。
常にフルパワーで稼働し続けることにより、CPU使用率やメモリ消費、Claudeのスロットリングにひっかかるなど別の課題が出てくる可能性もあります。検証を重ねて最適な落とし所を探る必要はありそうです。
課題2:モデル選択と役割分担が「Sonnetに全部やらせる」構造だった
今回の構成(反省点)
| エージェント | モデル | 役割 |
|---|---|---|
| メイン | Opus 4.5 | 全体統括、振り分け、集約 |
| サブ×6 | Sonnet | 各カテゴリの問題解決(解析〜実行まで一貫) |
| trivia | Haiku | トリビア即答 |
サブ(Sonnet)が、問題を受け取ると 解析→設計→実装→実行 まで全部担当していました。
難問で「深い分析が必要」になったとき、試行錯誤が増えやすい形です。
改善案:フェーズ別にモデルを割り当てる
- Phase 1:解析・設計(Opus)
- Phase 2:実装・実行(Sonnet)
- Phase 3:検証・提出(Haiku)
こうしておくと、難問で Opusの強み(深い推論) を活かし、Sonnetは 手を動かす役 に寄せられます。
課題3:Human-in-the-Loop(人間介入)が少なすぎた
セッション全体で AskUserQuestion を使ったのは 2回だけでした。
AIが粘って時間を溶かすより、人間に一言聞けば即終わるタスクがあります。
AIが苦手で、人間が強い例
- CAPTCHA
- 画像の目視(ステガノの違和感等)
- 音声の聴き取り
- 直感的な優先順位づけ(候補が複数あるとき)
介入を依頼する判断基準(運用ルール案)
- 同じアプローチで3回以上失敗
- 視覚/聴覚が必要(画像・音声)
- CAPTCHA / 2FA が出た
- 15分経って進展なし
- 候補が複数あって優先順位が決められない
形式的にツールへ寄せるより、状況によっては「画像開いたので文字教えてください」のような直接会話の方が速い場面もありました。
技術メモ:Claude Codeでモデル指定
Task 側でモデル指定できます。
Task(
subagent_type="pwn-exploiter",
prompt="...",
model="opus" # "sonnet", "opus", "haiku"
)
また、.claude/agents/*.md でデフォルトモデル指定も可能です。
name: deep-analyst
model: opus
description: 複雑な問題の深い分析を行う
次回は analyst(Opus) と executor(Sonnet) のペアを用意して、難問で役割分担を明確にする予定です。
その他反省点
大会中時間中(10:00-18:00)は問題を解くことに集中させて、最後に振り返ってWriteUpや行動改善をさせようと思っていたら、振り返りのタイミングでコンテキストのCompactingが走ってしまい、詳細な部分の振り返りをClaude自身にさせることができませんでした。
問題を解くためのプロセスも、メインエージェントによるトリアージと各エージェントへの差配の判断プロセスも、ログに吐き出させるべきでした(厳密にはlogフォルダは用意していたのですが、うまく動作させられず...)
まとめ
- サブエージェント自体は有効だった(午前中30/31まで到達)
- ただし運用面で大きな改善余地あり
-
run_in_backgroundを前提にした非ブロッキング並列化 - 「Opusで設計→Sonnetで実行」などモデルの役割分担
- 人間介入のルール化(詰まったら聞く)
-
同じようにClaude Codeで「複数タスクを並列で回したい」人には、run_in_background 周りが特に効くと思います。
実際にこの改善アイディアが効果を発揮することは、次回のCTF(3月のpicoCTFあたりを狙ってます)をもって証明していきたい所存です。
続報お楽しみに!