В этой статье мы рассмотрим как на оборудовании стоимостью от 1000$, с полностью локальными LLM и VLM на основе скриншотов и коротких пояснений генерировать грамотно написанные отчеты об уязвимостях самого широкого спектра, начиная от Web/мобилок, заканчивая инфрастурктурными уязвимостями Active Directory, k8s и так далее. Заглянем под капот VLM (Vision Language Model) и рассмотрим различные подходы к описанию уязвимостей.

В мире offensive security — redteam, пентестов и ассессментов — есть одна извечная боль: отчёты. Это главный артефакт за который заказчик платит деньги. Качество услуги и уровень удовлетворённости заказчика во многом зависят от того, насколько грамотно составлен этот документ. Во многих командах отчёты пишут те же специалисты, которые проводят тестирование, и это логично: обнаруживший уязвимость лучше всех знает, как её описать. Проблема в том, что большинство пентестеров не любит заниматься «бумажной» работой и делает это через силу. Они думают на своём техническом, порой сленговом языке, а отчёт должен быть оформлен методологически корректно, понятен как технарям, так и бизнес-аудитории.

Хорошо что мы с вами живем на заре развития LLM. Написание текстов грамотным техническим языком это как раз то что LLM уже делают на очень хорошем уровне, так почему бы отдать часть нелюбимой работы им? Ведь тогда все будут счастливы, пентестеры и редтимеры сосредоточатся на поиске крутых векторов киллчейнов и уязвимостях и не будут страдать от написания отчета, а общие трудозатраты на пентест и уровень боли существенно снизятся. Написание хорошего отчета о пентесте в среднем занимает 20-30% от общих трудозатрат технарей, что, согласитесь, не мало. Поэтому наша амбициозная цель - сократить их до 2-3%, то есть в 10 раз и при этом улучшить их качество.


Итак, что мы хотим в идеале?

— Тратить минимум времени и сил на подготовку отчёта о пентесте: просто скидывать пруфы и промежуточные заметки об уязвимостях, которые наспех записывали в блокнот/Obsidian/CherryTree.

— Получать описания уязвимостей, написанные технически грамотным языком со скришотами в правильных местах и в нужном нам формате.

— Гарантировать, что артефакты отчёта (скриншоты, логи пентеста, описания уязвимостей) не покидают доверенную зону — в идеале, пределы нашего ноутбука/ПК или сервера в нашей локальной сети. Мы понимаем, что хороший отчёт о пентесте — это по сути пошаговая инструкция, как нанести максимальный ущерб заказчику. Почти все NDA запрещают обработку таких данных третьими лицами, поэтому любые облачные решения (включая VDS с GPU) — под запретом. Нам нужно решение на базе локальных LLM.

— Запуск на аппаратной платформе за разумные деньги (от $1000). Не у всех есть возможность собирать RIG или держать фермы видеокарт.

— Стабильная работа на разных платформах — и на PC (CUDA), и на Mac (Metall).

Хорошо, давайте переходить к поиску решения.

В качестве фреймворка для запуска LLM и VLM будем использовать Ollama: он прост в установке, позволяет удобно скачивать и запускать разные модели, эффективно задействует GPU как на macOS (Metal), так и на Windows/Linux с CUDA, а также предоставляет удобный API для интеграции.

Выбор LLM

Для локального запуска остановимся на Gemma 3, 27B от Google.

  • Мультимодальность. Это VLM: умеет принимать текст и изображения. Подходит для описания уязвимостей со скриншотами/диаграммами.

  • Большой контекст. До ~128k токенов — удобно «кормить» длинными логами, выписками из CherryTree/Obsidian и фрагментами отчёта.

  • Работает локально даже на «домашнем» железе. На Mac с чипами Apple M3 c 32 GB Unified Memory помещается в память и может работать без свопа; на Windows/Linux рекомендуется видеокарта с 24Gb VRAM (например -  RTX 3090 24 ГБ VRAM) и минимум 32Gb оперативной памяти.

  • Многоязычность. Поддерживает 140+ языков, в том числе хороший технический русский.

  • Качество. По открытым бенчмаркам и практическим отзывам (на момент подготовки материала, июль 2025) — одна из сильных локальных non-reasoning моделей; если вам критичен сложный многошаговый reasoning, возможно, стоит подобрать специализированную модель.

Изначально в VRAM обычно грузятся веса модели в 16 битах (FP16/BF16). Квантование (quantization) позволяет сжать эти веса до 8 или даже 4 бит обычно без заметной потери качества. Это резко снижает потребление VRAM и  ускоряет инференс — скорость зависит от железа и реализации. Если можете — ставьте 8-битную (INT8) как безопасный дефолт. Если память поджимает — берите 4-битную (INT4), понимая, что ответы могут стать чуть «шумнее».

ONLY TEXT

Для начала рассмотрим только текстовый подход без vision составляющей. Идея состоит в том чтобы на вход LLM дать краткое описание действий по эксплуатации уязвимости, ссылки на скриншоты и попросить эти ссылки не менять, чтобы потом те же скриншоты поставить в нужные места с помощью Obsidian.

Давайте сразу же попробуем описать уязвимость. Скопируем типичный текст из заметок в Obsidian, которые многие пентестеры пишут.

Атакующий зашел на дашборд http://172.66.66.22:34443/

![[Pasted image 20250612183000.png]]
Получил доступ через веб к секретам и SA
![[Pasted image 20250612183050.png]]
смотрим поды с помощью kubectl --server=https://172.66.66.22:34443 --token=$token --insecure-skip-tls-verify get po -A
![[Pasted image 20250612183124.png]]
с помощью kubectl --server=https://172.66.66.22:34443 --token=$token --insecure-skip-tls-verify auth can-i --list 
показывает что мы cluster-admin

таким образом был скомпрометирован кластер 

Возьмем этот текст, придумаем самый простой промпт типа

"На основе данной информации необходимо грамотным техническим языком расписать уязвимость для отчета о пентесте.
Всегда вставляй ссылки скриншоты без изменений— в виде ![[screenshotN.png]]."

Отправляем в ollama

cat text.txt prompt.txt | ollama run gemma3:27b

и полученный текст вставляем в Obsibian, нужные скриншоты он сам подтягивает.
Получаем хорошо описанную уязвимость, пригодную после вычитки для вставки в отчет.

Мы видим что Gemma в правильные места вставила ссылки на картинки (правда уменьшила их количество с 8 до 6) и корректно описала шаги. С помощью промпта можно кастомизировать формат описания уязвимости, добавить/удалить разделы.

ONLY IMAGE

Что хотелось бы улучшить. Часть информации которую мы взяли из Obsidian была избыточной, так как все эти же команды уже были на скриншотах. И кажется что для описания уязвимостей ее достаточно. Стоит напомнить, что мультимодальные варианты Gemma 3 (4B/12B/27B) относятся к классу VLM — vision-language моделей. Она умеет обрабатывать не только текст, но и изображения: картинки через специальный энкодер преобразуются в токены, которые затем вместе с текстовыми токенами поступают на вход модели для дальнейшего анализа.

Для примера возьмем скриншоты уязвимостей из примера шаблона отчета о внутреннем пентесте Hack the box - https://www.hackthebox.com/storage/press/samplereport/sample-penetration-testing-report-template.pdf, например уязвимости "5. Tomcat Manager Weak/Default Credentials"

Скриншоты из демо-отчета Hack-the-box
Скриншоты из демо-отчета Hack-the-box

Сохраним эти 8 изображений на диск и отправим в нашу VLM в правильном порядке вообще без каких-либо подсказок. Только надо подправить промпт, ведь теперь нейросеть должна вставлять ссылки картинки в нужные места, чтобы мы их заменили нужными скриншотами.

{ 
  curl --no-progress-meter http://localhost:11434/api/chat \
    -H "Content-Type: application/json" \
    -N \
    -d @- <<EOF
{
  "model": "gemma3:27b",
  "stream": true,
  "options": { "num_ctx": 16384 },
  "messages": [
    {
      "role": "user",
      "content": "На основе данной информации необходимо грамотным техническим языком расписать уязвимость для отчёта о пентесте. Всегда вставляй скриншоты прямо в текст — ![alt](screenshotN.png)",
      "images": [
        "$(base64 -i tomcat1.png -b 0)",
        "$(base64 -i tomcat2.png -b 0)",
        "$(base64 -i tomcat3.png -b 0)",
        "$(base64 -i tomcat4.png -b 0)",
        "$(base64 -i tomcat5.png -b 0)",
        "$(base64 -i tomcat6.png -b 0)",
        "$(base64 -i tomcat7.png -b 0)",
        "$(base64 -i tomcat8.png -b 0)"
      ]
    }
  ]
}
EOF
} | jq -r -j 'select(.message.content != null) | .message.content'

Полученный текст копируем и вставляем в Obsidian (или в любой другой редактор, поддерживающий Markdown).

Видим что модель правильно распознала все 8 картинок и правильно определила логику эксплуатации уязвимости, осталось вставить правильные скриншоты (tomcat1-8.png) вместо ссылок.

Но, к сожалению не все так гладко, не все картинки хорошо распознаются Gemma3. Возьмем например скриншот экплуатации log4shell в VCenter. По нему каждый пентестер сможет понять (или загуглить), что это за уязвимость, какой импакт, понять где IP атакующего, а где жертвы и описать в эту уязвимость в отчете.

Отправляем картинку с промптом в ollama

ollama run gemma3:27b \
  "На основе данной информации необходимо грамотным техническим языком расписать 
  уязвимость для отчета о пентесте. Обозначь здесь место для вставки скриншота 
  ./screen.png"

Вот что получилось:

Несложно заметить, что Gemma некорректно распознала название эксплоита (logshell вместо log4shell) и придумала для него описание. Сама уязвимость описана максимально расплывчато, без каких-либо подробностей, IP адресов и так далее. Мол прилетел запрос и случилось RCE.

Давайте разберемся почему так происходит. Для обработки визуальной информации Gemma3 разбивает изображения на неперекрывающиеся фрагменты 896×896 пикселей и обрабатывает их отдельно. Это называется алгоритмом Pan and scan.

Далее Gemma кодирует каждый фрагмент через 400-миллионный энкодер SigLIP в векторные токены, упрощающие информацию и при этом удаляющие высокочастотные компоненты (тонкие контуры букв). Каждое такое изображение представляется 256 визуальными токенами что может быть недостаточно для детального понимания текста в терминале. А в пентестерской работе львиная доля скриншотов - текстовые и многие из них содержат большое количество информации, например есть взять запрос и ответ в BurpSuite или консольный вывод многих утилит. Соответственно, если на скриншоте "много" информации, то gemma не всё корректно распознает.

Хорошая новость в том, что Gemma может качественно распознавать (и "понимать") небольшие порции текста в хорошем разрешении. Например если наш скриншот поделить на 2, то Gemma с большой вероятностью сможет качественно описать уязвимость.

Примечание: Существуют способы обработки изображений перед попаданием в Gemma3, которые повышают качество распознавания, о них мы расскажем в следующих статьях.

TEXT+OCR

Давайте посмотрим на другого фаворита в гонках мультимодальных LLM-VLM семейства Qwen-2.5-vl. Которые используют другой подход при работе с изображениями.

Qwen 2.5-VL использует ViT + window attention. Модель берёт любое изображение и делит его на мелкие фрагменты (патчи) размером примерно 14×14 пикселей.

Из этих патчей модель группирует соседние патчи в блоки — окна (обычно по несколько десятков патчей в каждом). Затем каждое окно обрабатывается отдельно (механизм window attention), то есть модель «смотрит» на группу патчей сразу и связывает их между собой, не пытаясь охватить всю картинку разом. Это экономит ресурсы и ускоряет вычисления.

После того как все окна «просканированы», число получившихся векторов (токенов) может быть слишком большим. Модуль MLP Merger автоматически объединяет и сжимает соседние патч-токены, оставляя ровно столько «визуальных признаков», сколько нужно для дальнейшей работы языковой части.

Даже небольшая модель на 7b по рейтингам превосходит GPT4o-mini, что очень хорошо при полностью локальной работе и невысокими требованиями к железу. У данной модели отличные характеристики по Vision составляющей но довольно базовые по текстовому выводу (тут сказывается относительно небольшой размер модели - 7 миллиардов параметров).

Соответственно, почему бы нам не совместить преимущества нескольких моделей. Qwen будет делать распознавание текста (OCR), генерировать краткое описание и вставлять в нужное место в описание уязвимости, которое уходит в Gemma3. Таким образом в контекст Gemma3 уходят заметки пентестера, OCR из скриншотов и также токены с изображения которое получила Vision часть Gemma.

К слову, для задач OCR можно использовать более лёгкую модель Nanonets-OCR-s. Это дообученная версия Qwen-2.5-VL-3B, прошедшая fine-tuning специально под распознавание текста. Она существенно экономнее в плане ресурсов и работает заметно быстрее. Однако стоит учитывать, что на более сложных изображениях модель иногда может «зацикливаться».

Как нам получить OCR скриншота быстро и просто?
Сначала качаем модель

ollama pull qwen2.5vl:7b-q8_0

Затем отправляем скриншот в формате base64, вместе с промптом и параметрами генерации, в REST API Ollama:

curl -N -s -k http://localhost:11434/api/generate \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2.5vl:7b-q8_0",
    "prompt": "Extract all text from screenshot",
    "stream": true,
    "options": {
      "temperature": 0.0,
      "top_k": 40,
      "top_p": 1.0,
      "min_p": 0.1,
      "repeat_penalty": 1.2,
      "repeat_last_n": 256,
      "num_ctx": 4096
    },
    "images": ["'"$(openssl base64 -in ~/Desktop/555.png -A)"'"]
  }' \
| jq -r -j .response

После выполнения запроса мы получаем текст со скриншота, который затем можно добавить в контекст обращения к Gemma 3. В связке с Ollama это позволяет передать Gemma уже распознанное содержимое изображений и на его основе получить полноценное описание уязвимости. Таким образом, «слеповатая» Gemma с помощью Qwen смогла корректно интерпретировать скриншоты и выдать осмысленную аналитику.

Однако вместе с этим возникла новая проблема — процесс оказался довольно громоздким. Нужно загружать изображения в правильном порядке, вставлять ссылки на них, копировать и вставлять OCR-текст, а затем следить, чтобы финальный результат собирался без лишних скринов. То есть часть рутины удалось снять, но мы породили другую — подготовка данных по-прежнему требует много малоприятной ручной работы.

Лучше напиши Порфирию

Чтобы избавиться от лишней рутины и описывать уязвимость за несколько минут я разработал инструмент Porfiry (https://github.com/wadim1281/porfiry). Он решает проблему ручной подготовки данных и позволяет полностью сосредоточиться на анализе уязвимости.

Рабочий процесс в Porfiry выглядит так:

  1. Загружаете скриншоты и расставляете их в нужном порядке.

  2. Для того чтобы передать ссылку на скриншот в контекст необходимо нажать на карандаш(✏️)

  3. В том же окне при необходимости можно запустить OCR (?) — текст вставится прямо в черновик.

  4. При желании добавляете пояснения, чтобы LLM точнее интерпретировала уязвимость.

  5. Нажимаете кнопку Generate! — и Porfiry собирает корректный запрос и отправляет его в локальную LLM.

Иногда при распознавании текста (OCR) модель может уйти в повтор или зависнуть. В таких случаях есть два проверенных подхода:

  1. Разделить изображение на части. Разбейте скриншот на несколько логических фрагментов и подавайте их поочерёдно.

  2. Передать в более мощную модель. Если Nanonets-3B или Qwen-2.5-VL-7B не справляются, имеет смысл попробовать Qwen-2.5-VL-32B. Даже в 4-битной квантовке она потребует около 21 GB VRAM, но при этом заметно устойчивее ведёт себя на сложных случаях.

Когда все данные собраны, они передаются в Gemma. Первые строки ответа появляются в стриме уже через несколько секунд, а полная генерация обычно занимает 1–2 минуты. При этом все ссылки на скриншоты автоматически подставляются в виде изображений в base64.

В разделе OUTPUT вы получаете красиво отформатированный Markdown-отчёт со встроенными скриншотами. Его можно сразу экспортировать в один .md-файл и использовать для дальнейшей работы или преобразовать в другие форматы.

Редактирование и уточнения

Если отчёт в целом устраивает, но хочется доработать отдельные моменты — например, переписать раздел рекомендаций, более подробно расписать какой-то аспект или перевести весь документ на другой язык — для этого под кнопкой Generate! есть поле «Ask for something».

В это поле можно вписать свои пожелания. Текст уйдёт вместе с основным контекстом, и отчёт будет автоматически перегенерирован с учётом этих комментариев. Это делает процесс гибким: Porfiry не только снимает рутину подготовки данных, но и позволяет быстро редактировать результат без ручного переписывания.

Также предусмотрен механизм сохранения промежуточных результатов во внутренней БД. Например, если текущий вариант неплох, но есть идея его улучшить, то можно сохранить текущую версию и при необходимости восстановить в дальнейшем. Описали уязвимость, сохранили файл md на диск и описываем следующую.

Генерируем раздел "Killchain"

Помимо описания уязвимостей, в техническом разделе отчёта есть отдельный блок «Killchain» (или «Compromise Walkthrough», «Attack paths» «Сценарий атак»), представляющий собой полный, пошаговый перечень действий атакующего — от первоначального этапа до достижения финальных целей — с иллюстрирующими скриншотами каждого шага.

В данном случае грузить картинки смысла нет, потому что их обычно очень много (от 10 штук) в этом разделе, и VLM может не справится с таким объемом.
Вместо этого мы можем воспользоваться подходом "text only", кратко расписать шаги и вставить нужные скриншоты в Obsidian.
Например:

на сервисе helpdesk - helpdesk.victimcorp.com сбрутили юзера Administrator
![админка](Pasted%20image%2020250714203802.png)

в разделе «Параметры - Общие параметры - Настраиваемые расписания» можно настроить расписания так чтобы он выполнялись из под учетки nt authority/system
![](Pasted%20image%2020250714204012.png)

Таким образом залили шелл shell.jsp
![выполнение whoami из шелла](Pasted%20image%2020250714204128.png)

потом загрузили туда procdump, сняли дамп lsass и отправили в мимикатс и помощью logonpasswords получили кучу паролей в открытом виде от доменных юзеров.
![пароли в откром виде](Pasted%20image%2020250714204320.png)

один из юзеров - victim\kaspersky входит в группу victim\server_admins

![скрин с выполнением команды net user kaspersky /domain](Pasted%20image%2020250714204640.png)

также на периметре нашли прокси proxy.victimcorp.com:8080, через который запивотились во внутреннюю сеть

Делаем copy/paste, загружаем в Porfiry и получаем полностью описаный раздел с Killchain. Вставляем обратно в Obsidian и получаем описанный раздел со сценариями атак. Также сохраняем его в Markdown файле.

Генерируем остальные разделы отчета

Теперь у нас есть все описанные уязвимости, есть Killchain. Самое время писать разделы типа "Executive Summary", "Заключение" и прочие общие рекомендации. Писать сами мы конечно же не будем, запустим porfiry скормим ему все markdown файлы c описанными уязвимостями и киллчейном. На основе этих данных, с правильным промптом мы сгенерируем все остальные разделы.

Перед отправкой данных в LLM porfiry вычистит все base64 изображения, так как они не несут никакой смысловой нагрузки и очень сильно "загрязняют" контекст. После отправки мы получим описанный раздел, в формате Markdown, который можно сохранить на диск.

Также можно получить раздел со статистикой по найденным уязвимостям, загрузив все Markdown файлы с уязвимостями.

Про аппаратные потребности

Данное решение работает и на CUDA (NVIDIA на x86_64) и Apple Metal (M1–M4 на ARM). Соответственно потребности такие же как и у самой требовательной LLM, которая запускается на ollama, в нашем случае - Gemma3 27b.

В таблице написаны примеры минимальной конфигурации

Бэкенд

Платформа

Доступная VRAM/UMA

CUDA

NVIDIA GeForce RTX 3090

24 GB GDDR6X  

MPS

чипом > M3 или > M2pro и 32 GB Unified Memory

≈24 GB (75% UMA)  

В данном случае определяющее значение играет объем видеопамяти VRAM. Для загрузки модели gemma3 на 27 миллиардов параметров в 4-битной кварнтировке требуется 17 GB VRAM плюс KV кэш 2-3 Гб.

С оперативной памятью (RAM) отдельная история. В CUDA архитектуре системная RAM служит буфером для данных, которые CPU подготавливает и копирует в видеопамять через PCIe: если её будет недостаточно, начнёт происходить страничный свопинг или ошибки OOM при загрузке больших батчей.

По общепринятому правилу для Deep Learning и инференса LLM системная RAM должна быть не меньше, а лучше на 25 % больше, чем объём VRAM. Кроме того, рекомендуют закладывать 4 CPU-ядра на каждую GPU и иметь пропускную способность памяти не ниже 200 GB/s, чтобы не становиться узким местом при загрузке данных на GPU.

На Apple Silicon CPU и GPU физически разделяют один пул памяти — Unified Memory Architecture (UMA). Metal позволяет скрыть границу между host и device-памятью, но под капотом создаёт разные виды ресурсов. По умолчанию macOS позволяет для GPU использовать до ≈ 75 % Unified Memory.

Когда вы запускаете OCR на VLM, а затем сразу включаете LLM для генерации отчёта, обе модели на короткое время оказываются в памяти. По умолчанию Ollama держит загруженную модель ~5 минут бездействия (keep-alive), прежде чем выгрузить её. На ПК с ограниченной VRAM это легко приводит к свопу/фризам. Решений два: правильно настроить keep-alive при запуске

ollama run <vlm_model> --keepalive 0 

или вручную останавливать модель перед следующим шагом

ollama stop <vlm_model>

По спецификации Ollama keep-alive задаётся переменной окружения OLLAMA_KEEP_ALIVE: 0 — выгружать сразу; отрицательное (например, -1) — держать бесконечно; положительное/с суффиксами(30s, 5m, 24h) — держать указанное время. 

Для оперативного контроля за ресурсами удобнее всего использовать nvtop: по её графикам видно, хватает ли видеопамяти для LLM и что именно в текущий момент нагружает GPU.

На временной шкале это выглядит так. Сначала при запуске задания для Gemma 3 на несколько секунд заметно растёт потребление видеопамяти — в этот момент модель загружается в VRAM. Затем, когда начинается инференс, загрузка GPU поднимается до максимума и держится на пике, а объём занятой VRAM остаётся на стабильном плато вплоть до завершения работы модели.

После окончания генерации отчёта загрузка GPU возвращается к исходным значениям, однако объём занятой видеопамяти не снижается.

Заключение

В первой части цикла мы показали, как превратить набор скриншотов, заметок и коротких подсказок в полноценно оформленное описание уязвимости для отчёта о тестировании на проникновение. Далее поверх этих описаний мы добавим раздел с цепочкой атаки (Killchain). И на его основе уже можно формировать остальные разделы отчёта.

В следующих материалах разберём более продвинутые техники описания уязвимостей и всего отчёта в целом, а также посмотрим на другие полезные AI-подходы и трюки. Наша цель неизменна: снизить трудозатраты, повысить качество и избавить от боли рутинного написания отчётов.

Комментарии (0)