Создание трёхмодального механизма знаний для CTF-агентов
Как мы разработали систему извлечения знаний для Librarian: SQLite для точных пейлоадов, ChromaDB для методологии и JSON-индекс для локальных инструментов — объединённые единым шлюзом с автоматическим fallback и кросс-типовым улучшением.
Когда Librarian получает вопрос о tcache stashing, он должен вернуть что-то более полезное, чем то, что знает базовая Claude модель. Модель имеет общее понимание эксплуатации heap — она может описать, что такое tcache, объяснить концепцию атаки unlink stash, указать контуры эксплойта. Но она не знает специфичный idiom pwntools, который использовала ваша команда на прошлой неделе, или точную GDB команду, которая раскрывает состояние free list в версии libc, привязанной к бинарнику challenge. Именно этот разрыв — между общим знанием и действенными деталями — механизм знаний существует, чтобы закрыть.
Почему «просто спросить LLM» недостаточно
Знания базовой модели имеют два режима отказа в контексте CTF.
Первый — это устаревание. CTF-challenges часто включают свежие CVE, обновлённые версии инструментов или техники, задокументированные только в writeups за последний год. Модель с датой обучения такого не знает. Второй — точность. Знание того, что GTFOBins документирует техники privilege escalation nmap, — это не то же самое, что иметь готовый --script=exec incantation для копирования. В ограниченном по времени соревновании разница между «агент знает теорию» и «агент имеет точную команду» может быть разницей между решением и тупиком.
Есть также проблема с бюджетом контекста. Librarian (Claude Haiku) вызывается один раз за challenge и имеет фиксированное окно контекста. Вы не можете встроить весь HackTricks в промпт. Вам нужен целенаправленный поиск: три наиболее релевантных вещи для этого конкретного challenge, доставленные быстро, в формате, на котором агент может сразу действовать.
Трёхмодальная архитектура
База знаний разделяет три фундаментально разных типа поиска на три отдельных хранилища.
Тип A — Мышечная память (SQLite + FTS5)
Тип A — для команд, которые вы хотите скопировать и вставить. База данных (ctf_knowledge.db) содержит две таблицы.
binaries содержит ~2739 структурированных записей из GTFOBins и LOLBAS — по одной строке на бинарник на метод эксплуатации, индексирована по имени, платформе (linux/windows) и функции (shell, sudo, suid, download и т.д.). Они берутся из YAML-файлов GTFOBins и JSON-экспорта LOLBAS. Схема намеренно жёсткая: name, platform, function, code, description. Запрос для nmap + sudo возвращает точную команду, не описание того, что может делать nmap.
tricks — это таблица полнотекстового поиска SQLite FTS5 с ~4155 записями из HackTricks и PayloadsAllTheThings. Pipeline сборки проходит по всем markdown-файлам, извлекает блоки кода с использованием regex и записывает окружающий заголовок как контекст. PayloadsAllTheThings фильтруется: директории Intruder, Wordlists, Files и Images пропускаются (эти ассеты идут в Тип C), а блоки кода длиннее 20 строк отбрасываются — сознательный выбор, чтобы хранить tricks готовыми для копирования, а не превращать таблицу в архив скриптов.
FTS5-запросы работают путём AND-обработки поисковых термов вместе (nmap AND sudo AND shell). Когда FTS5 не срабатывает — что происходит с запросами, содержащими пунктуацию — шлюз переходит на LIKE-поиск по первому слову.
Тип B — Кортекс (ChromaDB + BGE-M3)
Тип B — для методологии, концепций и writeups. Он использует ChromaDB с embeddings BAAI/bge-m3: 1024-мерные векторы, нормализованные, с ускорением Metal (MPS) на Apple Silicon.
Pipeline сборки (crawl_type_b.py) — это веб-краулер, который читает целевые URLs из markdown-конфигурационных файлов, собирает до 500 страниц на сайт и загружает текст в коллекцию methodology. Страницы разбиваются по двойному переводу строки, ограничены 1500 символами на chunk, chunks менее 100 символов отбрасываются. Сырой HTML кэшируется локально, так что перестройка индекса после изменения модели embedding не требует повторного краулинга.
В настоящее время индексировано: блог 0xdf (machine writeups, практические техники эксплуатации) и документация pwntools. Репозиторий ctf-wiki клонируется локально и доступен для загрузки, но требует отдельной обработки. Один момент: trafilatura — основная библиотека извлечения — блокируется docs.pwntools.com, поэтому краулер переходит на urllib для этого домена.
Заметка об embedding-модели: мы обновились с 384-мерной модели на BGE-M3 (1024 измерения) во время разработки. ChromaDB не поддерживает смешанные коллекции по размерности, поэтому обновление требовало удаления и перестройки всей базы данных. Build-скрипт справляется с этим автоматически, но это означает, что каждое обновление embedding-модели — это полная перестройка.
Тип C — Арсенал (JSON-индекс)
Тип C — для локальных файлов: wordlists, web shells и скрипты privilege escalation. Вместо базы данных это плоский JSON-индекс (asset_index.json), соединяющий имена и теги с абсолютными путями файлов.
Что в индексе: SecLists (списки паролей, wordlists директорий, списки имён пользователей, fuzzing-пейлоады), PayloadsAllTheThings web shells (.php, .jsp и другие), и PEASS-ng предкомпилированные бинарники (linpeas.sh, winpeas.bat, winPEASany.exe). Wordlist rockyou.txt предварительно распакована и готова к использованию. Каждая запись содержит категорию, список тегов и абсолютный путь — так что когда Operator нужно запустить ffuf -w <path>, Librarian возвращает путь, не инструкцию по поиску пути.
Инструменты вроде pwntools и ROPgadget явно исключены. Это окружение tools, которые Operator вызывает напрямую; они TypeD — присутствуют в окружении выполнения, но не индексируются здесь. Тип C — для файлов, которые вы передаёте или ссылаетесь, не бинарники, которые вы запускаете.
LibrarianGateway
Шлюз (librarian_gateway.py) — единственный интерфейс ко всем трём типам. Его работа — маршрутизировать запросы и применять логику автоматического fallback и улучшения.
TypeA query:
FTS5 search binaries + tricks
├─ hit → return payloads
└─ miss → fallback: run TypeB semantic search, label as theory (no ready payload)
TypeB query:
ChromaDB semantic search
├─ hit → extract keywords (words >4 chars, first 3) → run TypeA lookup
│ return theory + concrete examples
└─ miss → nothing
TypeC query:
JSON substring/tag filter (name, tags, category)
→ return up to 5 matches with absolute paths
TypeA→TypeB fallback — самый полезный путь на практике. Когда Operator просит Librarian о точной команде, которая не существует в SQLite базе, шлюз не просто возвращает ничего — он говорит «пейлоад не найден, но вот методология», давая Operator достаточно теории, чтобы реконструировать подход с нуля.
TypeB→TypeA enhancement работает в противоположном направлении. После того как семантический поиск возвращает результаты методологии, шлюз извлекает ключевые слова из возвращённого текста и запускает FTS5-поиск, чтобы найти конкретные команды, иллюстрирующие теорию. Это избегает паттерна, где агент понимает концепцию, но должен угадать синтаксис.
Извлечение ключевых слов грубое: берите слова длиннее четырёх символов, выбирайте первые три, запускайте FTS5. Это достаточно часто работает, чтобы быть полезным, но пропускает короткие, но domain-specific термины вроде «ROP», «XSS», «SQL» или имена бинарников вроде nc. Это часть системы, которая больше всего нуждается в улучшении.
Pipeline сборки
Сборка с нуля:
# Type A: parse GTFOBins YAML + LOLBAS JSON + HackTricks MD + PayloadsAllTheThings MD
python3 TypeA/build_db.py
# Type B: crawl configured sites, embed with BGE-M3, store in ChromaDB
python3 TypeB/crawl_type_b.py
# Type C: walk SecLists / PayloadsAllTheThings / PEASS-ng, emit JSON index
python3 TypeC/build_asset_index.py
Тип A собирается за секунды. Тип B — медленный: BGE-M3 запускает вывод для каждого chunk, и краулинг 500-страничного сайта с задержками вежливости 0.5s занимает время. Кэш сырого HTML означает, что после краулинга перестройка индекса векторов из кэша намного быстрее, чем повторный краулинг.
Одно ограничение зависимостей стоит отметить: Python 3.14 ломает ChromaDB 1.5.1 из-за проблемы совместимости Pydantic. Проект требует Python 3.10–3.13.
Что сработало на BearcatCTF
Самый чёткий сигнал был накоплением категорий. К тому времени, когда Operator достиг восьмого cryptography challenge, Librarian имел достаточно индексированного контекста — из предыдущих решений и своих собственных источников — что его брифинг был материально лучше откалиброван, чем то, что он дал для первого challenge. Forensics показал ту же картину: binwalk и foremost появились в ранних ответах Librarian, и к третьему challenge Operator начинал с правильными инструментами, а не открывал их в процессе попытки.
Тип C был эффективен для веб-challenges. Когда Operator нужно было загрузить reverse shell или fuzzy-искать директорию, Librarian возвращал абсолютные пути, а не инструкции по поиску файлов. Снижение трения там небольшое, но реальное в контексте с ограничением по времени.
Слабая точка архитектуры была в pwn. Покрытие Type B для методологии heap-эксплуатации разумное — writeups 0xdf хорошо это покрывают — но покрытие Type A для специфичных pwntools-invocations тонкое. Большинство GTFOBins-записей — для privilege escalation, не для binary exploitation. Operator должен был реконструировать pwntools boilerplate из документации, а не извлекать его из индексированного источника.
Что мы бы изменили
Улучшить покрытие Type B для pwn. Блог 0xdf и документация pwntools — текущие источники. CTF-wiki клонируется локально, но ещё не загружена. Добавление его, вместе с целенаправленным краулингом известных pwn writeup-архивов, улучшило бы покрытие для категорий challenges, где трансляция теории в пейлоад имеет наибольшее значение.
Исправить извлечение ключевых слов. Текущий эвристический подход (слова >4 символов, первые 3) был временным заполнителем, который никогда не был заменён. Минимальное улучшение было бы извлекать известные CTF ключевые слова — номера CVE, имена бинарников, названия техник — перед переходом на эвристику по длине.
Добавить подсказки интеграции TypeD. Когда Librarian возвращает результат методологии, который предполагает специфичный tool invocation (ROPgadget, pwntools, gdb-peda), он должен заметить инструмент и предложить pattern invocation, даже если его нет в индексе. В настоящее время нет связи между результатами Type B теории и TypeD tools в окружении выполнения.
Инвалидация кэша для Type B. Кэш сырого HTML не имеет срока действия. На блоге 0xdf появляются новые writeups; документация pwntools обновляется с новыми релизами. Текущий подход требует вручную удалять кэшированные файлы, чтобы подобрать изменения. TTL или проверка по хешу контента исправили бы это.
Механизм в текущей форме функционален и был положительным на BearcatCTF. Это также явно первая версия. Архитектура правильная — трёхсторонний раздел между немедленными пейлоадами, методологией и локальными ассетами чисто отображается на то, как человеческий CTF-игрок фактически использует разные справочные материалы. Шероховатости находятся в качестве заполнения и извлечения в каждом слое, не в дизайне.