Cet article a pour but de présenter la résolution du challenge CRYPTO unlucky présenté à la quinzième édition de la nuit du hack . Ce challenge n’a pas été résolu lors de la wargame (du moins dans le temps imparti) et était récompensé de 350 points.
Je tiens à préciser que je ne suis pas l’auteur du chall et n’ai aucune connaissance de la personne qui aurait pu l’écrire. Ce qui implique que ce chall était donc réalisable par une personne tierce.
Présentation de l'épreuve
L’épreuve se présente sous forme d’un fichier texte
Contenant une clé publique d’un serveur 32 bits, un message chiffré avec cette clé publique (le message que nous devrons déchiffrer) ainsi que 61 signatures interceptés avec le clair correspondant.
L’algorithme de chiffrement utilisé ici est RSA avec des clés 4096 bits, ne présentant à première vue aucun signe de faiblesse.
Nous savons également que le serveur utilise le language Go 1.5.1.
CVE-2015-8618
Je remercie mon partenaire @_Bytemare de m’avoir rapidement trouvé cette CVE concernant une mise à jour de sécurité de Go v1.5.3.
En effet, la vulnérabilité publiée en 2015 concerne une bibliothèque de mathématique de Go (math/big) qui est utilisée pour le chiffrement RSA. Celle-ci a la probabilité d’effectuer une erreur de calcul de 1/2^26 (1 fois sur 64 millions) sur une architecture 32 bits. Si cette erreur intervient lors d’un chiffrement RSA_CRT, cela pourrait permettre à un attaquant d’en déduire la clé privée (Détails expliqués plus bas). La CVE n’en dit pas plus quant à son exploitation, aucun POC n’est trouvable sur le net.
Cette CVE correspond parfaitement à notre scénario.
Néanmoins, afin de pouvoir exploiter une telle vulnérabilité nous allons être obligés de passer par un peu de mathématiques…
Un peu de maths
Nous allons voir dans cette partie comment avec les informations dont nous disposons, pouvons monter une attaque afin de récupérer la clé privée.
Pour ceux dont ils ne s’intéressent juste à casser du chiffrement sans vouloir savoir comment cela se fait, je vous suggère de vous diriger directement vers la partie Résumé de l’exploitation.
Ce n’est pas mal de faire des preuves sur le papier… Mais c’est mieux quand c’est appliqué ;)
Si nous récapitulons, voici notre feuille de route pour pouvoir résoudre ce challenge :
Trouver une mauvaise signature dans la liste des signatures données
Récupérer le message faux ainsi que le message clair sous forme d'entiers afin de pouvoir effectuer des calculs
Calculer q = pgcd(message_faux - message, N)
En déduire l'exposant privé d
Construire une clé privée utilisable par une bibliothèque de cryptographie(ex : format PEM)
Déchiffrer
I - Trouver une mauvaise signature
La première étape de notre exploitation consiste à retrouver parmi les signatures données, une qui serait mauvaise. Voici le script permettant de vérifier une signature :
note : le script proposé ci-dessus permet d’afficher sur le terminal la valeur du clair et la vérification se fait manuellement. Il est possible d’effectuer cette recherche de façon automatique avec un code plus élaboré.
Après avoir testé chaque signature une par une, nous trouvons enfin une signature erronée :
En effet, au lieu de récupérer le clair “Pic Sans Nom (3913 m)”, nous nous retrouvons avec un binaire complétement incompréhensible !
Nous rappelons que la probabilité de tomber sur une telle signature avec une base de 61 signatures est exactement de 1 chance sur 524 590, ce qui explique le titre “unlucky”
II - Transformer les messages en entiers
Afin que nous puissions effectuer nos calculs mathématiques, il faut d’abord convertir deux messages (le mauvais ainsi que le clair) sous forme d’entiers.
Pour le message erroné, pas trop de problèmes, il suffit d’ajouter les lignes suivantes au code précédent :
ce qui nous permet de récupérer la valeur mFault (en hexadecimal) :
Pour le message clair, c’est un peu plus compliqué car il faut lui appliquer le padding adéquat pour que les calculs soient bons… soit PCKS1. Après une dizaine de minutes de recherches sur le net qui ne mènent à rien, j’ai décidé d’utiliser une méthode un peu maison, j’ai récupéré un message d’un même nombre de caractères que ma chaîne complété d’un padding PCKS1, et ai changé les caractères de cette chaîne par les miens… ce n’est pas une procédure habituelle… mais ça fonctionne !
Voici donc la valeur de notre message clair :
Nous avons maintenant effectuer tout les calculs que nous souhaitons avec ces messages !
III - Calcul de l'exposant privé d
Nous rappelons les étapes pour pouvoir déterminer cet exposant privé :
Calculer q = pgcd(message_faux - message, N)
Calculer p = N / q
Calculer phi = (p-1) * (q - i)
Calculer d par le biais de l'algorithme d'Euclide étendu appliqué sur e et phi
Voici le code python pour effectuer l’ensemble de ces étapes :