CTFエージェントのための三モーダル知識エンジンの構築
LibrarianのCTF知識検索システムをどのように構築したか:精密なペイロード用のSQLite、方法論用のChromaDB、ローカルツール用のJSONインデックス——自動フォールバックとクロスタイプ強化を持つ単一ゲートウェイで統合。
Librarianにtcache stashingについて尋ねると、ベースのClaudeモデルが知っている以上に有用なものを返す必要がある。モデルはヒープエクスプロイテーションの一般的な理解を持っている——tcacheとは何かを説明し、stash unlink攻撃の概念を解説し、エクスプロイトの形を描くことができる。しかし、チームメイトが先週使った特定のpwntoolsのイディオムや、チャレンジバイナリに固定されたlibcのバージョンでフリーリストの状態を明らかにする正確なGDBコマンドは知らない。このギャップ——一般的な知識と実行可能な具体性の間——が知識エンジンが埋めようとしているものだ。
なぜ「LLMに直接聞く」では不十分か
ベースモデルにはCTFコンテキストにおける2つの失敗モードがある。
第一は鮮度の問題。CTFの問題はしばしば最近のCVE、更新されたツールバージョン、または過去1年のライトアップでしか文書化されていないテクニックを含む。トレーニングカットオフを持つモデルはこれらを知らない。第二は精度の問題。GTFOBinsがnmapの権限昇格テクニックを文書化していると知ることは、貼り付け可能な正確な--script=execの呪文を持つこととは異なる。時間制限のある競技では、「エージェントが理論を知っている」と「エージェントが正確なコマンドを持っている」の差が、解答とデッドエンドの差になりうる。
コンテキスト予算の問題もある。Librarian(Claude Haiku)は1問につき1回呼ばれ、固定のコンテキストウィンドウを持つ。HackTricks全体をプロンプトに埋め込むことはできない。ターゲットを絞った検索が必要だ:この特定の問題に対して最も関連性の高い3つのこと、素早く、エージェントがすぐに行動できる形式で。
三モーダルアーキテクチャ
知識ベースは根本的に異なる3種類の検索を3つの独立したストアに分離する。
タイプA——筋肉記憶(SQLite + FTS5)
タイプAはコピー&ペーストしたいコマンド向け。データベース(ctf_knowledge.db)には2つのテーブルがある。
binariesはGTFOBinsとLOLBASからの約2,739件の構造化レコードを保持——バイナリごと、エクスプロイトメソッドごとに1行、名前、プラットフォーム(linux/windows)、機能(shell、sudo、suid、ダウンロードなど)でインデックス付け。GTFOBinsのYAMLファイルとLOLBASのJSONエクスポートから作成。スキーマは意図的に厳格:name、platform、function、code、description。nmap + sudoのクエリはnmapが何をできるかの説明ではなく、正確なコマンドを返す。
tricksはHackTricksとPayloadsAllTheThingsからの約4,155件のレコードを持つSQLite FTS5全文検索テーブル。ビルドパイプラインはすべてのMarkdownファイルを走査し、正規表現でコードブロックを抽出し、周辺のヘッダーをコンテキストとして記録する。PayloadsAllTheThingsはフィルタリング:Intruder、Wordlists、Files、Imagesディレクトリはスキップし(これらのアセットはタイプCへ)、20行を超えるコードブロックは削除——テーブルをスクリプトアーカイブにするのではなく、コピー&ペーストで使えるtricksにするための意図的な選択。
FTS5クエリは検索語をANDで繋ぐ(nmap AND sudo AND shell)。FTS5が失敗した場合——句読点を含むクエリで発生する——ゲートウェイは最初の語のLIKE検索にフォールバックする。
タイプB——大脳皮質(ChromaDB + BGE-M3)
タイプBは方法論、概念、ライトアップ向け。BAAI/bge-m3エンベディングを使ったChromaDBを使用:1024次元ベクトル、正規化、Apple SiliconではMetal(MPS)加速。
ビルドパイプライン(crawl_type_b.py)はMarkdown設定ファイルからターゲットURLを読み込み、サイトごとに最大500ページをクロールし、テキストをmethodologyコレクションに取り込むウェブクローラー。ページは二重改行で分割され、1チャンク最大1,500文字、100文字未満のチャンクは廃棄される。生のHTMLはローカルにキャッシュされるため、エンベディングモデルを変更してインデックスを再構築する際に再クロールは不要。
現在インデックス済み:0xdfのブログ(マシンライトアップ、実践的なエクスプロイテーションテクニック)とpwntoolsドキュメント。ctf-wikiリポジトリはローカルにクローン済みで取り込み可能だが、別途処理が必要。一つ問題がある:主要な抽出ライブラリのtrafilaturaがdocs.pwntools.comにブロックされるため、クローラーはそのドメインではurllibにフォールバックする。
エンベディングモデルについての補足:構築中に384次元モデルからBGE-M3(1024次元)にアップグレードした。ChromaDBは混合次元コレクションをサポートしないため、アップグレードにはデータベース全体の削除と再構築が必要だった。ビルドスクリプトはこれを自動的に処理するが、エンベディングモデルのアップグレードのたびに完全な再構築が必要になることを意味する。
タイプC——兵器庫(JSONインデックス)
タイプCはローカルファイル向け:ワードリスト、Webシェル、権限昇格スクリプト。データベースではなく、名前とタグを絶対ファイルパスにマッピングするフラットなJSONインデックス(asset_index.json)。
インデックスの内容:SecLists(パスワードリスト、ディレクトリワードリスト、ユーザー名リスト、ファジングペイロード)、PayloadsAllTheThingsのWebシェル(.php、.jspなど)、PEASS-ngのプリコンパイル済みバイナリ(linpeas.sh、winpeas.bat、winPEASany.exe)。rockyou.txtワードリストは事前解凍済みで即使用可能。各エントリにはカテゴリ、タグリスト、絶対パスが含まれる——だからOperatorがffuf -w <path>を実行する必要があるとき、Librarianはパスを見つけるための指示ではなく、パスそのものを返す。
pwntoolsやROPgadgetなどのツールは明示的に除外。これらはOperatorが直接呼び出す環境ツールであり、タイプD——実行環境に存在するがここではインデックスされない。タイプCは転送または参照するファイル向けであり、実行するバイナリ向けではない。
LibrarianGateway
ゲートウェイ(librarian_gateway.py)は3つのタイプへの単一インターフェース。クエリのルーティングと自動フォールバック・強化ロジックの適用が役割。
タイプAクエリ:
FTS5でbinaries + tricksを検索
├─ ヒット → ペイロードを返す
└─ ミス → フォールバック:タイプBのセマンティック検索を実行、理論としてラベル付け(即使用可能なペイロードなし)
タイプBクエリ:
ChromaDBセマンティック検索
├─ ヒット → キーワード抽出(4文字超の語、最初の3つ)→ タイプAルックアップを実行
│ 理論 + 具体例を返す
└─ ミス → 何もなし
タイプCクエリ:
JSON部分文字列/タグフィルター(名前、タグ、カテゴリ)
→ 絶対パス付きで最大5件のマッチを返す
実際にはタイプA→タイプBのフォールバックが最も有用なパスだ。OperatorがLibrarianにSQLiteデータベースに存在しない精確なコマンドを求めると、ゲートウェイはただ何も返さないのではなく——「ペイロードは見つかりませんでしたが、方法論を紹介します」と言い、Operatorがゼロからアプローチを再構築するのに十分な理論を提供する。
タイプB→タイプA強化は逆方向に機能する。セマンティック検索が方法論の結果を返した後、ゲートウェイは返されたテキストからキーワードを抽出し、その理論を説明する具体的なコマンドを見つけるためにFTS5ルックアップを実行する。これにより、エージェントが概念を理解しているが構文を推測しなければならないパターンを回避できる。
キーワード抽出は粗雑:4文字より長い語を取り、最初の3つを選び、FTS5を実行。これは十分に機能するが、「ROP」「XSS」「SQL」などの短いが分野固有の用語やncなどのバイナリ名を見逃す。システムの中で最も改善が必要な部分だ。
ビルドパイプライン
ゼロから構築する場合:
# タイプA:GTFOBins YAML + LOLBAS JSON + HackTricks MD + PayloadsAllTheThings MDをパース
python3 TypeA/build_db.py
# タイプB:設定されたサイトをクロール、BGE-M3でエンベッド、ChromaDBに保存
python3 TypeB/crawl_type_b.py
# タイプC:SecLists / PayloadsAllTheThings / PEASS-ngを走査、JSONインデックスを生成
python3 TypeC/build_asset_index.py
タイプAのビルドは数秒。タイプBが最も遅い:BGE-M3はすべてのチャンクに対して推論を実行し、0.5秒の礼儀遅延で500ページのサイトをクロールするのに時間がかかる。生のHTMLキャッシュにより、一度クロールしてしまえば、キャッシュからベクターインデックスを再構築するのは再クロールよりはるかに速い。
注目すべき依存関係の制約:Python 3.14はPydanticの互換性問題によりChromaDB 1.5.1を壊す。このプロジェクトはPython 3.10–3.13が必要。
BearcatCTFでの成果
最も明確なシグナルはカテゴリの蓄積効果だった。Operatorが8問目の暗号問題に達したとき、Librarianは十分なインデックス済みコンテキストを持っていた——過去の解答とその独自ソースから——そのブリーフィングは最初の問題に対して与えたものより実質的に改善されていた。フォレンジクスも同じパターンを示した:binwalkとforemostが初期のLibrarianレスポンスに登場し、3問目までにOperatorは途中で発見するのではなく、最初から正しいツールで始めるようになった。
タイプCはWeb問題で効果的だった。Operatorがリバースシェルをアップロードしたりディレクトリをファジングする必要があるとき、Librarianはファイルを見つけるための指示ではなく絶対パスを返した。時間制限のある環境でのこの摩擦軽減は小さいが実質的なものだ。
アーキテクチャの弱点はpwnだった。タイプBのヒープエクスプロイテーション方法論のカバレッジは合理的——0xdfのライトアップがカバーしている——が、タイプAの特定のpwntools呼び出しのカバレッジは薄い。GTFOBinsのほとんどのエントリは権限昇格向けで、バイナリエクスプロイテーション向けではない。Operatorはインデックスされたソースから取得するのではなく、ドキュメントからpwntoolsのボイラープレートを再構築しなければならなかった。
変更すること
タイプBのpwnカバレッジを改善する。 現在のソースは0xdfブログとpwntoolsドキュメント。CTF-wikiはローカルにクローン済みだがまだ取り込まれていない。それを追加し、有名なpwnライトアップアーカイブのターゲットクロールを加えれば、理論からペイロードへの変換が最も重要なチャレンジカテゴリのカバレッジが改善される。
キーワード抽出を修正する。 現在のヒューリスティック(4文字超の語、最初の3つ)は一度も置き換えられなかったプレースホルダーだ。最小限の改善は、長さヒューリスティックにフォールバックする前に既知のCTFキーワード——CVE番号、バイナリ名、テクニック名——を抽出することだ。
タイプD統合ヒントを追加する。 LibrarianがROPgadget、pwntools、gdb-pedaなど特定のツール呼び出しを示唆する方法論の結果を返すとき、インデックスになくても、ツールを記載して呼び出しパターンを提案すべきだ。現在、タイプBの理論結果と実行環境のタイプDツールの間に関連性がない。
タイプBのキャッシュ無効化。 生のHTMLキャッシュには有効期限がない。0xdfのブログは新しいライトアップを公開し、pwntoolsドキュメントは新リリースで更新される。現在のアプローチでは変更を取り込むためにキャッシュされたファイルを手動で削除する必要がある。TTLまたはコンテンツハッシュチェックでこれを修正できる。
現在の形のエンジンは機能的であり、BearcatCTFではネットポジティブだった。また明らかに最初のバージョンだ。アーキテクチャは正しい——即時ペイロード、方法論、ローカルアセットの3方向分割は、人間のCTFプレイヤーが実際に異なる参考資料を使う方法にきれいに対応している。粗削りな部分は設計ではなく、各レイヤー内のデータ充填と検索品質にある。