
AI-ассистируемый реверс-инжиниринг с помощью Ghidra
Rev·Deck — это локальная однопользовательская рабочая станция статического анализа. Она объединяет веб-интерфейс с приоритетом фактов и LLM-копайлота поверх бинарного файла, анализируемого headless-сервисом Ghidra: просматривайте детерминированные факты (функции, строки, импорты, перекрёстные ссылки, ограниченный граф вызовов) напрямую или задавайте ассистенту ограниченные вопросы, фактические утверждения в ответах которых должны ссылаться на проверяемые данные.
Анализируемые бинарные файлы никогда не исполняются. Браузер взаимодействует только с этим Flask-приложением; приложение проксирует проверенные типизированные запросы к сервису Ghidra.
https://github.com/user-attachments/assets/fba14dc5-7ad5-4137-9349-ed824da64fbe
cp .env.example .env # set API_BASE and MODEL_NAME; set API_KEY if required
docker compose up --build
Docker Compose автоматически читает .env для подстановки значений. Он завершается ошибкой до запуска, если API_BASE или MODEL_NAME отсутствуют; API_KEY=not-used остаётся допустимым для локальных провайдеров без ключа. Стек запускает оба сервиса. Откройте http://127.0.0.1:5000.
Чтобы запустить только сервис Ghidra:
docker pull biniamfd/ghidra-headless-rest:latest # ensure the newest image
docker run --rm \
-p 127.0.0.1:9090:9090 \
-v "$(pwd)/data:/data/ghidra_projects" \
--security-opt no-new-privileges:true \
biniamfd/ghidra-headless-rest:latest
Для воспроизводимой фиксации версии используйте протестированный дайджест релиза вместо latest:
docker run --rm \
-p 127.0.0.1:9090:9090 \
-v "$(pwd)/data:/data/ghidra_projects" \
--security-opt no-new-privileges:true \
biniamfd/ghidra-headless-rest:1.2.1@sha256:971591a3a8448d8ed969079b452306e806f36079c3ddd298f4a618d6e2f1442d
biniamfd/ghidra-headless-rest:latest.Скопируйте .env.example в .env и заполните их; полный список и значения по умолчанию см. в этом файле.
Rev·Deck взаимодействует с любой OpenAI-совместимой конечной точкой Chat Completions через OpenAI SDK, настраиваемый исключительно через API_BASE / API_KEY / MODEL_NAME. Здесь нет специфичной для провайдера логики заголовков, параметров или моделей: локальный сервер Ollama (API_BASE=http://127.0.0.1:11434/v1), самостоятельно размещённая конечная точка vLLM/llama.cpp/LM Studio, сам OpenAI или шлюз вроде OpenRouter — всё работает одинаково.
Примеры настроек провайдера в .env (используйте заглушки, никогда не коммитьте реальные ключи):
# Ollama
API_BASE=http://127.0.0.1:11434/v1
API_KEY=not-used
MODEL_NAME=qwen3:8b
# OpenRouter
API_BASE=https://openrouter.ai/api/v1
API_KEY=replace-with-your-key
MODEL_NAME=anthropic/claude-opus-4.8
# OpenAI
API_BASE=https://api.openai.com/v1
API_KEY=replace-with-your-key
MODEL_NAME=replace-with-a-supported-model-id
# LM Studio, vLLM, or llama.cpp (adjust port/model to the server)
API_BASE=http://127.0.0.1:1234/v1
API_KEY=not-used
MODEL_NAME=replace-with-the-served-model-id
По умолчанию (LLM_STREAM=auto) ассистент запрашивает потоковый ответ и передаёт токены в браузер по мере их поступления. Потоковая передача также даёт более сильную гарантию отмены: когда вы останавливаете ответ (или закрываете вкладку), Rev·Deck немедленно закрывает нижележащий поток провайдера и не выполняет дальнейших раундов инструментов или модели, поэтому вышестоящая генерация прерывается, а не продолжает выполняться в фоне до завершения.
Предостережения:
auto, если провайдер отклоняет потоковый запрос с ошибкой совместимости (HTTP 400/404/405/422) до вывода какого-либо содержимого или вызовов инструментов, Rev·Deck один раз выполняет откат к блокирующему вызову и запоминает это на всё время работы процесса. Ошибки аутентификации (401/403), ограничения частоты запросов (429) и серверные ошибки (5xx) не рассматриваются как проблемы совместимости и выводятся как ошибки, а не молча повторяются. Установите LLM_STREAM=false, чтобы полностью отключить потоковую передачу, или LLM_STREAM=true, чтобы сделать её обязательной (без отката).Откройте приложение и загрузите бинарный файл, чтобы начать задачу анализа. Если содержимое явно является обычным текстом, приложение запросит подтверждение перед отправкой в Ghidra; используйте явное переопределение raw-binary только тогда, когда содержимое намеренно является прошивкой/данными, а не исполняемым форматом. После завершения анализа переключайтесь между двумя вкладками рабочей области:
Оба режима используют бюджет шагов на задачу, а также опцию No step limit (без ограничения числа шагов), которая выполняет задачу до завершения (по-прежнему ограниченную MAX_STEP_BUDGET, чтобы зациклившаяся модель не могла выйти из-под контроля). Если запуск достигает своего бюджета, он сообщает частичные результаты и предлагает Continue (продолжить) — эта опция возобновляет тот же разговор, используя уже полученные факты, без повторного выполнения завершённых вызовов инструментов. Стоимость растёт с числом вызовов инструментов/модели, поэтому более высокий бюджет стоит дороже.
Доступные рабочие процессы:
Каждая задача анализа имеет основной чат Main плюс опциональные сфокусированные под-потоки. Выберите New sub-investigation (новое под-исследование), введите однострочное краткое описание и работайте в новом контексте разговора над тем же бинарным файлом и с теми же инструментами только для чтения. Истории потоков остаются изолированными, и в любой момент времени потоковая передача активна только для одного потока.
Когда сфокусированная работа готова, выберите Return conclusion to parent (вернуть вывод в родительский чат). Rev·Deck выполняет один ограниченный вызов модели только по этому под-потоку, проверяет его ссылки на факты и добавляет в родительский чат одну карточку вывода с отметкой о происхождении (provenance). Полная ветка остаётся доступной для повторного открытия, а контекст родительского чата получает только компактный вывод, а не транскрипт ветки. Возвращённая карточка без проверенных ссылок явно помечается как непроверенная.
Ответы ассистента ссылаются на факты прямо в тексте в виде [function:0xADDR], [string:0xADDR] или [import:name]. Ссылки проверяются по тому, что было фактически получено за этот ход (turn); ссылка, которая не совпадает, помечается как «(непроверено)» и должна рассматриваться как неподтверждённое утверждение, а не факт.
Диаграммы Mermaid в выводе ассистента (например, наброски графа вызовов) отображаются в изолированном (sandboxed) фрейме без доступа к внешней сети.
Браузер взаимодействует только с веб-приложением Rev·Deck. Rev·Deck координирует настроенную LLM и headless-сервис Ghidra, а затем представляет полученные факты и активность агента в единой рабочей области.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
npm ci && npm run vendor # one-time: vendors the pinned Mermaid runtime
cp .env.example .env # edit API_BASE / MODEL_NAME / API_KEY
set -a; source .env; set +a # plain Python does not load .env automatically
# Start the separate Ghidra service, then:
python webui/app.py
Откройте http://127.0.0.1:5000. Docker Compose читает .env автоматически; при запуске из исходников требуется экспортировать его, как показано выше. Flask-сервер разработки подходит для локального использования; Docker-образ запускает Gunicorn.
Это решение рассчитано на одного доверенного аналитика на его собственной машине, а не на многопользовательский или публичный хостинг. По умолчанию приложение и сервис Ghidra привязываются только к 127.0.0.1, режим отладки выключен, загруженные бинарные файлы никогда не исполняются, а ключ LLM-провайдера остаётся на стороне сервера.
pip install -r requirements.txt -r requirements-dev.txt
python -m pytest
node --test "webui/static/js/tests/**/*.test.mjs"
npm ci && npm run vendor:verify # verifies the vendored Mermaid bundle's integrity
/readyz возвращает 503 — сервис Ghidra недоступен по адресу GHIDRA_API_BASE, либо не заданы API_BASE/MODEL_NAME.API_BASE/API_KEY/MODEL_NAME и доступность провайдера в пределах LLM_TIMEOUT.MAX_UPLOAD_BYTES.ANALYSIS_TIMEOUT в контейнере Ghidra (например, 5400 для C++/Android-бинарников с 10k+ функциями) и загрузите файл заново. к этому не относится.| Переменная | По умолчанию | Значение |
|---|
API_BASE | обязательна | OpenAI-совместимый базовый URL (http/https). Compose завершается с ошибкой на раннем этапе, если переменная отсутствует. |
API_KEY | not-used | Ключ провайдера. Никогда не логируется и не отправляется в браузер; not-used допустим для локальных провайдеров без ключа. |
MODEL_NAME | обязательна | Идентификатор модели, ожидаемый настроенной конечной точкой. Compose завершается с ошибкой на раннем этапе, если переменная отсутствует. |
LLM_STREAM | auto | Режим потоковой передачи: auto (потоковая передача с однократным откатом к блокирующему режиму при ошибке совместимости до вывода), true (всегда потоковая передача), false (всегда блокирующий режим). |
GHIDRA_API_BASE | http://127.0.0.1:9090 | Базовый URL сервиса Ghidra. |
GHIDRA_IMAGE | biniamfd/ghidra-headless-rest:1.2.1@sha256:971591a3... | Протестированный релиз, зафиксированный неизменяемым дайджестом. :latest также разрешается в этот дайджест; переопределите, чтобы зафиксировать другой релиз. |
HOST / PORT | 127.0.0.1 / 5000 | Привязка dev-сервера. |
MAX_UPLOAD_BYTES | 104857600 | Ограничение размера загрузки. |
CHATS_DIR | webui/chats | Каталог истории чатов. |
| Рабочий процесс | Назначение | Требуется адрес целевой функции |
|---|
program_triage | Обобщает вероятное назначение программы на основе метаданных, импортов, строк и функций. | Нет |
suspicious_behavior | Сначала выявляет детерминированные индикаторы, затем — ограниченные, чётко обозначенные гипотезы. | Нет |
selected_function | Декомпилирует функцию и объясняет её вместе с вызывающими/вызываемыми функциями. | Да |
call_chain | Исследует одну ограниченную окрестность нативного/синтезированного графа вызовов, начиная с заданной функции. | Да |
attack_surface_triage | Читает покрытие/top-K детерминированных оценок, затем детально изучает не более трёх кандидатов; оценки — это приоритеты, а не вердикты. | Нет |
vulnerability_hypothesis | Выбирает одного ограниченного кандидата и представляет факты, контраргументы и открытые вопросы; никогда не подтверждает автоматически. | Нет |
LLM_TIMEOUT