Consignes

Le TP est à rendre, par email, avant le 24 décembre à 23h59...

Pour ce TP, la seule chose à m'envoyer sera un fichier prenom-nom.c contenant votre programme. Ce fichier sera utilisable directement avec les autres fichiers fournis pour le TP.

Le non-respect de cette consigne risque de me mettre de mauvaise humeur...

Attention, le seul fait que votre programme fonctionne ne suffit pas pour avoir une bonne note. Les points suivants seront pris en compte :

  1. l'architecture de votre programme (code découpé en fonctions etc.),
  2. la lisibilité de votre programme (choix pertinent pour les noms de variables etc.),
  3. la présence de commentaires aux endroits appropriés,
  4. la présence de documentation pour vos fonctions.

Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :

Liens utiles

1. Rappels : RC4

« RC4 » peut-être vu comme un générateur pseudo-aléatoire pour crypter un flot de données sur le modèle du « bloc-notes à usage unique » : chaque octet du flot de données est combiné (avec un XOR) avec l'octet correspondant de RC4. Il n'existe, à ce jours, pas d'attaque pour RC4 général.

RC4 comporte deux phases :

  1. initialisation à partir d'une clé de taille t, entre 1 et 256 octets,
  2. génération d'octets aléatoires.

En pseudo C, si on note "cle" la clé, et "t" sa taille :

KSA:  (* Key Scheduling Algorithm *)
  for(i = 0; i < 256; i++)
    etat.S[i] = i

  j = 0
  for(i = 0; i < 256; i++)
    j = (j + etat.S[i] + cle[i % t])  %  256
    etat.S[i]  <->  etat.S[j]    (* échange *)
  etat.i = 0
  etat.j = 0

et

PRGA:  (* Pseudo Random Generator Algorithm *)
  etat.i = (etat.i + 1)  %  256
  etat.j = (etat.j + etat.S[etat.i])  %  256
  etat.S[etat.i]  <->  etat.S[etat.j];  (* échange *)
  return etat.S[ (etat.S[etat.i] + etat.S[etat.j])  %  256]

Programmez une fonction

void code_RC4_drop(char *K, char *M, int n)

qui affiche les octets, en hexadécimal (format "%02X" pour printf), du codage de la chaîne M avec la clé K.

L'argument "n" sera le nombre d'octets initiaux qu'on jette lors de la génération. (Autrement dit, on attend "n" octets générés avant de commencer à coder.) On appelle parfois ce codage "RC4-drop[n]".

Estimation du temps : moins 30 minutes !
  • Pour obtenir la taille d'une chaîne de caractères, vous pouvez utiliser strlen,
  • utilisez la définition "typedef unsigned char octet;" pour définir un type octet,
  • pour convertir explicitement un "char" en "octet", il faut faire un "cast" : "(octet) c" (ce n'est probablement pas nécessaire),
  • vérifiez vos calculs avec les vecteurs de test, (l'argument "n" prend la valeur 0 dans ce cas)
  • pas la peine de m'envoyer le fichier ".c" correspondant...

2. WEP et RC4

2.1. Introduction

Le WEP ("Wired Equivalent Privacy") crypte chaque paquet en utilisant deux clés :

  1. la clé WEP, sur 40 ou 104 bits, càd sur 5 ou 13 octets,
  2. une "clé" temporaire, ou vecteur d'initialisation sur 24 bits, càd sur 3 octets.

Chaque paquet est codé avec RC4 en utilisant la clé "vecteur d'initialisation + clé WEP" (sur 8 ou 16 octets donc) et est envoyé avec son vecteur d'initialisation.

Pour décoder, on récupère le vecteur d'initialisation, concatène la clé WEP derrière et décode avec RC4.

De cette manière, on ne peut décoder que si on possède la clé WEP, et on utilise quand même des clés différentes pour chaque paquet. (C'est important, car sinon, il suffirait d'un message décodé pour retrouver le début de la suite pseudo-aléatoire, et donc de pouvoir décoder tous les messages de taille inférieure.

2.2. Attaque de Fluhrer, Mantin et Shamir

En 2001, Fluhrer, Mantin et Shamir ont montré que l'utilisation de RC4 pour WEP rendait la clé facilement craquable. Il suffit de récupérer un nombre relativement faible de paquets cryptés dont on connaît le premier octet. (On garde également leurs vecteurs d'initialisation...)

Nous avons vu l'attaque de Fluhrer, Mantin et Shamir en cours. Pour ce TP, vous devrez l'implanter pour vérifier qu'on peut effectivement récupérer une clé à partir de suffisamment de paquets codés.

Rappel : si on connaît les "n" premiers octets de la clé WEP,

  1. on récupère le vecteur d'initialisation IV,
  2. on calcule le début du KSA (les 3+n premières étapes)
  3. on vérifie si on avait un vecteur d'initialisation faible :
    • "P[1] < n+3"
    • "P[1] + P[P[1]] == n+3"
  4. si oui, on prédit la clé avec "prediction = P'[o] - j - P[n+3]", où "P'[o]" est la position de o (premier octet généré par le PRGA) dans la permutation P.

On garde la prediction qui apparaît le plus souvent.

Pour récupérer l'archive contenant les fichiers pour la suite du TP, remplacez tp3.html dans l'url du TP par TP3/tp3-XXXXXXXXXXXXXX.tar, où XXXXXXXXXXXXXX est le codage RC4 de "info528" en utilisant la clé "TP3" et en jetant les 1024 premiers octets du générateur pseudo-aléatoire. (Argument "n" à 1024 donc.) Attention, les chiffres hexadécimaux sont en majuscules (printf("%02X",o)).

L'archive du TP contient un répertoire info528-TP3 qui contient les fichiers :

Écrivez la fonction


/**********************************************************************
 * devine le "n"-ième octet de la clé (en supposant qu'on connaît les *
 * n-1 octets précédents) en essayant tous les vecteurs               *
 * d'initialisations faibles.                                         *
 * L'argument "cle_wep_partielle" contient le début de la clé (octets *
 * 0 à "n"-1, non compris).                                           *
 * La fonction ne modifie pas les "n"-1 premières valeurs de          *
 * "cle_wep_partielle".                                               *
 *                                                                    *
 * L'argument "nb_IV_faibles" est le nombre de vecteurs faibles à     *
 * considérer.                                                        *
 * L'argument "nb_IV_total" est le nombre total de vecteurs à tester. *
 *                                                                    *
 * La fonction s'arrête quand l'une des deux bornes est atteinte.     *
 *                                                                    *
 * Si les 2 bornes sont nulles, on utilise alors uniquement la        *
 * famille de vecteurs (n+3, 255, ...). ATTENTION, ça n'est possible  *
 * que si "taille_IV = 3".                                            *
 *                                                                    *
 * La fonction renvoie "n" si l'opération c'est bien passée. Dans ce  *
 * cas, l'octet "n"-1 de "cle_wep_partielle" est positionné à sa      *
 * valeur.                                                            *
 *                                                                    *
 * La fonction renvoie "-1" en cas de problème. La "n"-ième valeur de *
 * "cle_wep_partielle" est alors quelconque.                          *
 **********************************************************************/
int predit_octet(int n, octet *cle_wep_partielle, int nb_IV_faibles, int nb_IV_total)
{
  ...

Je vous conseille d'écrire au moins une fonction auxiliaire du style :

/**********************************************************************
 * teste un vecteur d'initialisation :                                *
 *   - "IV" est le vecteur en question                                *
 *   - "n" est l'octet de la clé que l'on recherche                   *
 *   - "cle_partielle" est la clé partielle que l'on a déjà calculée  *
 *                                                                    *
 * La fonction renvoie 1 si le vecteur était faible, et dans ce cas,  *
 * la prédiction correspondante se trouve dans le dernier paramètre   *
 * "prediction".                                                      *
 **********************************************************************/
int teste_IV(octet *IV, int n, octet *cle_partielle, octet *prediction)
{
  ...

La fonction principale fournie (fichier test.c et exécutable test produit par le fichier Makefile permet de tester facilement votre fonction predit_octet :

$ ./test

tire une clé WEP au hasard sur 13 octets et essaie de la craquer.

$ ./test -h

vous affichera un message d'aide. Lisez-le !

Si on donne des valeurs négatives à nb_IV_faibles et nb_IV_total, votre fonction utilisera uniquement les vecteurs d'initialisation de la forme { n+3, 255, X }, pour X = 0, 1, ... 255 qui sont presque tous faibles.

Commencez par faire uniquement cette partie car elle est beaucoup plus rapide. (Seul une toute petite partie des vecteurs d'initialisations est faible au sens vu en cours.)

Le fichier tp3-nom-prenom.c fournit contient une variable globale verbose qui est mise à jours lorsqu'on utilise le drapeau -v sur la ligne de commande. Servez-vous en pour contrôler les affichages de vos fonctions.

2.3. Tests

  1. quelle proportion de clé WEP arrivez-vous à craquer uniquement avec les vecteurs de la forme { n+3, 255, X } ?
  2. combien de vecteurs faibles faut-il considérer pour arriver à craquer une clé de 5 octets ? (Les vecteurs d'initialisations sont toujours à 3 octet.) Combien de vecteur d'initialisation faut-il considérer pour trouver ce nombre de vecteurs faibles ?
  3. même question pour des clés WEP de 13 octets.

Vous pouvez récupérer ici et deux fichiers objets contenant le code compilé de rc4.c avec une clé secrète. Les seules différences avec rc4.c sont :

Écrivez une petite fonction principale dans un fichier craque.c qui essaie de récupérer les deux clés secrètes. Mettez-les en commentaire en haut de votre fichier nom-prenom.c. (Pas besoin de m'envoyer votre fichier "craque.c".)

Pour ceux qui sont sur une architecture 64 bits, voir plutôt ici et .

2.4. Ouverture

  1. décrivez les étapes à mettre en oeuvre pour mener une telle attaque
  2. proposez des solutions pour corriger le problème.