Le proxy TCP fonctionne comme un point de passage réseau qui intercepte et redirige les connexions entre un client et un serveur. Il établit une liaison via un hôte intermédiaire pour relayer les données de manière transparente.
Objectifs de la mise en œuvre
Ce proxy doit assurer plusieurs fonctions clés : visualiser les échanges entre les entités, manipuler les paquets en transit, et gérer les connexions socket de manière fiable.
Affcihage des données échangées
Pour analyser le trafic, une fonction de dump hexadécimal convertit les données brutes en une représentation lisible, montrant à la fois les valeurs hexadécimales et les caractères imprimables.
def hex_dump(info, step=16, output=True):
if isinstance(info, bytes):
info = info.decode('latin-1') # Décodage pour gérer tous les octets
segments = []
for position in range(0, len(info), step):
portion = info[position:position + step]
hex_values = ' '.join(f'{byte:02x}' for byte in portion.encode('latin-1'))
ascii_repr = ''.join(chr(b) if 32 <= b < 127 else '.' for b in portion.encode('latin-1'))
segments.append(f"{position:04x} {hex_values:<{step*3}} {ascii_repr}")
if output:
for line in segments:
print(line)
else:
return segments
Traiteemnt des flux de données
Les fonctions de réception récupèrent les données des sockets avec un délai d'attente, tandis que les gestionnaires permettent de modifier les requêtes et réponses avant leur transfert.
def obtenir_donnees(connexion):
accumulateur = b""
connexion.settimeout(5)
try:
while True:
fragment = connexion.recv(4096)
if not fragment:
break
accumulateur += fragment
except:
pass
return accumulateur
def ajuster_requete(contenu):
# Ici, on peut insérer des transformations sur les requêtes
return contenu
def ajuster_reponse(contenu):
# Ici, on peut insérer des transformations sur les réponses
return contenu
def gerer_proxy(socket_client, hote_cible, port_cible, recevoir_en_premier):
socket_cible = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_cible.connect((hote_cible, port_cible))
if recevoir_en_premier:
reponse_cible = obtenir_donnees(socket_cible)
hex_dump(reponse_cible)
reponse_cible = ajuster_reponse(reponse_cible)
if reponse_cible:
socket_client.send(reponse_cible)
while True:
requete_client = obtenir_donnees(socket_client)
if requete_client:
print(f"[*] Reçu {len(requete_client)} octets du client.")
hex_dump(requete_client)
requete_client = ajuster_requete(requete_client)
socket_cible.send(requete_client)
reponse_cible = obtenir_donnees(socket_cible)
if reponse_cible:
print(f"[*] Reçu {len(reponse_cible)} octets du serveur.")
hex_dump(reponse_cible)
reponse_cible = ajuster_reponse(reponse_cible)
socket_client.send(reponse_cible)
if not requete_client and not reponse_cible:
socket_client.close()
socket_cible.close()
break
Gestion des connexions réseau
Le serveur proxy écoute sur un port local et crée un nouveau thread pour chaque connexion entrante, déléguant le traitement à la fonction de proxy.
def demarrer_serveur(ecoute_hote, ecoute_port, destination_hote, destination_port, recevoir_en_premier):
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
serveur.bind((ecoute_hote, ecoute_port))
except Exception as err:
print(f"Échec de liaison sur {ecoute_hote}:{ecoute_port} : {err}")
sys.exit(1)
serveur.listen(5)
print(f"En écoute sur {ecoute_hote}:{ecoute_port}")
while True:
client_socket, adresse = serveur.accept()
print(f"Connexion entrante de {adresse[0]}:{adresse[1]}")
fil = threading.Thread(
target=gerer_proxy,
args=(client_socket, destination_hote, destination_port, recevoir_en_premier)
)
fil.start()
Validation de l'application
Pour tester le proxy, configurez un environnement avec deux machines virtuelles : l'une exécutant le proxy sur le port 9000, et l'autre agissant comme client et serveur distant. Observez les journaux pour vérifier le relais des données.