• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Как написать мощные плагины для Metasploit

petrinh1988

X-pert
Эксперт
Регистрация
27.02.2024
Сообщения
243
Реакции
493
Автор petrinh1988
Источник https://xss.pro

Всем привет!

Используйте материал статьи только в белых праведных целях, не совершайте преступлений.

Мы уже разрабатывали модули для Метасплоита, но то касалось чекеров и эксплоитов. Плагины же, нацелены на изменение самого фреймворка или его поведения, влияния на пользовательский опыт. Под этими общими словами, на самом деле скрывается довольно широкий спектр вариантов. Например, в этой статье мы сделаем следующие плагины:

  1. Плагин, получающий данные из Acunetix или любой другой системы сканирования через API. Пример прекрасно адаптируется под любую систему. Хоть под Invicti Professional, хоть под Nessus, хоть подо что угодно. Ну, за исключением пагинации… но об этом позже.
    Плагин позволит получить таргеты, уязвимости и автоматически подобрать эксплоит. В одной из вариаций, подбор эксплоита отдадим нейросети, чтобы повысить точность.
  2. Обратная тема. Если не удалось выполнить эксплоит, почему бы не передать сканеру на дополнительный чек. В этой части запустим сканирование в окуне и nmap. Она больше концептуальная, чтобы навести на интересные мысли по выстраиванию многошаговой автоматизации.
  3. В последнем плагине поработаем с моделью состояний Metasploit. Напишем плагин, который будет отправлять уведомление о успешно созданной сессии в телеграм. Это может быть крайне полезным, когда мы ждем отработку пэйлоада с реверс шеллом и когда эта отработка произойдет мы не знаем.

Попутно зацепим полезные темы, например, как сделать так чтобы плагин подгружался автоматически с msfconsole. Сомнительная история, когда каждый раз нужно руками подгружать плагин.

К статье предлагаю относиться, как к исследованию и источнику идей. Да, некоторые плагины можно брать и сразу использовать. Идеи из некоторых нужно адаптировать. К слову сказать, слегка доработав первый плагин, я смог получить 117 живых сессий из давно отработанной базы таргетов.

Код написан на Rub, но достаточно знаний любого другого языка чтобы понять код. Не понятные моменты можно уточнить у AI. И да простят меня профи Ruby. Если у вас пойдет кровь из глаз я не виноват. Ну не мой это язык… часть кода написана мной, часть нейросетями… хоть это та еще пытка и зачастую тупо сжирает много часов на попытки заставить что-то работать, после чего приходилось самому разбираться)))

Важная оговорка про пути​


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

Код:
~/.msf4/plugins/
/usr/share/metasploit-framework/plugins/

Если выскочила подобная ошибка:

Код:
[-] Failed to load plugin from /usr/share/metasploit-framework/plugins/test: Plugin is not a module
/usr/share/metasploit-framework/lib/msf/core/plugin.rb:21: previous definition of Plugin was here

Скорее всего, у пользователя под которым запущен msfconsole нет прав на чтение файла. Либо перезапустите консоль с sudo, либо переместите файл плагина в локальную .msf4. К слову, если консоль запущена с sudo, локальная папка будет /root/.msf4.

Загрузка данных из сканеров​


Начнем с Acunetix. Как взаимодействовать с его API я уже писал, поэтому на самом API подробно не буду останавливаться.

Наша задача построить более-менее приличный интерфейс для удобной работы из Metasploit. В моем понимании, это реализация следующей схемы работы:

  1. Выбор таргета. Либо постраничный вывод полного списка таргетов, которые есть в окуне. Либо поиск таргета по домену.
  2. Когда таргет выбран, нам было бы неплохо иметь возможность посмотреть список уязвимостей. В идеале, есть смысл отфильтровать большую часть, так как не все можно поэксплуатировать. В рамках статьи, будем отбрасывать всякий информационный шлак типа “Generic Email Address Disclosure”. Дальше, по примеру, каждый сможет собрать свою коллекцию фильтров.
  3. Очень неплохо было бы сделать автоматический подбор эксплоита.
Начнем с простейшего плагина, чтобы в целом понять какие элементы нужны, какие элементы важны:

Ruby:
# -*- coding: binary -*-
# Acunetix Integration Plugin for Metasploit

module Msf
  class Plugin::AcunetixIntegration < Msf::Plugin
    class AcunetixCommandDispatcher
      include Msf::Ui::Console::CommandDispatcher

      def name
        "Acunetix Integration"
      end

      def commands
        {
          'acu_search' => "Пример команды для интеграции с Acunetix"
        }
      end

      def cmd_acu_search(*args)
        print_line("Acunetix Integration: команда acu_search была вызвана!")
      end
    end

    def initialize(framework, opts)
      super
      add_console_dispatcher(AcunetixCommandDispatcher)
      print_status("Acunetix Integration plugin загружен.")
    end

    def cleanup
      remove_console_dispatcher('Acunetix Integration')
    end

    def name
      "Acunetix Integration"
    end

    def desc
      "Плагин для интеграции с Acunetix"
    end
  end
end

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

Код:
# -*- coding: binary -*-

Чтобы интегрироваться в фреймворк метасплоита, каждый плагин должен быть обернут в модуль Msf. Теперь можно объявить наш основной класс, который наследуется от Msf::Plugin

Ruby:
module Msf
class Plugin::AcunetixIntegration < Msf::Plugin
Класс реализует функцию инициализации, очистки, а также имя и описание. Эти функции достаточно просты и интуитивно понятны, чтобы их разбирать подробно. Лучше посмотрим на внутренний класс AcunetixCommandDispatcher, который нужен для обработки команд. Именно благодаря ему, мы можем создавать собственные команды в Metasploit. Обратите внимание, что здесь, как и в основном классе, есть функция name. Если в основном классе, функция name определяет имя класса по которому мы загружаем и выгружаем его в/из фреймворка (load acunetix), в диспетчере она нужна для отображения в списке команд вызываемом функцией help:

1749291559285.png


Да-да, идиотской затеей было использовать кириллицу в плагине. Исправим.

Функция “commands” возвращает список команд с описанием, а реализуются эти команды при помощи функций с названиями начинающимися на cmd_ и именем из того самого возвращаемого списка.

Выглядит все достаточно доступно. Реализуем вывод таргетов из окуня. Для этого нам потребуется где-то хранить путь к API и секретный ключ. Ну и пару функций для выполнения запросов.

Начнем с добавления в основной класс приватных сервисных методов, которые будут запрашивать данные у Acunetix API и выводить результаты:

Ruby:
      private


      def acunetix_api_request(path, query, cursor = nil)
        uri = URI.parse("#{@acunetix_api_url}#{path}")
        query_string = []
        query_string << "c=#{cursor}" if cursor
        query_string << "l=25"
        query_string << "q=#{query}" if query
        uri.query = query_string.join('&')


        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE


        request = Net::HTTP::Get.new(uri.request_uri)
        request['X-Auth'] = @acunetix_api_key
 
        response = http.request(request)
        JSON.parse(response.body) rescue {}
      end


      def list_targets(cursor = nil)
        data = acunetix_api_request('/targets', nil, cursor)
        pagination = data['pagination'] || {}
        cursors = pagination['cursors'] || []
        @targets_total_pages = (pagination['count'].to_f / 25).ceil || 0
      
        [data['targets'] || [], cursors]
      end


      def search_targets(search_term)
        data = acunetix_api_request('/targets', search_term)
        data['targets'] || []
      end


Ключевая здесь “acunetix_api_request”, остальное все обертки.

Здесь же, напишем функцию распечатывающую информацию о найденных таргетах. Так как объект возвращаемый Acunetix нам позволяет два раза не вставая получить информацию по количеству уязвимостей, выведем с разбивкой по уровню риска.

Ruby:
def display_targets(targets)
        targets.each_with_index do |target, index|
          num = index + 1
          desc = target['description'] ? " (#{target['description']})" : ""
          severity_counts = target['severity_counts'] || {}


          print_line("%3d. %-45s %-25s | Critical: %3d | High: %3d | Medium: %3d " % [
              num,
              target['address'].to_s[0, 45],  # обрезка, если длинный адрес
              desc.to_s[0, 25],               # обрезка, если длинное описание
              severity_counts['critical'] || 0,
              severity_counts['high'] || 0,
              severity_counts['medium'] || 0
            ])
        end
      end
    end

Добавим функцию инициализации для диспетчера команд.

Ruby:
class AcunetixCommandDispatcher
      include Msf::Ui::Console::CommandDispatcher

      def initialize(driver)
        super(driver)
        @acunetix_api_url = 'https://localhost:3443/api/v1'
        @acunetix_api_key = '<your_api_key>'
        @selected_target = nil
        @selected_vuln = nil
        @cached_targets = []
        @cached_vulns = []
        @severity = nil
        @active_exploit_module = nil
        @pending_exploits = nil

        @target_cursors = [nil]
        @target_current_page = 1
        @targets_total_pages = 1
        @vuln_cursors = [nil]
        @vuln_current_page = 1
        @vuln_total_pages = 1   

      end

На входе принимаем драйвер, который передаем родительскому классу. После чего объявляем сервисные переменные. Теперь можно заняться списком команд плагина::

Ruby:
      def commands
        {
          'acu_search'    => "Search targets in Acunetix by name/description",
          'acu_list'      => "List all targets (paginated, 25 per page)",
          'acu_select'    => "Select target by number for further operations",
          'acu_show'      => "Show currently selected target"
        }
      end

Из списка команд понятно, что реализовывать будем четыре команды:
  1. Поиск по части доменного имени
  2. Постраничный вывод всех таргетов
  3. Выбор таргета для дальнейшей работы
  4. Вывод информации о выбранном таргете

Ruby:
     def cmd_acu_search(*args)
        if args.empty?
          print_error("Please specify search query. Example: acu_search example.com")
          return
        end

        search_term = args.join(' ')
        targets = fetch_targets(search_term)

        if targets.empty?
          print_error("No targets found for: #{search_term}")
        else
          print_good("Found #{targets.size} target(s):")
          @cached_targets = targets
          display_targets(targets)
        end
      end

     def cmd_acu_list(*args)

       all_targets, cursors = list_targets()
       if all_targets.empty?
         print_error("No targets found in Acunetix")
         return
       end

       @cached_targets = all_targets
       display_targets(all_targets)
     end

1749291705143.png


Как видно со скрина, команды прекрасно отрабатывают. Даже автодополнение команд, это видно по моим затупам при вызове функции acu_search.

Отдельно хочется поговорить про пагинацию, потому что она реализована крайне интересным образом. Пришлось провести небольшое исследование, чтобы понять логику. Это не пипец как важно для реализации нашего плагина, но чем как не исследованиями занимаются собравшиеся здесь люди? Искусство взлома, не важно технического или социального, это ведь в первую очередь исследование… поэтому и уделяю внимание этому блоку.

Вот пример объекта пагинации:

Код:
"cursors": [
   null,
"WyIyMDI0LTExLTIyVDA3OjQ5OjM5LjQ4NzgzNCswMDowMCIsIDM1MDY3ODg0MDEzNzYzOTcxNjBd",
   "WyIyMDI0LTExLTIyVDA3OjQ5OjM5LjA3ODU3NiswMDowMCIsIDM1MDY3ODgzOTc5MzcwNjc4MjBd"
  ],

Это результат запроса без курсора, т.е. первой страницы в выводе. Логично было бы предположить, что первое значение это предыдущая страница, второе текущая, третье следующая. В эту логику укладывается и null, который на первой странице не может ничего другого содержать, кроме как null. Но эта логика привела к вырванным клочкам волос, так как реализовав алгоритм, я получал совершенно неожиданные результаты и полностью непослушный механизм пролистывания. Благо сам окунь использует точно такие же объекты и логику в своем интерфейсе, а значит можно мониторить запросы на вкладке “Сеть” браузера:

1749291731535.png


Логика оказалась следующей:
  1. Первая страница доступна только без указания курсора. Чтобы определить, что мы на первой странице, нужно проверить первое значение на Null
    1749291747944.png
  2. Вторая и последующие страницы содержат указатель на предыдущую страницу и две последующих.
    1749291760495.png
  3. Предпоследняя страница, содержит два курсора (предыдущая и последующая)
    1749291775301.png
  4. Последняя страница содержит один указатель на предыдущую страницу.
    1749291786507.png
В результате, алгоритм получился не идеальный, но хотя бы рабочий. Переходить между страницами можно, как используя “next” и “prev”, так и используя номера страниц. Правда, нужно понимать, что по номерам можно обращаться только к уже посещенным страницам.

Теперь можно посмотреть полный код получения списка таргетов:

Ruby:
     def cmd_acu_list(*args)
       direction = args[0]


       if direction.nil?
         @target_current_page = 1
       elsif direction == "next"
         @target_current_page += 1
       elsif direction == 'prev'
         @target_current_page -= 1
       else
         @target_current_page = args[0].to_i
       end


       if @target_current_page < 1
         @target_current_page = 1
       elsif @target_current_page > @target_cursors.size
         @target_current_page = @target_cursors.size
       end


       all_targets, cursors = list_targets(@target_cursors[@target_current_page - 1])
       if all_targets.empty?
         print_error("No targets found in Acunetix")
         return
       end


       @target_cursors = (@target_cursors + cursors).uniq
       @cached_targets = all_targets
       display_targets(all_targets)


       print_line("Use 'acu_list prev' or 'acu_list next' or 'acu_list <num_page>' to navigate pages.") if cursors.size > 0
       print_line("Current page is #{@target_current_page} from #{@targets_total_pages}")
     end

Чтобы переходить к уязвимостям, нужно организовать выбора таргета. Решил пойти в направлении удобства, хоть и пожалею об этом, как обычно))) Сделал выбор, как по идентификатору, так и введя часть имени домена:

Ruby:
      def cmd_acu_select(*args)
        if args.empty?
          print_error("Please specify target number or part of domain/description.")
          return
        end


        query = args.join(' ')
        if query.match?(/^\d+$/)
          index = query.to_i
          if @cached_targets && index.between?(1, @cached_targets.size)
            @selected_target = @cached_targets[index - 1]
            print_good("Selected target ##{index}: #{@selected_target['description'] || @selected_target['address']}")
          else
            print_error("Invalid target number. Use 'acu_list' or 'acu_search' to see available targets.")
          end
        else
          matches = @cached_targets.select do |t|
            t['address'].downcase.include?(query.downcase) ||
            (t['description'] && t['description'].downcase.include?(query.downcase))
          end


          if matches.empty?
            print_error("No targets match your query: '#{query}'")
          elsif matches.size == 1
            @selected_target = matches.first
            print_good("Selected target: #{@selected_target['description'] || @selected_target['address']}")
          else
            print_status("Multiple targets match your query. Please refine it or use the number:")
            display_targets(matches)
          end
        end
      end

Обратите внимание, что для функции поиска не прикручивал алгоритм пагинации, лучше используйте точные значения)

1749291841063.png


Чтобы убедиться, что все работает и таргет выбирается, добавим функцию вывода основной информации:

Ruby:
      def cmd_acu_show(*args)
        if @selected_target
          print_good("Currently selected target:")
          print_line("  ID: #{@selected_target['target_id']}")
          print_line("  Address: #{@selected_target['address']}")
          print_line("  Description: #{@selected_target['description'] || 'N/A'}")
        else
          print_error("No target selected. Use 'acu_list' and 'acu_select' first")
        end
      end

С первой частью практически покончено. Процесс выбора уязвимости мало отличается от выбора таргета. Мы делаем схожий запрос, только к другому endpoint, передавая идентификатор выбранного таргета. Поэтому акцентировать внимание на них не буду, весь код есть в приложенных исходниках.

Дополним список функций, необходимыми для получения уязвимостей и выводу информации по ним:

Ruby:
      def commands
        {
          'acu_search'      => "Search targets in Acunetix by name/description",
          'acu_list'        => "List all targets (paginated, 25 per page)",
          'acu_select'      => "Select target by number for further operations",
          'acu_show'        => "Show currently selected target",
          'acu_vuln_list'   => 'List all target vulnerabilities',
          'acu_vuln_show'   => "Show details for specific vulnerability by number",
          'acu_vuln_select' => 'Select vulnerability by number'
        }
      end

В выводе списка уязвимостей, так же реализован вывод с фильтрацией по уровню опасности. В Acunetix есть пять уровней от 0 до 4, где 0 это Low, а 4 это Critical. Плагин поддерживает перечисление через запятую: 3,4 приведет к выводу уязвимостей с уровнем High и Critical.

Ruby:
     def cmd_acu_vuln_list(*args)
       if @selected_target.nil?
         print_error("No target selected. Use 'acu_list' and 'acu_select' first")
         return
       end


       direction = args[0]


       if direction.nil?
         @vuln_current_page = 1
       elsif direction == "next"
         @vuln_current_page += 1
       elsif direction == 'prev'
         @vuln_current_page -= 1
       else
         @vuln_current_page = args[0].to_i
       end


       if @vuln_current_page < 1
         @vuln_current_page = 1
       elsif @vuln_current_page > @vuln_cursors.size
         @vuln_current_page = @vuln_cursors.size
       end


       @severity = args[1].to_i if args[1]&.to_i&.positive?


       all_vulns, cursors = list_vulns(@vuln_cursors[@vuln_current_page - 1])
       if all_vulns.empty?
         print_error("No vulnerabilities found for target in Acunetix")
         return
       end


       @vuln_cursors = (@vuln_cursors + cursors).uniq
       @cached_vulns = all_vulns


       display_vulns(all_vulns)


       print_line("Use 'acu_vuln_list prev <severity>' or 'acu_vuln_list next <severity>' or 'acu_vuln_list <page num> <severity>' to navigate pages.") if cursors.size > 0
       print_line("Current page is #{@vuln_current_page} from #{@vuln_total_pages}")
     end




     def list_vulns(cursor = nil)
       query = "target_id:#{@selected_target['target_id']}"
       query += ";severity:#{@severity}" if @severity
       data = acunetix_api_request('/vulnerabilities', query, cursor)
       pagination = data['pagination'] || {}
       cursors = pagination['cursors'] || []
       @vuln_total_pages = (pagination['count'].to_f / 25).ceil || 0
      
       [data['vulnerabilities'] || [], cursors]
     end


      def display_vulns(vulns)
        vulns.each_with_index do |vuln, index|
            print_line("%3d. [%s] %-50s" % [
            index + 1,
            vuln['severity'],
            vuln['vt_name']
            ])
        end
      end

Функция выбора уязвимости, как и выбора таргета, принимает индекс или частичное название:

Ruby:
      def cmd_acu_vuln_select(*args)
        if args.empty?
            print_error("Please specify vulnerability number or part of name.")
            return
        end


        query = args.join(' ')
        if query.match?(/^\d+$/)
            index = query.to_i
            if @cached_vulns.is_a?(Array) && index.between?(1, @cached_vulns.size)
            @selected_vuln = @cached_vulns[index - 1]
            print_good("Selected vulnerability ##{index}: #{@selected_vuln['vt_name']}")
            else
            print_error("Invalid vulnerability number. Use 'acu_vuln_list' to see available vulnerabilities.")
            end
        else
            matches = @cached_vulns.select do |t|
            t['vt_name'] && t['vt_name'].match?(/#{Regexp.escape(query)}/i)
            end


            case matches.size
            when 0
            print_error("No vulnerabilities match your query: '#{query}'")
            when 1
            @selected_vuln = matches.first
            print_good("Selected vulnerability: #{@selected_vuln['vt_name']}")
            else
            print_status("Multiple vulnerabilities match your query. Please refine it or use the number:")
            display_vulns(matches)
            end
        end
      end

Функция вывода описания уязвимости имеет два формата: обычный и расширенный. Если пользователь выполнит команду с параметром “extended”, в вывод добавятся такие поля, как request и response, и другие. Обращаю внимание, что логика команды построена так чтобы пользователь мог вывести информацию, как с использованием индекса из списка уязвимостей, так и без его указания, если была выполнена acu_vuln_select для выбора:
Ruby:
      def cmd_acu_vuln_show(*args)
        if @cached_vulns.empty?
            print_error("No vulnerabilities cached. Use 'acu_vuln_list' first")
            return
        end


        extended = args.include?('extended')
        args = args.reject { |a| a == 'extended' }
        vuln = nil


        if args.empty?
            if @selected_vuln
            vuln = @selected_vuln
            else
            print_error("No vulnerability number provided and no vulnerability selected. Use 'acu_vuln_select' or pass a number.")
            return
            end
        else
            vuln_index = args[0].to_i
            if vuln_index < 1 || vuln_index > @cached_vulns.size
            print_error("Invalid vulnerability number")
            return
            end
            vuln = @cached_vulns[vuln_index - 1]
        end


        vuln_id = vuln['vuln_id']
        show_vuln_details(vuln_id, extended: extended)
      end


      def show_vuln_details(vuln_id, extended: false)
        data = acunetix_api_request("/vulnerabilities/#{vuln_id}", nil) || {}


        if data.empty?
            print_error("Failed to fetch vulnerability details")
            return
        end


        print_good("\nVulnerability Details:")
        print_line("  Name: #{data['vt_name'].to_s}")
        print_line("  Severity: #{data['severity'].to_s}")
        print_line("  Status: #{data['status'].to_s}")
        print_line("  Confidence: #{data['confidence'].to_s}")
        print_line("  Found at: #{data['found_at'].to_s || data['first_seen'].to_s}")
        print_line("  Affects URL: #{data['affects_url'].to_s}")


        print_line("\nDescription:")
        print_line(word_wrap(data['description'].to_s, indent: 2))


        if data['impact']
            print_line("\nImpact:")
            print_line(word_wrap(data['impact'].to_s, indent: 2))
        end


        # if data['recommendation']
        #     print_line("\nRecommendation:")
        #     print_line(word_wrap(data['recommendation'].to_s, indent: 2))
        # end


        if extended
            if data['cvss_score']
            print_line("\nCVSS Scores:")
            print_line("  CVSS2: #{data['cvss2'].to_s} (#{data['cvss_score'].to_s})")
            print_line("  CVSS3: #{data['cvss3'].to_s}")
            print_line("  CVSS4: #{data['cvss4'].to_s} (#{data['cvss4_score'].to_s})")
            end


            if data['request']
            print_line("\nRequest:")
            print_line(word_wrap(data['request'].to_s, indent: 2, line_width: 100))
            end


            if data['response']
            print_line("\nResponse:")
            print_line(word_wrap(data['response'].to_s, indent: 2, line_width: 100))
            end


            if data['references'] && !data['references'].empty?
            print_line("\nReferences:")
            data['references'].each do |ref|
                print_line("  - #{ref['rel'].to_s}: #{ref['href'].to_s}")
            end
            end


            if data['tags'] && data['tags'].any?
            print_line("\nTags:")
            print_line("  #{data['tags'].join(', ')}")
            end
        else
            print_line("\n(Use `acu_vuln_show <n> extended` for full details.)")
        end
      end

Переходим к самому интересному. На основе уязвимости попытаемся реализовать механизм подбора и автоматической настройки эксплоита.

Для тестов, возьмем старый добрый Metasploitable 2, который и был создан для эксплуатации метасплоитом. Как установить описано во множестве материалов. После установки нужно просканировать окунем. Для наглядности, в hosts прописал metasploitable2.com

Как может работать алгоритм по подбору эксплоита?​


Перед нами стоит большое количество проблем, многие из которых не получится решить универсально. Мы сможем сделать только компромиссный вариант, нацеленный на некоторые виды уязвимостей. Хорошая новость в том, что постепенно его можно наполнять и наполнять. Для статьи используем CVE-2012-1823, которая классически используется для демонстрации возможностей Metasploit.

Первое, что приходит в голову, это поиск по CVE. Многие уязвимости в Acunetix имеют в своем описании ссылки на существующие CVE. Вычленив номер из уязвимости, мы можем покопаться в framework.modules. Учитывая, что Acunetix может запихать CVE почти куда угодно, проверять будем сразу множество полей (тэги, название уязвимости, описание и ссылки):

Ruby:
      def extract_cves(vuln)
        cves = []
      
        if vuln['tags']
          vuln['tags'].each do |tag|
            tag.to_s.scan(/CVE[-\s_]?(\d{4})[-\s_]?(\d{4,7})/i).each do |year, id|
              cves << "CVE-#{year}-#{id}"
            end
          end
        end
      
        vuln['vt_name'].to_s.scan(/CVE[-\s_]?(\d{4})[-\s_]?(\d{4,7})/i).each do |year, id|
          cves << "CVE-#{year}-#{id}"
        end
      
        [vuln['description'], vuln['details']].each do |text|
          text.to_s.scan(/CVE[-\s_]?(\d{4})[-\s_]?(\d{4,7})/i).each do |year, id|
            cves << "CVE-#{year}-#{id}"
          end
        end

        if vuln['references']
          vuln['references'].each do |ref|
            ref['rel'].to_s.scan(/CVE[-\s_]?(\d{4})[-\s_]?(\d{4,7})/i).each do |year, id|
              cves << "CVE-#{year}-#{id}"
            end
          end
        end
        cves.uniq
      end

Когда у нас есть список найденyых CVE, можно заняться поиском подходящего эксплоита.
Но есть нюанс. В этом массиве храняться только имена модулей. Чтобы посмотреть метаданные, нужно загрузить этот модуль. Тогда и только тогда будет возможность проверить совпадение по нашему CVE.

Ruby:
      def find_exploits_by_cve(cve)
        return [] unless cve

        unless cve.is_a?(String)
          raise ArgumentError, "find_exploits_by_cve expects a String argument, got #{cve.inspect}"
        end

        normalized_cve = cve.gsub(/^CVE-/i, '')

        exploits = []


        framework.modules.module_names('exploit').each do |name|
          begin
            mod = framework.modules.create("exploit/#{name}")
            next unless mod.respond_to?(:references)

            if mod.references.any? { |ref| ref.ctx_id == 'CVE' && ref.ctx_val == normalized_cve }
              exploits << "exploit/#{name}"
              print_good "Matched exploit: exploit/#{name} for CVE-#{normalized_cve}"
            end
          rescue => e
            print_error "Failed to load module exploit/#{name}: #{e}"
          end
        end


        exploits
      end

Процесс поиска проходит на основании объекта references, который содержит ссылки (описания) на внешние ресурсы, связанные с CVE, CWE, BID, MSB, WPVDB и т.д. Помимо ctx_id и ctx_val, есть геттер site, который содержит ссылку на ресурс с описанием уязвимости. Вот кусок файла описывающего объект /usr/share/metasploit-framework/lib/msf/core/module/reference.rb

Ruby:
  def initialize(in_ctx_id = 'Unknown', in_ctx_val = '')
    self.ctx_id  = in_ctx_id
    self.ctx_val = in_ctx_val

    if in_ctx_id == 'CVE'
      self.site = "https://nvd.nist.gov/vuln/detail/CVE-#{in_ctx_val}"
    elsif in_ctx_id == 'CWE'
      self.site = "https://cwe.mitre.org/data/definitions/#{in_ctx_val}.html"
    elsif in_ctx_id == 'BID'
      self.site = "http://www.securityfocus.com/bid/#{in_ctx_val}"
    elsif in_ctx_id == 'MSB'
      year = in_ctx_val[2..3]
      century = year[0] == '9' ? '19' : '20'
      self.site = "https://docs.microsoft.com/en-us/security-updates/SecurityBulletins/#{century}#{year}/#{in_ctx_val}"

Т.е., при желании, можно значительно расширить возможности поиска.

Необходимость загружать модули сильно замедляет процесс. Если сравнить поиск с “search cve:”, разница будет существенной. В рамках статьи оставлю реализацию как есть, но при желании можно использовать костыли… например, системно выполнить searchsploit и распарсить результат. Или воспользоваться этим плагином:

Ruby:
require 'msf/core'

module Msf
class Plugin::MetasploitModule < Msf::Plugin
  class CVEExploitMapperCommandDispatcher
    include Msf::Ui::Console::CommandDispatcher

    def name
      "CVEExploitMapper"
    end

    def commands
      {
        "map_cve_exploits" => "Scan all exploit modules and save CVE to module mappings"
      }
    end

    def cmd_map_cve_exploits
      output_file = ::File.join(Msf::Config.config_directory, "cve_exploit_map.txt")
      mapped = {}

      print_status("Scanning exploit modules...")

      framework.modules.module_names('exploit').each_with_index do |name, index|
        fullname = "exploit/#{name}"
        begin
          mod = framework.modules.create(fullname)
          next unless mod && mod.respond_to?(:references)

          mod.references.each do |ref|
            if ref.ctx_id == 'CVE'
              cve = ref.ctx_val.strip.upcase
              mapped[cve] ||= []
              mapped[cve] << fullname unless mapped[cve].include?(fullname)
              print_good("#{cve}: #{fullname}")
            end
          end
        rescue ::Exception => e
          print_error("Failed to load #{fullname}: #{e}")
        end
      end

      print_status("Saving results to: #{output_file}")
      ::File.open(output_file, "w") do |f|
        mapped.sort.each do |cve, modules|
          modules.each do |mod|
            f.puts "#{cve}:#{mod}"
          end
        end
      end

      print_good("Done. Found #{mapped.keys.size} unique CVEs.")
    end
  end

  def initialize(framework, opts)
    super
    add_console_dispatcher(CVEExploitMapperCommandDispatcher)
    print_status("CVEExploitMapper plugin loaded. Use command: map_cve_exploits")
  end

  def cleanup
    remove_console_dispatcher('CVEExploitMapper')
  end
end
end

Это плагин создаст файл, в который выгрузит все названия модулей и CVE в таком формате:

2024-8504:exploit/unix/webapp/vicidial_agent_authenticated_rce
2024-8517:exploit/multi/http/spip_bigup_unauth_rce
2024-8856:exploit/multi/http/wp_time_capsule_file_upload_rce
2024-9474:exploit/linux/http/panos_management_unauth_rce
2025-0655:exploit/linux/http/dtale_rce_cve_2025_0655
2025-1094:exploit/linux/http/beyondtrust_pra_rs_unauth_rce
2025-24813:exploit/multi/http/tomcat_partial_put_deserialization
2025-27218:exploit/windows/http/sitecore_xp_cve_2025_27218
2025-27520:exploit/linux/http/bentoml_rce_cve_2025_27520
2025-2945:exploit/multi/http/pgadmin_query_tool_authenticated
2025-3248:exploit/multi/http/langflow_unauth_rce_cve_2025_3248

Останется только в плагине отфильтровать данные из файла и вытащить полные названия. А мы идем дальше.

Напишем функцию активации и протестируем:

Ruby:
def activate_exploit(module_name, vuln_data = nil)
  begin
  
    driver.run_single("use #{module_name}")

    if vuln_data
      if vuln_data['affects_url']
        driver.run_single("set RHOSTS #{URI.parse(vuln_data['affects_url']).host}") rescue nil
        driver.run_single("set TARGETURI #{URI.parse(vuln_data['affects_url']).path}") rescue nil
      end
    end

    available_port = find_available_port
    if available_port
      driver.run_single("set LPORT #{available_port}")
      print_status("Auto-selected available LPORT: #{available_port}")
    else
      print_warning("Could not find available port. You may need to set LPORT manually.")
    end

    return true
  rescue => e
    print_error("Error activating exploit: #{e}")
    return nil
  end
end

Я не стал заморачиваться и использовал возможность выполнить команду прямо в консоли при помощи драйвера. Помните передавали для создания объекта? Вот, пригодился.

Само использование команды вряд ли требует пояснений. Просто пишем команду, которая нам нужна и все, для msfconsole это тоже самое, как если бы пользователь вбил данные.

Кстати, плагин в автоматическом режиме ищет свободный порт. Функция поиска порта:

Ruby:
      def find_available_port(min_port = 1337, max_port = 7777)
        port = min_port
        used_ports = []
        framework.sessions.each do |sid, session|
          if session.tunnel_local && session.tunnel_local =~ /:(\d+)$/
            used_ports << $1.to_i
          end
        end


        while port <= max_port
          unless used_ports.include?(port) || !port_open?(port)
            return port
          end
          port += 1
        end
        raise "No free port found in range #{start_port}-#{max_port}"
      end

      def port_open?(port)
        begin
          server = TCPServer.new('0.0.0.0', port)
          server.close
          return true
        rescue Errno::EADDRINUSE, Errno::EACCES
          return false
        end
      end

Минимальное и максимальное значение взято случайным образом. Функция получает все занятые порты сессиями, чтобы не пересекаться с ними. После проходит по списку портов, пытаясь создать на каждом сервер. Если сервер создается и порт не относится к существующей сессии, функция считает порт доступным для использования. Не забудьте в начале плагина импортировать библиотеку ‘socket’.

В плагине отсутствует получение IP адреса для установки LHOST. Это сделано из-за того, что может быть разная конфигурация сетевых устройств. Но, в теории, можно было бы использовать что-то типа такого:

Ruby:
def local_ip
  udp = UDPSocket.new
  udp.connect('8.8.8.8', 1)
  ip = udp.addr.last
  udp.close
  ip
end

Результат тестирования:

1749292110933.png


Ругательства метасплоита, в процессе поиска, как раз таки связаны с необходимостью загружать модули в память. Оно не всегда выскакивает, но если выпало, не пугайтесь - все идет по плану.

Как видно на скрине, в конце у нас изменилось приглашение, а значит эксплоит активировался. Если посмотреть опции, плагин их так же установил:

1749292128373.png


Если у нас нет CVE, скорее всего уязвимость не подойдет для эксплуатации и создания сессии в Метасплоит. Не могу гарантировать, но вроде как всем эксплуатируемым уязвимостям Acunetix указывает CVE. В ином случае придется заниматься фаззингом по ключевым словам. Брать тэги, брать части названия и шерстить по модулям. Но этот процесс может сильно затянуться, поэтому писать код под него не вижу смысла. Если появится желание его дописать, важно придумать алгоритм вычлинения ключевых слов, а процесс поиска будет крайне схож с поиском по CVE: загрузили модуль в память, посмотрели совпадения. Можно попытаться поискать совпадения и в самому пути к эксплоиту.

Что касается устанавливаемых опций, я использовал минимальный набор. Но, ради интереса, написал плагин, который собирает все уникальные опции эксплоитов. Всего получилось 1307 вариаций. Плагин в приложенном архиве, как и файл со списком.

Нейромозг​

Раз у нас началась полная вакханалия, почему бы не попытаться поднять точность срабатываний через нейронные сети? По хорошему, заморочиться и натренировать тот же LLama… по хорошему… если бы уметь это делать)))) Но есть OpenAI с достаточно хорошими познаниями в отношении Metasploit и доступными эксплоитами. Вот сравнение ответов общедоступного ChatGPT с подобным LLama 3.3 без предварительной тренировки:

1749292145518.png


1749292159273.png

Без накачки знаниями, модель llama ничерта не знает о модуля метасплоита. Возможно есть готовые подходящие модели на huggingface, я не нашел.

С промптами к gpt нужно быть внимательным, иначе может случиться казус:

1749292172319.png


Потребуется платный аккаунт, который можно поискать в разных шопах за адекватные деньги. Либо взять какой-то агрегатор нейронок. Я нашел агрегатор в котором достаточно было подтвердить номер телефона и получить стартовый баланс в 20 рублей, чего за глаза хватает для моей задачи. Это не реклама, даже не отзыв о сервисе, сделал в нем всего пару десятков запросов чтобы получить рабочий вариант плагина.

Начнем с упрощения функции поиска эксплоита, удалив все лишнее:

Ruby:
      def cmd_acu_find_exploit
        if @selected_vuln.nil?
          print_error("No vulnerability selected. Use 'acu_vuln_select' first.")
          return
        end


        vuln = @selected_vuln


        exploit = ai_gen_exploit_name


        mod = activate_exploit(exploit, @selected_vuln)
        if mod
          print_good("Exploit module ready. Run 'exploit' to start.")
          @active_exploit_module = mod
          @pending_exploits = nil
        else
          print_error("Failed to activate exploit.")
        end
      end

Рассматривать API сервиса не вижу смысла, так как нам от него нужна всего одна функция и один типовой запрос. Единственный нюанс, я сделал ставку не на лонг-пулинг, а на синхронный запрос. Задача не сложная и ответ от нейросети будет коротким, поэтому проблем возникнуть не должно. Так же не заморачивался с температурами и прочими параметрами.

Функция запрашивающая название модуля эксплоита у AI:

Ruby:
def ai_gen_exploit_name
        return nil unless @selected_vuln


        vuln = @selected_vuln
        title = vuln['vt_name']
        description = vuln['description'] || ''
        tags = vuln['tags'] || []
        references = vuln['references'] || []
        rels = references.map { |r| r['rel'] }.compact

        prompt = <<~PROMPT
            You are helping a Metasploit plugin select the best matching exploit module based on a given vulnerability.
            Please analyze the vulnerability based on these fields:
            - Title: #{title}
            - Description: #{description}
            - Reference tags: #{rels.to_json}
            - Tags: #{tags.to_json}
            Return the most relevant Metasploit exploit module path, like `exploit/unix/webapp/phpinfo_exec`, or return "None" if no relevant module exists. Return only the module path or "None".
        PROMPT

        body = {
            "messages": [
            {
                "role": "user",
                "content": prompt
            }
            ],
            "is_sync": true,
            "stream": false,
            "n": 1,
            "frequency_penalty": 0,
            "max_tokens": 1024,
            "presence_penalty": 0,
            "temperature": 0.7,
            "top_p": 1,
            "response_format": "{\"type\":\"text\"}"
        }.to_json

        uri = URI('https://api.gen-api.ru/api/v1/networks/o1')
        req = Net::HTTP::Post.new(uri)
        req['Authorization'] = "Bearer #{@ai_token}"
        req['Content-Type'] = 'application/json'
        req.body = body

        print_status("Sending request to neural network...")
        print_status("Request URL: #{uri}")
        print_status("Request headers:")
        req.each_header { |key, value| print_status("  #{key}: #{value}") }
        print_status("Request body:\n#{body}")

        print_status("Waiting for neural network response...")

        begin
            http = Net::HTTP.new(uri.host, uri.port)
            http.use_ssl = true
            http.verify_mode = OpenSSL::SSL::VERIFY_NONE
            res = http.request(req)

            print_status("Received response:")
            print_status("HTTP #{res.code} #{res.message}")
            res.each_header { |key, value| print_status("  #{key}: #{value}") }
            print_status("Response body:\n#{res.body}")

            if res.is_a?(Net::HTTPSuccess)
                data = JSON.parse(res.body)

                output = data.dig("response", 0, "message", "content").to_s.strip
                output = output.gsub(/\\\//, '/').gsub(/^['"]|['"]$/, '')

                if output =~ %r{\Aexploit\/[a-z0-9_/-]+\z}i
                    return output.downcase
                elsif output.strip.downcase == "none"
                    return "None"
                else
                    print_warning("Unexpected neural response: #{output}")
                    return nil
                end
            else
                print_error("AI request failed: #{res.code} #{res.message}")
                return nil
            end
        rescue => e
            print_error("Exception during AI exploit selection: #{e}")
            return nil
        end
        end
    end

Я не силен в написании промптов, поэтому попросил написать его ChatGPT. Возможно, можно сильно сократить с целью экономии денег. Модель я использовал “о1”, можно попробовать chat-gpt-3, чтобы было подешевле.

Остается прописать API ключ и выполнить тест:

1749292321904.png


Иииха, все четко работает. Неприятна только цена в почти 9 рублей за генерацию. Можно, конечно, выкинуть описание ошибки. В нем много лишнего, что жрет токены. Но, что интересно, предыдущая попытка запроса стоила меньше 4х рублей. Может сервис что-то мутит. Но повторюсь, это просто первый попавшийся сервис. Главное, что мы добились своей цели — скрестили Acunetix, Metasploit и нейросеть.

Можно попробовать поискать на github слитые ключи, или попробовать дорками покопать (что-то вроде “site:github.com key sk-”), варианты есть, но конечно же не советую и осуждаю.

Использование сторонних сканеров​

В обратную сторону мы тоже можем работать, причем в довольно интересном ключе. Почему бы не набросать плагин, который позволит прямо из Метасплоита запускать сторонние сканеры? Большая часть знаний у нас уже есть. Чтобы отправлять в тот же окунь, нужно только научиться получать настроенные в Мете опции. На самом деле это безумно просто, так как у CommandDispatcher есть объект “active_module”, который указывает на, удивительно, модуль активный в данный момент в msfconsole В свою очередь, в нем есть объект с опциями “datastore”, через который можно получить установленные опции.

Ruby:
rhost = active_module.datastore['RHOST']

Кстати, устанавливать опции тоже можно через ‘datasotre’. В предыдущем плагине, после выбора объекта через driver.run_single, мы могли не использовать его же для установки опций, а воспользоваться ‘datastore’. Это более нативный способ, но “тихий”, при использовании run_single пользователь увидит в консоли происходящие изменения, а datastore ничего не сообщит. В случаях полной автоматизации, более предпочтительно работать с active_module,

Объединим знания из предыдущего раздела с новыми и напишем модуль отправляющий таргет на сканирование в окуня.

1749292349795.png


В чем удобство, если нужно каждый раз загружать плагин? Исправим!

Отредактируйте файл:

Код:
~/.msf4/msfconsole.rc

Если его нет создайте. Если запускаете консоль под sudo, то создавайте файл не в домашней папке текущего пользователя, а в /root/.msf4. msf4 в любом случае, даже если у вас шестая версия, как у меня. В файл прописываем команду загрузки:

Код:
load acu_send

Теперь, при загрузке консоли метасплоит, вы будете видеть сообщение об успешной загрузке плагина:, а значит с плагином все окей.

1749292374697.png


Что делать, если мы хотим выполнить какую-то программу, у которой нет собственного API? Кто постоянно пользуется Метасплоитом знает, что для этого не нужен никакой сторонний плагин. Можно просто ввести команду, как в обычном терминале Linux и получить результат. Но мы больше о концепциях и возможностях, которые можно реализовывать при помощи плагинов. В данном случае, будем запускать сканирование уязвимостей при помощи nmap с последующим парсингом отчета и автоматическим подбором эксплоита. Плагин так же будет брать данные из активного модуля. Сканировать будем со скриптом “vuln”. “Жертва” снова Metasploitable 2.

Описывать весь код плагина не буду, так как ключевые функции просто скопированы из предыдущих плагинов. Главная информация, это сам запуск nmap и работа с отчетом. Простейший способ, это выполнение команды с генерацией отчета во временный файл с последующим парсингом:

Ruby:
      def cmd_nmap_scan(*_args)
        @found_exploits = []
      
        unless active_module
          print_error("No active module selected. Use 'use <module>' first.")
          return
        end

        rhost = active_module.datastore['RHOST']
        unless rhost
          print_error("RHOST is not set. Use 'set RHOST <target>' first.")
          return
        end

        command = "nmap -p21,80,443,1099,3632 --script vuln #{rhost} -oX /tmp/nmap_result.xml"
        print_status("Launching Nmap scan with: #{command}")
        print_status("Please wait for the scan to complete. This may take some time...")

        begin
            `#{command}`
        rescue StandardError => e
            print_error("Failed to run Nmap: #{e.message}")
            return
        end

        unless File.exist?('/tmp/nmap_result.xml')
            print_error("Nmap did not generate output file")
            return
        end

        parse_nmap_results('/tmp/nmap_result.xml')
        display_exploits_table unless @found_exploits.empty?
      end

      def parse_nmap_results(path)
        xml_data = File.read(path)
        cve_matches = xml_data.scan(/CVE-\d{4}-\d{4,7}/i).uniq


        if cve_matches.empty?
          print_status("No CVEs found in the scan results.")
        else
          print_status("Starting search for exploits...")
          cve_matches.each do |cve|
            print_line("  #{cve} search exploit")
            find_exploits_by_cve(cve)
          end
        end
      end

Выполнили системный запуск команды при помощи обратных кавычек ``, после чего регуляркой вытащил CVEшки. Функция поиска эксплоитов почти идентична функции из прошлого раздела, разве что ищет не только “exploit”, но и “auxiliary”:

1749292423682.png


Другой интересный кусок кода:

Ruby:
             mod.references.each do |ref|
                if ref.ctx_id == 'CVE' && ref.ctx_val.sub(/^CVE-/i, '') == normalized_cve
                  add_exploit(cve, "#{mtype}/#{name}", mod.rank_to_s)
                  break
                end
              end

Интересна она получением успешности эксплоита из объекта mod. В нем, на самом деле, есть огромное количество полезных методов. Вот некоторые из них, которые были бы полезны для наполнения вывода плагина:
  1. mod.privileged - возвращает true, если модуль требует привилегий для выполнения
  2. mod.options - список доступных параметров конфигурации модуля.
  3. mod.targets - список поддерживаемых целей, каждая из которых описывает конкретную конфигурацию или платформу, на которую нацелен модуль
  4. mod.compatible - проверяет совместимость модуля с заданной конфигурацией или платформой
  5. mod.arch - архитектура, на которую нацелен модуль, например, x86
  6. mod.platform - платформа, для которой предназначен модуль, например, Windows, Linux.
У плагина есть и минусы. Минус данного способа в том, что мы не отслеживаем состояние nmap и теряем время на последовательное выполнение операций. От части, это проблема из-за буферизации nmap, которую можно попробовать обойти используя более высокие уровни логирования. От части, из-за нашего метода запуска приложения. Можно использовать, например, open3, чтобы иметь возможность читать sdtout и stderr. Повторюсь, я не Ruby-кодер, поэтому на этом мои полномочия все.

Отслеживание открытия сессии​

Далеко не всегда можно отследить момент успешного открытия сессии. Представьте, что вы пробились на сервер с ограниченным доступом. Нашли вектор в виде бэкапера, который срабатывал по таймеру. Сгенерировали полезную нагрузку в msfvenom и получилось её успешно загрузить. Но, в отличии от лабораторных, ждать пока полезная нагрузка выполнится, можно ооочень долго. Настолько долго, что к чертям все будет забыто.

Сделаем плагин, который будет отправлять такие вот уведомления в Телеграм:

1749292452782.png


Сразу становится понятно, что и где происходит. Указание на сервер Метасплоита нужно на случай, если нужно указать где именно загрузилась сессия)))

Для реализации задумки, нам потребуется объект framework.events, который представляет собой центр событий. При помощи него мы можем подписываться на события происходящие в Мете. Можно отслеживать изменения состояний сессий, эксплоитов и базы данных. Например, при создании сессии, генерируется подобное событие:

framework.events.on_event:)session_open, session)

Чтобы его перехватить, нам нужно подписаться, дав Метасплоиту понять, что мы готовы обрабатывать событие:

Ruby:
framework.events.add_session_subscriber(self)

Для перехвата, потребуется соответствующая функция:

Ruby:
def on_session_open(session)
  print_status("New session opened: #{session.sid}")
end

Чтобы избежать проблем, добавим отписку от обработке событий:

Ruby:
def cleanup
  framework.events.remove_session_subscriber(self)
end

Всё! Так легко и просто можно обрабатывать открытие сессии. Для завершения нашего плагина нужно дописать получение данных из объекта сессии и отправку в телегу:

Ruby:
    def on_session_open(session)
      return unless @active

      begin
        session_info = gеt_session_info(session)
      
        message = "🚀 <b>New session opened!</b>\n\n" +
                 "<b>Host:</b> #{session_info[:host]}\n" +
                 "<b>Platform:</b> #{session_info[:platform]}\n" +
                 "<b>Type:</b> #{session_info[:type]}\n" +
                 "<b>Via:</b> #{session_info[:via]}\n" +
                 "<b>Session ID:</b> #{session_info[:session_id]}\n" +
                 "<b>TTY:</b> #{session_info[:tty]}\n" +
                 "<b>Info:</b> #{session_info[:info]}\n" +
                 "<b>UUID:</b> #{session_info[:uuid]}\n\n" +
                 "<b>Metasploit Server:</b> Test227"
        print_line(message.inspect)
        send_telegram_message(message)
      
        print_status("Notification sent to Telegram for session #{session_info[:session_id]}")
      rescue => e
        print_error("Failed to send notification: #{e.message}")
      end
    end

    private

    def gеt_session_info(session)
      {
        host: session.session_host,
        platform: session.platform,
        type: session.type,
        via: session.via_exploit,
        session_id: session.sid,
        tty: session.try(:tty) || 'N/A',
        info: session.try(:info) || 'N/A',
        uuid: session.try(:uuid) || 'N/A'
      }
    end


Из важных новшеств, стоит обратить внимание на изменения в диспетчере:

Ruby:
      class << self
        attr_accessor :plugin_instance
      end

      def initialize(driver)
        super(driver)
        @plugin = SessionNotifierCommandDispatcher.plugin_instance
      end

Что здесь происходит? Мы создаем статическую переменную (да простят меня адепты Ruby), чтобы через нее передать инстанс нашего основного объекта плагина. Это нужно для того, чтобы из диспетчера были доступны переменные отвечающие за состояние бота. После, переменные плагина будут доступны следующим образом:
Ruby:
        @plugin.bot_token = bot_token
        @plugin.chat_id   = chat_id
        @plugin.active    = true

Перед добавлением диспетчера нужно не забыть выполнить присвоение:

Ruby:
      SessionNotifierCommandDispatcher.plugin_instance = self
      add_console_dispatcher(SessionNotifierCommandDispatcher)

1749292563347.png

Вот как будет выглядеть работа в консоли Metasploit.

Как выглядит само уведомление, можно увидеть выше.

Заключение​

Вот и подошло к концу наше исследование. Если честно, в какой-то момент я себя жестко обрубил, так как материала было слишком много. Хотелось добавить еще и еще, но это постепенно превращало статью из концептуальной в справочную. Да и функционал от плагина к плагину все чаще копировался, а писать один и тот же код, рассматривая его под разными углами, это уже моветон.

Мы и так написали несколько очень крутых плагинов. Попробовали разные варианты взаимодействия с внешним программным обеспечением, познакомились с внутренним устройством объектов Метасплоита, протестировали работу с событийной моделью. Теперь дело за малым, адаптировать плагины под себя. Я уже писал, что слегка адаптировав скрипты из статьи, прошелся по базе из 9800 таргетов и нашел чуть больше сотни новых сессий. Причем, в практически автоматическом режиме. Скорее всего, это следствие невнимательности, лени или недостаточного опыта при прошлых попытках работы. Но даже с учетом этого, мне кажется, довольно неплохой улов, учитывая что скрипты останутся у меня и почти не требуют никаких дополнительных мучений.

Обязательно напишите, как вам статья. Пока прощаюсь с вами, но скоро будет еще один материал про Метасплоит, который точно не оставит никого равнодушным. Надеюсь вам понравилось)
 

Вложения

  • source_codes.zip
    52 КБ · Просмотры: 29


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх