La plupart des appareils contiennent au moins un port USB avec des pilotes associés qui peuvent être très différents et dont la sécurité mérite d’être éprouvée. Nous verrons ici un rappel sur le protocole USB et comment l’attaquer en boîte noire.
Dans des missions d'audit de systèmes embarqués, il est courant de découvrir des ports USB exposés. Ils constituent une surface d'attaque importante dans notre recherche de vulnérabilités.
Lors de ces audits, le code source du système n'est pas toujours disponible et nous devons nous attaquer au système en boîte noire. Que peut-on faire face à un port USB exposé ? Certains outils tels que le Facedancer permettent d'effectuer un scan des appareils USB supportés par le système et des tests de fuzzing.
Dans cet article, nous allons voir le fonctionnement général du protocole USB, les différents outils disponibles pour s'attaquer à une interface USB en boîte noire et enfin les limites d'une telle démarche.
1. Fonctionnement du protocole USB
L’Universal Serial Bus (USB) est un protocole complexe, qui existe depuis 1996. Nous détaillons ici brièvement certaines notions du protocole. Pour avoir plus d’informations, le lecteur est invité à consulter le site de [USB-IF] qui contient toutes les spécifications du protocole USB jusqu’à USB4.
1.1 Communications USB
Le protocole USB se base sur une architecture hôte-périphériques. L’hôte initie toutes les communications. Les périphériques USB répondent aux requêtes de l’hôte et ne peuvent pas initier de communications eux-mêmes.
Pour synchroniser la communication avec les périphériques, l’hôte utilise les frames qui sont des intervalles de temps dont la durée dépend de la spécification USB utilisée. Au début de chaque frame, l’hôte envoie un paquet spécial appelé « Start of Frame » (SOF). Les périphériques peuvent ensuite synchroniser leur horloge interne et déterminer le début d’une frame.
Le protocole USB utilise différents types de transferts pour transmettre des données entre l'hôte et les périphériques. Il existe quatre types de transferts USB :
- Les transferts d'interruption sont utilisés pour envoyer des petites quantités de données avec des latences faibles. Ils sont principalement utilisés pour les périphériques qui ont besoin d'envoyer ou de recevoir des mises à jour fréquentes, tels que les claviers et les souris ;
- Les transferts de masse (bulk) sont utilisés pour transférer de grandes quantités de données. Ils sont utilisés pour les imprimantes et les clés USB par exemple ;
- Les transferts isochrones sont utilisés pour transférer des données en continu, tels que les périphériques audio et vidéo ;
- Les transferts de contrôle sont utilisés pour envoyer des commandes simples aux périphériques et recevoir leurs réponses. Ils sont également utilisés lors de l'énumération et la configuration des périphériques.
1.2 Processus d’énumération
L’énumération est le processus de détection d’un périphérique USB et de sa configuration par l’hôte.
Lorsqu’un périphérique se connecte sur un port USB de l’hôte, sa présence est reconnue par le changement de tension entre les fils D+ et D-. L’hôte va alors lui envoyer un signal d’initialisation (commande RESET). Puis, il va lire les descripteurs du périphérique contenant les informations sur celui-ci et lui affecter une adresse unique de 7 bits. Si le périphérique est supporté par le système, alors les pilotes correspondants seront chargés.
1.3 Les classes de périphériques USB
Il existe une vingtaine de classes de périphériques USB. Les plus communes sont :
- la classe Mass Storage (MSD) : disques durs externes, clés USB, etc. ;
- la classe des périphériques de communication (CDC) : modems, routeurs permettant de se connecter à Internet via un port USB ;
- la classe Human Interface Device (HID) : clavier, souris, etc. ;
- les classes audio et vidéo : écouteurs, enceintes, etc. ;
- la classe Hub ;
- etc.
Il existe aussi des périphériques qui ont des structures composites, par exemple une caméra qui a aussi des fonctionnalités de microphone.
En plus des classes communes, il existe une classe USB Vendor Specific. Elle n’est pas définie dans les standards USB. Cette classe est généralement utilisée pour les périphériques qui ne rentrent pas dans les autres catégories de classes de périphériques USB, ou qui ont des fonctionnalités spécifiques qui nécessitent des pilotes propriétaires.
1.4 Les descripteurs USB
Chaque périphérique USB possède un ensemble de descripteurs, qui donne toutes les informations sur l’appareil pour être configuré. Ces descripteurs sont envoyés à l’hôte lors du processus d’énumération.
Il existe plusieurs types de descripteurs :
- descripteur de périphérique : c’est le premier descripteur lu pendant l’énumération. Il contient les informations sur les identifiants (vendor ID et product ID, VID/PID en version courte), la version de la norme USB, la classe, le nombre de configurations, etc. ;
- descripteur de configuration : il indique le nombre d’interfaces présentes dans la configuration et la consommation électrique nécessaire. Un périphérique peut avoir une ou plusieurs configurations, mais une seule configuration peut être active à la fois ;
- descripteur d’interface : il décrit les détails sur le fonctionnement du périphérique, on y trouve notamment le nombre d’endpoints implémentés et la classe de l’interface. Par exemple, un clavier aura la classe HID et deux endpoints ;
- descripteur d’endpoint : les endpoints étant des buffers dans lesquels les données peuvent circuler. Chaque endpoint a son propre descripteur qui décrit son adresse, sa direction, le type de transferts, la taille maximale de chaque paquet, etc. Les endpoints sont unidirectionnels, leur direction est soit IN, soit OUT. Chaque périphérique peut avoir au maximum 32 endpoints, dont 2 endpoints de contrôle à l’adresse 0.
Un périphérique USB peut avoir des descripteurs organisés selon la hiérarchie en Figure 1 par exemple.
Les descripteurs des périphériques USB connectés au système sont consultables à l’aide de la commande lsusb -v.
2. Présentation des outils d’attaques USB
2.1 Facedancer
Les Facedancers constituent une famille de cartes électroniques open source développées initialement par Travis Goodspeed il y a une dizaine d'années, dérivées de son projet GoodFET [FD21].
Un Facedancer présente deux interfaces USB. L’une d'entre elles est utilisée pour contrôler le Facedancer à l'aide de scripts Python depuis un ordinateur, tandis que l'autre sert à émuler un périphérique USB quelconque auprès d'un hôte USB cible.
Les principales versions sont le Facedancer11 (équipé d'un MAX3420E) et le Facedancer21 (équipé d'un MAX3421E capable d'émuler également un hôte USB).
Cet appareil va donc être en mesure d'effectuer différentes analyses, par exemple :
- émuler des VID/PID arbitraires pour découvrir quelles classes de périphériques sont prises en charge par l’hôte, voire quels chipsets spécifiques ;
- émuler de manière plus complète un périphérique (mass storage, clavier...) et potentiellement fuzzer la communication ;
- relayer un périphérique réel vers l’hôte cible. C'est une disposition en Man-in-the-Middle (MitM) appelée USBProxy dans le projet [FACEDANCER].
2.2 GreatFET One
Le GreatFET One [GREATFET] est une plateforme d'expérimentation semblable à l'Hydrabus, sur laquelle viennent s'enficher des cartes filles, mais il est équipé initialement avec deux interfaces USB et peut donc remplacer le Facedancer tel quel.
Son contrôleur USB a moins de limitations que celui du Facedancer (notamment sur le type et le nombre d’endpoints) et permet donc d'émuler des périphériques plus complexes.
Il offre également un meilleur support MitM USBProxy-nv que le support initial USBProxy du Facedancer.
2.3 LUNA
LUNA [LUNA] est le dernier projet de carte matérielle de la lignée, pour l'instant disponible uniquement en précommande, et sa sortie est repoussée aux alentours de juin 2023.
Cette carte est cette fois-ci basée sur un FPGA ECP5 programmé avec une toolchain open source Amaranth HDL et suffisamment rapide pour traiter l'USB 2.0 high-speed (480 Mbit/s).
Il est conçu pour aider aussi bien au développement de périphériques USB que pour des fins de recherche en sécurité. Il peut aujourd’hui être utilisé comme un analyseur de trames USB avec ViewSB et par la suite, il sera compatible avec la suite logicielle Facedancer et pourra agir comme un proxy USB.
3. Installation et utilisation des outils
3.1 Facedancer 2.9
Facedancer 2.9 est une réécriture de l'outillage initial du Facedancer pour supporter également d'autres plateformes compatibles telles que le GreatFET One ou, à terme, LUNA.
3.1.1 Installation
Facedancer 2.9 s’installe à l’aide des commandes suivantes :
3.1.2 Utilisation avec Facedancer21
Pour l'utiliser avec un Facedancer, il faut utiliser le backend goodfet. Ici, nous avons lancé l’exemple Rubber Ducky qui simule la saisie des touches d’un clavier :
3.1.3 Utilisation avec GreatFET One
Pour l'utiliser avec un GreatFET One, il faut d'abord installer quelques bibliothèques :
L’installation se fait à l’aide des commandes suivantes :
On vérifie que le GreatFET One est bien détecté et on flashe le dernier firmware :
Une fois pygreat et greatfet installés, on peut rejouer le même exemple qu’avec le Facedancer21. L’hôte de contrôle se connecte à l'USB sur le côté convexe (USB0) et l’hôte cible se connecte sur le côté concave (USB1) du GreatFET.
3.1.4 Proxy USB
Le GreatFET One peut aussi être utilisé pour réaliser un MitM entre un vrai périphérique USB et un système hôte cible, grâce au Facedancer 2.9 USBProxy 'Nouveau'.
Prenons l’exemple de la souris, se branchant sur un ordinateur, on se retrouve dans la configuration de la Figure 5.
Pour lancer USBProxy, il faut fournir au script facedancer-usbproxy.py le vendor ID et le product ID du périphérique dont on souhaite intercepter le trafic.
Pour automatiser la démarche, on peut utiliser ces quelques lignes de script :
On peut reprendre le code de l'exemple usbproxy-switch-invertx.py pour se rendre compte à quel point il est alors simple de modifier le contenu du trafic USB à la volée. Pour inverser l'axe X de la souris, on crée un filtre qui inverse le troisième octet :
Et on ajoute un appel à d.add_filter à la suite des autres :
On relance le proxy, et cette fois le curseur va se déplacer vers la gauche lorsque la souris est déplacée vers la droite, et vice versa !
3.2 Umap2
Umap2 est un outil open source développé par NCC Group en Python. Il s’agit de la deuxième révision de l’outil Umap pour réaliser des tests USB. Il peut être utilisé avec le Facedancer pour émuler des périphériques USB. Le GreatFET One n’est cependant pas supporté.
3.2.1 Installation
Le projet est disponible sur [UMAP2], mais n'est pas encore converti en Python3. Une pull request existe [PR34] et nous utiliserons donc directement cette contribution.
3.2.2 Émulation d’une classe USB
umap2emulate permet d’émuler une classe USB. Umap2 supporte les classes suivantes :
Les descripteurs de ces classes sont définis dans le répertoire umap2/umap2/dev/. On peut également définir nos propres descripteurs pour les classes que l’on souhaite.
On émule un périphérique mass storage simplement de la manière suivante :
Ou un périphérique personnalisé avec un script Python :
3.2.3 Scan des classes USB supportées
À l’aide de l’utilitaire umap2scan, on peut émuler un ensemble de périphériques USB de classes connues et tester s’il est supporté par le système.
Lorsqu’un scan est lancé avec Umap2, ce dernier va émuler les différentes classes de périphériques USB listées ci-dessus une par une pendant quelques secondes et analyser la réaction de la cible. Si un message a été reçu sur un endpoint donné du périphérique, alors il est supporté par le système.
La commande ci-dessous montre une session de scan avec Umap2 sur un port USB présent sur une télévision :
On remarque que seulement trois types de périphériques sont supportés : un hub, un clavier et un périphérique de stockage de masse.
3.2.4 Fuzzing
Le fuzzing consiste à envoyer des données aléatoires au système dans le but de déclencher des comportements inattendus. Dans le cadre du fuzzing USB, l’idée est d’émuler des périphériques USB à partir de différents descripteurs et analyser la réaction des pilotes installés sur le système cible.
Umap2 permet de faire cela. Nous avons tout d'abord besoin d'identifier les classes USB supportées par le système cible pour concentrer le fuzzing uniquement sur ces classes supportées, ce qui peut être fait à l'aide de umap2scan vu précédemment. Puis, il faut enregistrer le flux de communication pour une classe donnée avec umap2stages. Par exemple pour la classe clavier, on sauvegarde le flux dans le fichier keyboard.stages :
Umap2 utilise Kitty [KITTY] comme framework de fuzzing. C'est un fuzzer basé sur la génération, c’est-à-dire que les données sont générées à partir d’un modèle. Cette approche est plus pertinente qu’un fuzzer basé sur la mutation, car les descripteurs USB ont des structures particulières qu’il faut préserver pendant la génération de cas de tests.
Pour lancer le fuzzer, on lance d’abord le backend Kitty :
Puis, on lance umap2fuzz sur la classe que l’on souhaite fuzzer :
Ensuite, on consulte les données échangées dans les logs du fuzzer ou dans l’interface web de Kitty disponible sur http://localhost:26000/.
Grâce à Umap2, on peut facilement fuzzer une classe de périphériques sur un port USB exposé. L'inconvénient est que Kitty ne fonctionne qu’avec Python2. Le fuzzing ne fonctionnera donc pas avec Python3.
Il existe un projet nü-map [NUMAP], dérivé de Umap2, qui fonctionnerait sur le GreatFET One avec Python3. Cependant, lors de nos tests, nous avons eu du mal à le faire fonctionner correctement.
3.2.5 Limites du fuzzing USB
3.2.5.1 Fuzzing en boîte noire
La principale limite d'un tel fuzzing en boîte noire est que nous n’avons pas de retour sur l'état du système ciblé. Si un crash se produit, nous n’avons aucun détail sur la partie du code à l'origine du problème. Les informations sur la couverture du code sont aussi inexistantes. Ainsi, nous ne pouvons pas adapter nos entrées en fonction de ce qui a pu être exécuté et nous n’avons pas de garantie sur la pertinence de nos tests. Mais le fuzzing matériel a aussi l’avantage de pouvoir tester toute la chaîne, du branchement sur le port USB jusqu’aux interactions avec le pilote.
3.2.5.2 Contraintes matérielles
Dès lors qu’un composant matériel entre en jeu dans le fuzzing, sa vitesse devient assez limitée. En fuzzing logiciel, nous avons l’habitude d’avoir des vitesses de l’ordre de quelques milliers d’exécutions par seconde, mais ici, c’est plutôt de l’ordre d’une exécution toutes les quelques secondes.
D’autre part, la parallélisation est aussi compliquée. Lancer plusieurs instances du fuzzer nécessite d’avoir plusieurs Facedancers, ce qui peut devenir rapidement coûteux. Le nombre limité de périphériques pouvant être connecté à un contrôleur USB étant de 127, on ne peut pas paralléliser indéfiniment.
3.2.5.3 Protocoles USB propriétaires
De nombreux fabricants utilisent des pilotes propriétaires. C’est le cas des périphériques de la classe Vendor Specific. Deux problèmes se posent alors. D’abord, il faut pouvoir scanner les périphériques Vendor Specific supportés par le système. Le Facedancer doit dans ce cas émuler un périphérique de la classe Vendor Specific avec des identifiants VID/PID particuliers par exemple. Pour cela, il suffit d’avoir une base de couples VID/PID connus des fournisseurs de périphériques USB. Nombre de ces informations se retrouvent sur le dépôt public des identifiants USB connus [USBIDS]. L’utilitaire umap2vsscan peut être utilisé pour scanner ces périphériques.
Le deuxième problème est que nous ne connaissons pas la topologie des descripteurs attendus. Pour lancer une session de fuzzing, il faut alors se baser sur des descripteurs génériques, ce qui n’est pas idéal. Un travail de rétro-ingénierie sur les pilotes installés sur le système doit être fait en amont pour connaître exactement la structure attendue.
3.3 ViewSB
ViewSB [VIEWSB] est un outil open source permettant de visualiser les différents échanges du protocole USB.
3.3.1 Installation
Son installation nécessite d’abord d’installer la librairie python-usb-protocol :
3.3.2 Usbmon
Il peut être utilisé avec le module noyau usbmon pour afficher les paquets USB, sans avoir besoin de matériel supplémentaire :
Les paquets suivants montrent le processus d’énumération lors du branchement d’une clé USB (Figure 6).
Une fois que l’énumération est terminée, les échanges avec la clé USB se font à l’aide de transferts de type Bulk (Figure 7).
Il peut également être utilisé avec LUNA pour analyser les trafics USB.
3.3.3 USBProxy
ViewSB est censé fonctionner avec USBProxy. Avec un GreatFET One, nous pouvons utiliser ce petit script :
Cependant, lors de nos tests, rien ne semble se passer après avoir exécuté la dernière ligne.
4. Exemple d’exploitation d’une vulnérabilité USB : CVE-2016-2384
La CVE-2016-2384 correspond à une vulnérabilité de type double free dans la fonction snd_usbmidi_create qui affecte les pilotes USB MIDI du noyau Linux avant la version 4.5. Elle a été découverte par Andrey Konovalov en 2016 [CVE].
Pour la corriger, il suffit d’appliquer ce petit patch qui retire l’appel à snd_usbmidi_free :
Les conditions pour déclencher cette vulnérabilité sont assez simples. Il suffit de brancher un périphérique USB sur le système vulnérable avec les éléments suivants :
- idVendor = 0x0763 ;
- idProduct = 0x1002 ;
- une des configurations possédant une interface ayant bInterfaceClass = 255 et zéro endpoint.
Son Proof of Concept [POC] a montré qu’il est non seulement possible de déclencher un déni de service avec un accès physique à la machine vulnérable, mais aussi d’obtenir une exécution de code dans le noyau avec un accès local en plus.
Nous avons testé son PoC sur un noyau Linux recompilé, où nous avons retiré le patch précédent et activé KASAN. Et en émulant le périphérique malveillant à l’aide d’un Facedancer, nous avons pu observer le message suivant dans les logs obtenus via dmesg lorsque l’émulation avec le Facedancer est lancée :
La vulnérabilité a donc bien été déclenchée.
Comme nous pouvons le voir dans la présentation de Martijn Bogaard et Dana Geist à la Black Hat Europe 2021 [BHEU2021], cette vulnérabilité est encore d’actualité. L’application des patchs de sécurité dans le noyau Linux peut malheureusement prendre beaucoup de temps et certains systèmes restent encore aujourd’hui vulnérables.
Dans leur présentation, ils ont exploité à nouveau cette vulnérabilité en utilisant alors un GreatFET et la suite logicielle Facedancer. Leur approche est intéressante, car cette fois-ci un simple accès physique au port USB permet d’être root sur la cible !
Conclusion
Il existe aujourd’hui de nombreux outils pour tester les ports USB présents sur les systèmes embarqués. Le Facedancer est l’un des premiers outils, il permet non seulement d’émuler un périphérique USB de notre choix, mais aussi de fuzzer une classe USB spécifique. L’approche est assez limitée en boîte noire, mais offre tout de même quelques possibilités d’attaques. GreatFET One, quant à lui, fournit un réel avantage avec la mise en place de MitM USB.
Malheureusement, quasi tous ces projets sont aujourd'hui peu maintenus. La sortie future de LUNA apportera-t-elle de meilleurs résultats avec un support de l'USB 2.0 High-Speed ? Et concernant un support de l'USB 3.0, la plateforme HydraUSB3 [HYDRAUSB3] pourrait devenir un socle prometteur pour de nouveaux outils de fuzzing.
Remerciements
Un grand merci à Philippe Teuwen pour avoir testé et débuggé tous les outils listés dans cet article. Ces tests l'ont d'ailleurs poussé à effectuer des pull requests sur certains de ces outils open source, qui ont été rapidement acceptés et ajoutés aux projets associés.
Je tiens également à remercier les relecteurs pour leurs remarques et conseils avisés.
Références
[USB-IF] USB Implementers Forum, base documentaire des spécifications USB : https://www.usb.org
[FD21] Présentation de Facedancer21 : https://goodfet.sourceforge.net/hardware/facedancer21/
[FACEDANCER] Dépôt GitHub du logiciel Facedancer : https://github.com/greatscottgadgets/Facedancer
[GREATFET] Documentation de GreatFET One : https://greatscottgadgets.com/greatfet/one/
[LUNA] Documentation de LUNA : https://greatscottgadgets.com/luna/
[UMAP2] Projet Umap2 : https://github.com/nccgroup/umap2
[PR34] Patch du projet Umap2 pour fonctionner avec Python3 : https://github.com/nccgroup/umap2/pull/34
[KITTY] Projet Kitty : https://github.com/cisco-sas/kitty
[NUMAP] Projet nü-map : https://github.com/usb-tools/nu-map
[USBIDS] Dépôt public des identifiants connus de périphériques USB :
http://www.linux-usb.org/usb-ids.html
[VIEWSB] Projet ViewSB : https://github.com/greatscottgadgets/ViewSB
[CVE] A. Konovalov, « CVE-2016-2384: Exploiting a double-free in the Linux kernel USB MIDI driver », 22 février 2016 : https://xairy.io/articles/cve-2016-2384
[POC] A. Konovalov, Proof of Concept du CVE-2016-2384 :
https://github.com/xairy/kernel-exploits/tree/master/CVE-2016-2384
[BHEU2021] M. Bogaard et D. Geist, « Achieving Linux Kernel Code Execution Through A Malicious USB Device », Black Hat Europe 2021 : https://i.blackhat.com/EU-21/Thursday/EU-21-Bogaard-Geist-Achieving-Linux-Kernel-Code-Execution-Through-A-Malicious-USB-Device.pdf
[HYDRAUSB3] Projet HydraUSB3 : https://github.com/hydrausb3