Stéganographie (avancée) de volumes LUKS

De Wiki de Eliott
Sauter à la navigation Sauter à la recherche

Cet article décrit les étapes à suivre pour créer un volume LUKS2 caché à une position précise dans un disque dur.

Mise en garde : Il est fortement recommandé de savoir utiliser la ligne de commande et les commandes de base des distributions GNU/Linux avant de lire cet article (afin d'être à l'aise avec la lecture des commandes présentées) ainsi que de s'être renseigné sur les technologies / outils présentés ici : LUKS2, LVM, dd, partitionnement, périphériques de type block et device-mapper, stéganographie. Si vous pensez être peu à l'aise avec ces technologies, ne suivez PAS cet article.
Mise en garde : Si vous souhaitez vous rendre dans un pays qui interdit le chiffrement (comme l’Australie), cette méthode n'est pas recommandée. En effet, la présence d'un volume chiffré est évidente et impossible à cacher. Pour ces pays, il vaut mieux utiliser une autre technique de stéganographie (qui ne permet pas d'installer un OS) basée sur les fichiers multimédias comme les photos, vidéos et musiques.

Préparation du disque

Mise en garde : Il n'est pas recommandé de remplir un SSD avec des données aléatoires en raison de son mode de fonctionnement. En effet, les SSD répartissent les données écrites sur l'ensemble des cellules de mémoires (wear-leveling) qui le compose afin d'augmenter les performances et l'uniformité de l'usure des cellules. En le remplissant, on supprime tout l'espace disponible et la durée de vie / les performances du disque sont réduites.
Danger : Pour que le volume LUKS soit réellement caché, il ne faut en aucun cas activer le TRIM ou ne pas remplir de données aléatoires la zone du disque qui contiendra le volume LUKS. En effet, un attaquant pourra facilement détecter une anomalie entre le nombre de blocs utilisés par le système de fichiers leurre et le nombre de blocs réellement utilisés et donc déceler la position exacte d'un volume chiffré. La solution la plus sûre reste d'utiliser un disque-dur mécanique (HDD).

Pour remplir un disque-dur de donnés aléatoires, on peut utiliser l'outil de création de volumes LUKS qui dispose d'un mode "plain", c-à-d qui n'utilise pas d'en-tête pour déchiffrer une zone du disque et utilise une unique clé de déchiffrement. Dans ce mode, il n'y a pas de vérification de la validité de la clé de chiffrement ni de la nature du volume a déchiffré. C'est donc un mode assez dangereux mais très utile dans certaines situations comme celle-ci. L'exemple suivant ouvre le disque-dur /dev/sda avec une clé générée par /dev/urandom (l'entrée de --key-file est automatiquement tronquée à la taille de la clé nécessaire pour le cipher).

cryptsetup open --type plain --cipher aes-xts-plain64 --key-file /dev/urandom /dev/sda a_effacer

Ensuite, il faut remplir de zéros le volume déchiffré accessible sous /dev/mapper/a_effacer. Les zéros seront chiffrés donc pas besoin d'utiliser dd avec /dev/urandom.

Mise en garde : Pour un SSD, il est préférable d’arrêter le remplissage du disque de données aléatoires à environ 90% de sa capacité maximale pour limiter les effets indésirables mentionnés plus haut.
dd if=/dev/urandom of=/dev/mapper/a_effacer bs=1M status=progress

Enfin, il ne reste plus qu'à fermer le volume. La clé de déchiffrement sera supprimée à tout jamais étant donnée qu'elle n'a été stockée que dans la mémoire vive.

cryptsetup close /dev/mapper/a_effacer

On aurait très bien pu remplir le disque à effacer directement avec dd (du style dd if=/dev/urandom of=/dev/sda bs=1M status=progress) mais cette méthode est plus longue et pour des disques-durs de plusieurs téraoctets, cela peu prendre plusieurs jours.

Création du système de fichiers leurre

Nous voilà donc avec un disque rempli entièrement de données aléatoires. À ce stade, un adversaire qui réaliserait une image du disque (par exemple dd if=/dev/sda of=./image.bin) et un graphique de l’entropie de l'image réaliserait que le disque est probablement chiffré. L'idée ici est de faire croire à l'adversaire que les données sensibles qu'il recherche se trouvent dans ce volume chiffré. Il faut donc faire en sorte de garder une certaine crédibilité dans le choix des données qui seront stockées dans ce volume ainsi que du choix du mot de passe de déchiffrement.

Le scénario d'attaque type est le suivant : Alice souhaite se rendre aux USA avec son portable qui contient des données confidentielles. Elle réalise toutes les étapes de cet article pour s'assurer que personne sauf elle ne pourra lire les données. Au moment de passer la douane, Alice est retenue par Bob un douanier qui découvre que le disque-dur de Alice est chiffré. Il demande à Alice la clé de déchiffrement. Alice refuse (pour garder une certaine crédibilité) puis au bout d'un certain nombre d’injonctions, accepte et donne la clé. Bob ouvre le volume, cherche des fichiers suspects mais ne trouve rien et laisse passer Alice.

L'avantage de cette solution, c'est qu'il est impossible pour un adversaire de savoir si le disque contient un autre volume chiffré. Il ne peut donc pas forcer une personne à révéler sa clé/mot-de-passe de déchiffrement.

La commande suivante va installer un volume LUKS2 sur le disque dur fraîchement rempli de données aléatoires. Ce volume est composé d'un en-tête (contenant les paramètres de déchiffrement, les caractéristiques du volume, et autres informations liées au chiffrement du volume) ainsi que d'une zone contenant les données chiffrées faisant la taille du disque/partition. Un mot de passe va être demandé, il faut absolument choisir un mot de passe crédible aux yeux de l'adversaire mais pas trop compliqué à retenir.

cryptsetup luksFormat --type luks2 /dev/sda

Ensuite l'on va ouvrir ce volume :

cryptsetup open /dev/sda fake_crypt

Puis l'on va le formater. Attention au choix du type de système de fichiers. En effet, la plupart des systèmes de fichiers modernes (comme EXT*, BTRFS, NTFS) écrivent, lors de leur création, un ensemble de données nécessaires à leur fonctionnement et réparties sur l’ensemble de la partition/volume. Ainsi, lors de la création du volume caché, ces données pourraient être détruites ce qui pourrait trahir la présence d'un volume caché. Une solution de contournement pourrait être de créer plusieurs volumes cachés entre ces zones de données et de les lier comme un seul volume virtuel avec une table device-mapper linéaire ce qui pourrait permettre de donner encore plus de crédibilité face à un adversaire. FAT32 est l'un des rares systèmes de fichiers à ne pas écrire des informations réparties sur l'ensemble de la partition/volume.

mkfs.fat -F32 /dev/mapper/fake_crypt

Ensuite, l'on peut monter le volume et l'on peut copier les fichiers qui serviront de leurre.

mount /dev/mapper/fake_crypt /media/fake_crypt

Création du volume caché

Nous avons donc notre volume LUKS2 leurre en place. Il faut maintenant créer le volume caché qui sera de type LUKS2 sans en-tête et avec un fichier comme clé de déchiffrement. Le problème avec LUKS2, c'est qu'il n'est pas possible de choisir l'emplacement exact du début et de la fin du volume dans un autre volume. LUKS2 prend forcément tout l'espace disponible dans le volume/disque/partition. Pour contourner ce problème, l'on va créer un volume virtuel situé à l’emplacement souhaité dans le volume/disque/partition existant.

Calcul de la taille du volume LUKS2 leurre

Mais avant, il faut calculer le nombre de secteurs de disques utilisés par le volume LUKS2 leurre ainsi que ses données. Il faut procéder en 2 étapes : l'en-tête et les données.

Pour l'en-tête, il faut réaliser une sauvegarde de celle-ci :

cryptsetup luksHeaderBackup /dev/sda --header-backup-file ./luks_fake_header.bin

Puis calculer sa taille en secteurs :

POSIXLY_CORRECT=1 du ./luks_fake_header.bin

A noter que le POSIXLY_CORRECT=1 permet d'utiliser des secteurs de 512 octets au lieu de 2048 par défaut. Pour savoir quelle taille de secteurs utilise un disque, on peut utiliser gdisk -l /dev/sda.

Ensuite, il faut calculer la taille en secteur des données (fake_crypt étant le nom du volume LUKS2 leurre) :

POSIXLY_CORRECT=1 df /dev/mapper/fake_crypt

Choix d'une position aléatoire du volume caché

Il faut maintenant choisir une position aléatoire (en secteurs) du futur volume caché. En effet, si l'on décide de prendre comme secteur de début le premier secteur après le dernier secteur occupé par le volume LUKS2 leurre et comme secteur de fin le dernier secteur du disque/partition/volume, un attaquant en possession de la clé de déchiffrement du volume caché ainsi que de son en-tête pourrait très bien deviner la position du volume et tenter de le déchiffrer. Cela permet également de garder une marge avec le volume LUKS2 leurre en cas de modification de son contenu.

La commande suivante va stocker dans la variable SECT_DEBUT une valeur aléatoire entre le dernier secteur utilisé par le volume LUKS2 leurre et une valeur choisie (si possible avec un écart d'au moins 1Go par rapport à la première valeur).

SECT_DEBUT=$(shuf -i dernier_secteur_occupé_leurre-autre_valeur1 -n1)

Pour choisir la position du dernier secteur, il faut connaître la taille en secteur du disque/partition/volume :

blockdev --getsz /dev/sda

On peut ensuite réaliser la même opération qu'avec le premier secteur :

SECT_FIN=$(shuf -i autre_valeur2-dernier_secteur_disque -n1)

Puis l'on calcule la taille du volume caché :

TAILLE_VOL=$((${SECT_FIN} - ${SECT_DEBUT}))

Création du volume caché

Maintenant que l'on a la taille du volume caché ainsi que le secteur de début de celui-ci, l'on peut créer le volume virtuel à l'aide de dmsetup. À noter que dmsetup n'utilise pas de fichier d'en-tête (comme cryptsetup en mode plain), il faut donc re-rentrer la commande suivante à chaque ouverture du volume caché. Ici le volume virtuel s'appellera virt_hidden.

dmsetup create virt_hidden --table "0 ${TAILLE_VOL} linear /dev/sda ${SECT_DEBUT}"

On peut créer le fichier-clé qui servira à déverrouiller le volume LUKS2 (8192k étant la taille maximale du fichier de clé supporté par LUKS2 en mode aes-xts-plain64 avant que celui-ci ne soit tronqué) :

dd if=/dev/urandom of=./luks-key.bin bs=1k count=8192

Ensuite, l'on peut passer à la création du volume LUKS2 caché sans en-tête avec un ficher-clé :

cryptsetup luksFormat --type luks2 --key-size 512 --hash sha512 --resilience-hash sha512 --iter-time 5000 --key-file ./luks-key.bin --header ./luks-header.bin /dev/mapper/virt_hidden


À noter que lors de la fermeture du volume caché, il faut d'abord fermer le volume LUKS2 puis fermer le volume virtuel avec dmsetup remove /dev/mapper/virt_hidden.

Exemple de partitionnement du volume

Après avoir créé le volume LUKS2 caché, il peut être utile d'installer un OS dessus. Voici un exemple de partitionnement possible (datacrypt représente le volume LUKS2 caché et datalvm le VG) :

pvcreate /dev/mapper/datacrypt
vgcreate datalvm /dev/mapper/datacrypt
lvcreate datalvm -l 50%FREE -n root
lvcreate datalvm -l 100%FREE -n home
mkfs.btrfs /dev/datalvm/root
mkfs.btrfs /dev/datalvm/home