В статье из прошлого номера мы познакомились с теоретической частью и провели небольшой «пентест» WordPress. В этой статье я буду призывать тебя копать глубже, вспомнив, что хакер — это в первую очередь программист :). Действительно, несмотря на то что счет встроенных в Metasploit модулей уже пошел на тысячи, рано или поздно любой, кто плотно работает с фреймворком, сталкивается с необходимостью расширить его функциональность собственными эксплоитами и вспомогательными модулями. Сегодня мы рассмотрим все необходимые аспекты создания модуля с нуля, а заодно коснемся особенностей языка Ruby, которые нужно учитывать при написании.

WARNING

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

Организация файлов в MSF

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

Первая — это директория, куда был установлен фреймворк. Например, в Kali Linux это /usr/share/metasploit-framework. В папке lib, помимо прочего, мы можем обнаружить библиотеку Rex, которая предоставляет базовые методы для работы с файлами и протоколами. Основа фреймворка — библиотеки Core и Base располагаются в директории lib/msf. Практикующим основательный подход можно начать изучение исходных кодов с класса Module из lib/msf/core, который определяет базовое поведение модулей. От него, в свою очередь, наследуются классы, которые определяют тип модуля Auxiliary, Exploit, Payload и так далее (их можно найти там же). Модули, идущие в поставке с фреймворком, можно обнаружить в директории modules, где они разбиты по типам.

Вторая локация — директория .msf4 в домашней папке пользователя. Именно она рекомендуется авторами фреймворка в качестве основного хранилища всех внешних модулей. Ее структура должна быть аналогична структуре папки modules. Например, если ты скачал эксплоит от внешнего разработчика или написал его сам, то, чтобы MSF распознал его, он должен находиться по пути ~/.msf4/modules/exploits. Внутри этой локации рекомендуется сортировать модули по подпапкам, например по операционным системам — MSF распознает их. Например, путь ~/.msf4/modules/exploits/windows/module_name будет валиден с точки зрения фреймворка.

 

Архитектура модуля

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

Подключим библиотеки, составляющие ядро фреймворка, которые располагаются в msf/core, при помощи ключевого слова require:

require 'msf/core'

Далее необходимо отнаследоваться от класса, который определит тип модуля. В нашем случае это будет Auxiliary.

class Metasploit3 < Msf::Auxiliary

Название класса Metasploit3 определяет версию фреймворка, для которого пишется модуль.

Определим метод initialize, который выполняет функцию конструктора класса в Ruby.

def initialize
  super(
    'Name' => 'Hello world Module',
    'Description' => 'Useless module for education purposes',
    'Author' => 'Xakep',
    'License' => MSF_LICENSE
  )
end

Ключевое слово super используется в Ruby при наследовании, когда не требуется полностью переопределять метод, а необходимо лишь дополнить его поведение. Происходит вызов метода с таким же именем в родительском классе, и ему передаются необходимые параметры. В данном случае будет вызван метод initialize родительского класса Auxiliary, а в качестве параметров ему будет передан хеш (пары ключ — значение) с метаданными: название модуля, его описание, автор и лицензия.

Следующее, что потребуется определить, — это непосредственно сам метод, который будет выполнять какое-либо полезное действие. В нашем случае он будет выводить строку c приветствием. Естественно, в реальных модулях методов будет гораздо больше, в чем ты сможешь убедиться, исследовав встроенные модули MSF. Название метода должно быть run.

def run
  puts 'Hello from MSF module!'
end

Приведу полный код нашего первого модуля:

require 'msf/core'

class Metasploit3 < Msf::Auxiliary
  def initialize
    super(
      'Name' => 'Hello world Module',
      'Description' => 'Useless module for education purposes',
      'Author' => 'Xakep',
      'License' => MSF_LICENSE
    )
  end

  def run
    puts 'Hello from MSF module!'
  end
end

Сохраняем его в директорию ~/.msf4/modules/auxiliary/xmodules в файл под названием hello.rb. Теперь запускаем консоль Metasploit (командой msfconsole). После загрузки консоли можно обратить внимание, что число auxiliary-модулей увеличилось на один.

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

Сообщение об ошибке при загрузке модуля
Сообщение об ошибке при загрузке модуля

Мы можем найти наш модуль по слову hello либо сразу выбрать его для работы:

use auxiliary/xmodules/hello

Как можно заметить, команде use мы передаем структуру, которая повторяет путь к файлу (начиная от директории modules), без расширения. Осталось только запустить модуль командой run и увидеть сообщение, которое мы указали в одноименном методе.

Результат запуска нашего простейшего модуля
Результат запуска нашего простейшего модуля
 

Простой модуль для работы с SSH

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

Предлагаю решить следующую задачу: дан список IP-адресов, нужно написать auxiliary-модуль, который определит, доступен ли этот сервер по SSH, и, если доступен, определит возможные типы авторизации.

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

Подключим необходимые библиотеки.

require 'msf/core'
require 'net/ssh'
require 'stringio'
require 'logger'

Отметим, что подключается модуль Scanner, который содержит готовые методы для соответствующих операций.

class Metasploit3 < Msf::Auxiliary
  include Msf::Auxiliary::Scanner

Здесь нужно понять механизм примесей (mixin), который довольно активно используется при написании модулей. В контексте Ruby также существует понятие модуля — это способ организации методов, классов и констант, он может использоваться как аналог пространств имен в других языках (namespaces) либо для реализации своего рода множественного наследования, которое и получило название mixin. Для его реализации необходимо подключить библиотеку, содержащую модуль (в данном случае он является частью msf/core), а затем в нужном месте подключить модуль при помощи ключевого слова include. Так класс нашего модуля унаследует все методы базового модуля Scanner.

Код метода initialize будет уже знакомым.

def initialize
  super(
    'Name' => 'SSH Auth type checker',
    'Description' => 'Check host availability by SSH and detect auth type',
    'Author' => 'Xakep',
    'License' => MSF_LICENSE
  )

  register_options(
    [
      Opt::RPORT(22)
    ], self.class
  )
end

Можно заметить, что здесь я зарегистрировал опцию RPORT. Это те самые опции, которые выставляются при помощи команды set в консоли Metasploit. Кстати, если ты посмотришь информацию о модуле командой info, то увидишь и другие опции: они зарегистрировались модулем Scanner, который я упоминал.

Опции, доступные нашему модулю
Опции, доступные нашему модулю

Метод run также реализован в Scanner, и в данном случае нет смысла его переопределять. Зато этот модуль предоставляет нам более удобный метод под названием run_host, которому передается IP-адрес. Этот код будет выполняться для каждого хоста.

  def run_host(ip)
    debug_info = StringIO.new
    log = Logger.new debug_info

    opts_hash = {
      port: datastore['RPORT'],
      verbose: Logger::DEBUG,
      logger: log,
      timeout: 5
   }

  begin
    Net::SSH.start(ip, 'user', opts_hash) do
      method = extract_succeded_method(debug_info.string)
      print_good("#{ip} - connection established, auth method: #{method}")
    end
  rescue Net::SSH::AuthenticationFailed
    methods = extract_auth_methods(debug_info.string)
    print_debug("#{ip} - auth failed, available methods: #{methods}")
  rescue Timeout::Error, Errno::ECONNREFUSED, Rex::ConnectionRefused
    print_error("#{ip} - host doesn't respond")
  end
end

В нем мы сначала определяем переменные служебного характера, они требуются для того, чтобы лог подключения писался в переменную, а не выводился на консоль. Затем определяется хеш с опциями. В нем мы указываем, что порт берем из опции RPORT, которую определили в методе initialize, далее выставляем «многословность» логирования в режим Debug, чтобы получить интересующую нас информацию, определяем какому экземпляру объекта Logger отдавать лог, и последним параметром идет тайм-аут подключения.

Теперь при помощи модуля Net::SSH (мы подключали его в начале, он входит в состав MSF) запускаем сессию, передав ей IP-адрес хоста, имя пользователя (в данном случае оно неважно, поэтому я поставил просто 'user') и хеш с настройками. Далее все просто: если вдруг удалось подключиться и авторизоваться, то выводим соответствующее сообщение, где вытягиваем из лога метод, которым было авторизовано соединение. Если авторизация провалилась (что в данном случае ожидаемый исход), то мы достанем из лога доступные методы авторизации для данного хоста. Если же хост недоступен по разным причинам, то напечатаем соответствующее сообщение. Обрати внимание на специальные методы для вывода информации на консоль MSF: print_good, print_debug и print_error. Они нужны, чтобы подсвечивать результаты соответствующими символами, для более легкого чтения.

Для получения информации из лога я написал два вспомогательных метода, которые достают при помощи регулярных выражений нужные строчки (далеко не самый оптимальный вариант, здесь используется для простоты).

def extract_auth_methods(debug_info)
  debug_info
    .match(/allowed\smethods:\s(.+)/)
    .captures
    .first
end

def extract_succeded_method(debug_info)
  debug_info
    .match(/(.+)\ssucceeded/)
    .captures
    .first
end

Привожу полный код модуля:

require 'msf/core'
require 'net/ssh'
require 'stringio'
require 'logger'

class Metasploit3 < Msf::Auxiliary
  include Msf::Auxiliary::Scanner

  def initialize
    super(
      'Name' => 'SSH Auth type checker',
      'Description' => 'Check host availability by SSH and detect auth type',
      'Author' => 'Xakep',
      'License' => MSF_LICENSE
    )

    register_options(
      [
        Opt::RPORT(22)
      ], self.class
    )
  end

  def run_host(ip)
    debug_info = StringIO.new
    log = Logger.new debug_info

    opts_hash = {
      port: datastore['RPORT'],
      verbose: Logger::DEBUG,
      logger: log,
      timeout: 5
    }

    begin
      Net::SSH.start(ip, 'user', opts_hash) do
        method = extract_succeded_method(debug_info.string)
        print_good("#{ip} - connection established, auth method: #{method}")
      end
    rescue Net::SSH::AuthenticationFailed
      methods = extract_auth_methods(debug_info.string)
      print_debug("#{ip} - auth failed, available methods: #{methods}")
    rescue Timeout::Error, Errno::ECONNREFUSED, Rex::ConnectionRefused
      print_error("#{ip} - host doesn't respond")
    end
  end

  def extract_auth_methods(debug_info)
    debug_info
      .match(/allowed\smethods:\s(.+)/)
      .captures
      .first
  end

  def extract_succeded_method(debug_info)
    debug_info
      .match(/(.+)\ssucceeded/)
      .captures
      .first
  end
end

Теперь осталось только выбрать модуль в консоли MSF, указать адреса хостов через установку опции RHOSTS, запустить модуль и увидеть результат.

Результат выполнения нашего модуля
Результат выполнения нашего модуля
 

Домашнее задание

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

 

Итог

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

  • Подключаем необходимые библиотеки при помощи ключевого слова require. При построении модулей можно (и нужно) использовать библиотеки, которые располагаются в директории lib. Прежде всего это Rex и Core, в которых реализовано множество готового функционала.
  • Объявляем класс, который наследуется от базового класса соответствующего типу модуля, в зависимости от желаемого поведения модуля.
  • В Ruby нет множественного наследования, зато есть механизм примесей, который позволит тебе подключать нужные модули при помощи ключевого слова include после объявления класса.
  • Определяем методы:
    • initialize — в него вносим информацию о модуле и определяем опции;
    • методы, выполняющие полезное действие, в зависимости от типа модуля и нужд.

Оставить мнение