Найти тему
IT Legion

Reverse shell на Python

Оглавление
Давайте поиграем в хакеров
Давайте поиграем в хакеров

Представим что мы проникли на чужой компьютер, и сейчас возникает вопрос; как мы должны передавать ему команды?Именно для этого нужен reverse shell, или «обратный шелл»

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

Эта задача часто встречается не только при разработке приложений, но и при пентесте или участии в CTF

Как мы знаем существуют два низкоуровневых протокола передачи данных в компьютерных сетях, - это UDP (User Datagram Protocol) и ТСР (Transmission Control Protocol)

В крации протокол UDP предназначен для передачи пакетов от одного узла к другому без гарантии доставки. Протокол ТСР гарантирует доставку пакетов до получателя целым и невредимым.

Переходим к практике

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

Подключаем его.

import socket

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

Используем UDP

На каждой из сторон первым делом создаем экземпляр класса socket и устанавливаем для него две константы (параметры).

Переменная для обмена данных выглядит так:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAМ)

Мы создали объект s, который является экземпляром класса socket.

Для этого мы вызвали метод из модуля socket с именем socket и передали ему два параметра - AF_INET и SOCK_DGRAМM.

AF_INET означает, что используется IР-протокол четвертой версии. При желании можно использовать IPv6. Во втором параметре для наших целей мы можем указать одну из двух констант: SOCK_DGRAММ или SOCK_STREAМ. Первая означает, что будет использоваться протокол UDP. Вторая - ТСР. Далее код различается для стороны сервера и клиента.

Сторона сервера

Рассмотрим сначала сторону сервера.

s.bind(('127.О.О.1' , 8888))
result = s.recv(1024)
print ('Message: ', result. decode ('utf-8'))
s. close ()

Здесь s.bind(('127.0.0.1', 8888)) означает, что мы резервируем на сервере (т. е. на нашей же машине) адрес 127.0.0.1 и порт 8888. На нем мы будем слушать и принимать пакеты информации. Здесь стоят двойные скобки, т.к. методу bind() передается кортеж данных - в нашем случае состоящий из строки с адресом и номера порта.

Резервировать можно только свободные порты. Например, если на порте 80 уже работает веб-сервер, то он будет нам мешать.

Далее метод recv() объекта s прослушивает указанный нами порт (8888) и получает данные по одному килобайту (поэтому мы задаем размер буфера 1024 байта).Если на него присылают датаграмму, то метод считывает указанное количество байтов и они попадают в переменную result.

Далее идет всем знакомая функция print(), с помощью которой мы выводим сообщение Message: и декодированный текст. Поскольку данные в result - это текст в кодировке UTF-8, мы должны интерпретировать его, вызвав метод decode('utf-8'). Ну и, наконец, вызов метода clоse() необходим, чтобы остановить прослушивание 8888-го порта и освободить его.

Таким образом, сторона сервера имеет следующий вид:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 8888))
result = s.recv(1024)
print('Message:', result.decode('utf-8'))
s.close()

Сторона клиента

Здесь все гораздо проще.

Для отправки датаграммы мы используем метод класса socket (точнее, нашего экземпляра s ) под названием .sendto() :

s.sendto(b'<Ваше сообщение>', ('127.0.0.1', 8888))

У метода есть два параметра. Первый - сообщение, которое ты отправляешь. Буква b перед текстом нужна, чтобы преобразовать символы текста в последовательность байтов. Второй параметр- кортеж, где указаны IP машины-получателя и порт, который принимает датаграмму.

Таким образом, сторона клиента будет выглядеть примерно так:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"Hello, Server", ('127.0.0.1', 8888))
s.close()

Тестируем

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

Пример: Вывода на стороне сервера UDP
Пример: Вывода на стороне сервера UDP

На стороне клиента мы ничего увидеть не должны, и это логично, потому что мы ничего и не просили выводить.

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

Используем ТСР

Пришло время познакомиться с ТСР.

Точно так же создаем класс s, но в качестве второго параметра будем использовать константу sоск_STREAМ.

s = socket.socket(socket.AF_INET, socket.SOCK_STREAМ)

Сторона сервера

Снова резервируем порт, на котором будем принимать пакеты:

s.bind(('127.0.0.1', 8888))

Дальше появляется незнакомый нам ранее метод listen().

С его помощью мы устанавливаем некую очередь для подключенных клиентов. Например, с параметром listen(5) мы создаем ограничение на пять подключенных и ожидающих ответа клиентов.

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

while True:
|try:
| |client, addr = s.accept()
|except Keyboardinterrupt:
| |s. close()
| |break
|else:
| |result = client.recv(1024)
| |print('Message: ', result.decode('utf-8'))

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

Метод accept() возвращает пару значений, которую мы помещаем в две переменные: в addr будут содержаться данные о том, кто был отправителем, а client станет экземпляром класса socket. То есть мы создали новое подключение.

Теперь посмотрим вот на эти три строчки:

|except Keyboardinterrupt:
| |s.close()
| |break

В них мы останавливаем прослушивание и освобождаем порт, только если сами остановим работу программы. Если прерывания не произошло, то выполняется блок else:

Здесь мы сохраняем пользовательские данные в переменную result, а функцией print() выводим на экран сообщение, которое нам отправлял клиент (предварительно превратив байты в строку Unicode).

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

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_SТREAМ)
s.bind(( '127 .0.0.1', 8888))
s.listen(5)
while True:
|try:
| |client, addr = s.accept()
|except Keyboardinterrupt:
| |s. close()
| |break
|else:
| |result = client. recv(1024)
| |print( 'Message: ' , result.decode('utf-8'))

Сторона клиента

Со стороной клиента опять же все обстоит проще.

После подключения библиотеки и создания экземпляра класса s мы, используя метод connect(), подключаемся к серверу и порту, на котором принимаются сообщения:

s.connect((' 127.0.0.1', 8888))

Далее мы отправляем пакет данных получателю методом send() :

s.send(b'<Ваше сообщение>')

В конце останавливаем прослушивание и освобождаем порт:

s.close()

Код клиента будет выглядеть примерно так:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8888))
s.send(b'Hello, Server')
s. close ()

Тестируем

Запустим в двух разных консолях код сервера и код клиента. На выходе мы должны получить примерно то же самое, что и с протоколом UDP

Пример: Вывода на стороне сервера TCP
Пример: Вывода на стороне сервера TCP

Выводы

В этой статье мы изучили создание простых серверов и клиентов для передачи данных между компьютерами с использованием протоколов UDP и TCP.

Естественно, данного кода будет недостаточно для профессионального взлома Пентагона, но на его основе можно понять, куда двигаться дальше. Изучение основ работы с консолью в OS, плюс базовые навыки работы с Python и встроенной библиотекой socket, позволяют понять, как устроен информационный обмен между приложениями и разновидностями вредоносного ПО, популяция которых не останавливается на одном языке программирования.

Наука
7 млн интересуются