Nous écrivons des pilotes USB pour les appareils abandonnés



Récemment sur eBay, je suis tombé sur un lot de périphériques USB intéressants (Epiphan VGA2USB LR), qui reçoivent une entrée VGA et envoient des vidéos sur USB en tant que webcam. J'étais tellement ravi de l'idée que je n'aurais plus jamais à me soucier des moniteurs VGA, et étant donné le support déclaré pour Linux, j'ai pris une chance et acheté le lot entier pour environ 20 livres (25 dollars américains).

Ayant reçu le colis, j'ai connecté l'appareil, mais il ne pensait même pas apparaître dans le système comme UVC . Qu'est-ce qui ne va pas?

J’ai étudié le site Web du fabricant et constaté qu’un pilote spécial était nécessaire pour fonctionner. Pour moi, c'était un nouveau concept, car le noyau de ma distribution Linux a généralement des pilotes pour tous les périphériques.

Malheureusement, la prise en charge des pilotes pour ces seuls périphériques s'est terminée dans Linux 4.9. Ainsi, aucun de mes systèmes ne le verra (Debian 10 sur Linux 4.19 ou la dernière version de LTS Ubuntu sur Linux 5.0).

Mais cela peut être corrigé, non? Bien sûr, les fichiers sont fournis dans le package DKMS , qui, à la demande, collecte le pilote à partir du code source, comme de nombreux pilotes ordinaires ...

C'est triste. Mais ce n'est pas le cas ici.

À l'intérieur du package se trouvait uniquement le vga2usb.o binaire précompilé vga2usb.o . J'ai commencé à l'étudier, me demandant la complexité de la rétro-ingénierie, et j'ai trouvé quelques lignes intéressantes:

 $ strings vga2usb.ko | grep 'v2uco' | sort | uniq v2ucom_autofirmware v2ucom_autofirmware_ezusb v2ucom_autofirmware_fpga 

Alors, est-ce vraiment du FPGA sur un bâton? Comment faire fonctionner quelque chose comme ça?

Une autre découverte amusante et légèrement dérangeante était la ligne avec les paramètres de clé privée DSA. Cela m'a fait me demander: que peut-il protéger à l'intérieur du pilote?

 $ strings vga2usb.ko | grep 'epiphan' | sort | uniq epiphan_dsa_G epiphan_dsa_P epiphan_dsa_Q 

Pour étudier le pilote dans son environnement normal, j'ai pris une machine virtuelle Debian 9 (dernière version prise en charge) et créé un relais KVM USB pour donner un accès direct au périphérique. Ensuite, j'ai installé le pilote et vérifié que cela fonctionne.

Après cela, j'ai voulu voir à quoi ressemble le protocole de communication. J'espérais que le périphérique enverrait des images brutes ou presque brutes, car cela faciliterait l'écriture d'un pilote pour l'espace utilisateur.

Pour ce faire, j'ai chargé le module usbmon sur l' usbmon machine virtuelle et lancé Wireshark pour capturer le trafic USB vers et depuis l'appareil pendant le démarrage et la capture vidéo.



J'ai constaté que lors du lancement, un grand nombre de petits paquets sont transmis à l'appareil avant qu'il ne commence à capturer l'image. Il est probablement basé sur la plate-forme FPGA sans stockage de données. Après chaque connexion, le pilote transférait le firmware sous forme de flux binaire FPGA vers l'appareil.

J'en ai été convaincu en ouvrant une des boîtes:



Rouge

ISL98002CRZ-170 - Fonctionne comme ADC pour les signaux VGA

Jaune

XC6SLX16 - FPGA Xilinx Spartan 6

Cyan

64 Mo de DDR3

Magenta

CY7C68013A - Contrôleur USB / frontal


Étant donné que pour «télécharger» l'appareil dont vous avez besoin pour lui envoyer un flux binaire / firmware, vous devrez le rechercher dans des fichiers binaires précompilés. J'ai exécuté binwalk -x et j'ai commencé à chercher des objets compressés (zlib). Pour ce faire, j'ai écrit un script de recherche de séquence hexadécimale - et spécifié trois octets à partir du paquet intercepté.

 $ bash scan.sh "03 3f 55" trying 0.elf trying 30020 trying 30020.zlib trying 30020.zlib.decompressed ... trying 84BB0 trying 84BB0.zlib trying 84BB0.zlib.decompressed trying AA240 trying AA240.zlib trying AA240.zlib.decompressed 000288d0 07 2f 03 3f 55 50 7d 7c 00 00 00 00 00 00 00 00 |./.?UP}|........| trying C6860 trying C6860.zlib 

Après avoir déballé le fichier AA240.zlib, il s'est avéré qu'il n'y avait pas suffisamment de données pour un flux binaire complet. J'ai donc décidé de récupérer le firmware des packages USB.

Tshark et tcpdump peuvent lire les paquets USB à partir des fichiers pcap, mais les deux ne les enregistrent que partiellement. Étant donné que chaque utilitaire avait différentes parties du puzzle, j'ai écrit un petit programme qui combine la sortie des deux programmes dans des structures go afin de lire les paquets sur l'appareil.

À ce stade, j'ai remarqué que le téléchargement se déroule en deux étapes: d'abord un contrôleur USB, puis FPGA.

J'ai été bloqué pendant plusieurs jours: il semblait que tout le flux binaire était en cours de chargement, mais l'appareil n'a pas démarré, bien que les packages du pilote réel et ma simulation se ressemblent.

En fin de compte, j'ai résolu le problème en examinant attentivement pcap en tenant compte du temps de réponse pour chaque paquet - et j'ai remarqué une grande différence de temps dans un package spécifique:



Il s'est avéré qu'en raison d'une petite faute de frappe, l' enregistrement s'est produit dans la mauvaise zone de l'appareil. Ce sera une leçon pour moi comment entrer des valeurs manuellement ...

Cependant, la LED a finalement clignoté sur l'appareil! Une immense réussite!


Il était relativement facile de répliquer les mêmes packages qui ont déclenché le transfert de données, donc j'ai pu écrire le point de terminaison USB Bulk et vider les données sur le disque instantanément!

C'est là que les vraies difficultés ont commencé. Parce qu'après analyse, il s'est avéré que les données n'étaient pas explicitement encodées de quelque façon que ce soit.

Pour commencer, j'ai exécuté perf pour avoir une idée de base de la trace de la pile du pilote lors de l'exécution:



Bien que je puisse attraper des fonctions avec des données de trame, je ne pouvais pas comprendre l'encodage des données lui-même.



Pour mieux comprendre ce qui se passe à l'intérieur du vrai pilote, j'ai même essayé l'outil Ghidra de la NSA:



Bien que Ghidra soit incroyable (lorsque je l'ai utilisé pour la première fois au lieu d'IDA Pro), il n'est toujours pas assez bon pour m'aider à comprendre le pilote. L'ingénierie inverse nécessitait un chemin différent.

J'ai décidé de prendre la machine virtuelle Windows 7 et de jeter un coup d'œil au pilote Windows, tout à coup il va lancer des idées. Et puis j'ai remarqué qu'il existe un SDK pour les appareils. L'un des outils s'est révélé particulièrement intéressant:

 PS> ls Directory: epiphan_sdk-3.30.3.0007\epiphan\bin Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 10/26/2019 10:57 AM 528384 frmgrab.dll -a--- 10/27/2019 5:41 PM 1449548 out.aw -a--- 10/26/2019 10:57 AM 245760 v2u.exe -a--- 10/26/2019 10:57 AM 94208 v2u_avi.exe -a--- 10/26/2019 10:57 AM 102400 v2u_dec.exe -a--- 10/26/2019 10:57 AM 106496 v2u_dshow.exe -a--- 10/26/2019 10:57 AM 176128 v2u_ds_decoder.ax -a--- 10/26/2019 10:57 AM 90112 v2u_edid.exe -a--- 10/26/2019 10:57 AM 73728 v2u_kvm.exe -a--- 10/26/2019 10:57 AM 77824 v2u_libdec.dll PS> .\v2u_dec.exe Usage: v2u_dec <number of frames> [format] [compression level] <filename> - sets compression level [1..5], - captures and saves compressed frames to a file v2u_dec x [format] <filename> - decompresses frames from the file to separate BMP files 

Cet outil vous permet de "saisir" des images uniques, et initialement elles ne sont pas compressées, de sorte qu'il est possible de traiter les images plus tard sur une machine plus rapide. C'est presque parfait, et j'ai répliqué la séquence de paquets USB pour obtenir ces blobs non compressés. Le nombre d'octets correspondait à environ trois (RVB) par pixel!

Le traitement initial de ces images (simplement accepter la sortie et l'écrire en pixels RVB) a quelque chose qui rappelle vaguement l'image réelle que l'appareil a reçue via VGA:



Après un débogage dans l'éditeur hexadécimal, il s'est avéré que chaque marqueur est répété tous les 1028 octets. C'est un peu gênant le temps que j'ai passé à écrire un filtre. D'un autre côté, ce faisant, on pouvait apprécier quelques exemples d'art contemporain.



Ensuite, je me suis rendu compte que l'inclinaison et la distorsion de l'image sont causées par le saut et le recouvrement des pixels sur chaque ligne (x = 799 n'est pas égal à x = 800). Et puis, enfin, j'ai obtenu une image presque correcte, à l'exception de la couleur:



Au début, je pensais que le problème d'étalonnage était dû à l'échantillonnage des données lorsque l'entrée VGA était bloquée en couleur unie. Pour la correction, j'ai fait une nouvelle image de test pour identifier de tels problèmes. Avec le recul, je comprends que vous deviez utiliser quelque chose comme une carte de test Philips PM5544 .



J'ai téléchargé l'image sur un ordinateur portable et elle a produit une telle image VGA:



Ensuite, j'ai eu la mémoire de vieux travaux en rendu / shader 3D. Il était très similaire à la palette de couleurs YUV .

En conséquence, j'ai plongé dans la lecture de la littérature YUV et je me suis souvenu que lors de la rétro-ingénierie du pilote officiel du noyau, si je mettais un point d'arrêt sur une fonction appelée v2ucom_convertI420toBGR24 , le système se bloquerait sans possibilité de renouvellement. Alors peut-être que l'entrée était un codage I420 (de -pix_fmt yuv420p ), et la sortie était RVB?

Après avoir utilisé la fonction intégrée Go YCbCrToRGB, l' image s'est soudainement rapprochée de l'original.



Nous l'avons fait! Même le pilote brut produit 7 images par seconde. Honnêtement, cela me suffit, car j'utilise VGA uniquement en cas d'accident comme écran de sauvegarde.

Donc, maintenant, nous connaissons assez bien cet appareil pour expliquer l'algorithme pour le démarrer depuis le tout début:

  1. Vous devez initialiser le contrôleur USB . A en juger par la quantité d'informations, en fait, le conducteur lui passe du code à télécharger.
  2. Une fois le chargement de l'USB terminé, l'appareil se déconnecte du bus USB et, après un moment, revient avec un point d'extrémité USB.
  3. Vous pouvez maintenant envoyer le flux binaire FPGA , un paquet USB de 64 octets pour chaque transfert de contrôle.
  4. À la fin du transfert, le voyant de l'appareil clignote en vert. À ce stade, vous pouvez envoyer ce qui semble être une séquence de paramètres (surbalayage et autres propriétés).
  5. Exécutez ensuite le package de contrôle pour obtenir le cadre , l'autorisation spécifiée par le package. Si vous envoyez une demande de trame 4: 3 vers une entrée grand écran, cela entraînera généralement des dommages à la trame.

Pour une facilité d'utilisation maximale, j'ai implémenté un petit serveur web dans le pilote. Grâce à l' API MediaRecorder basée sur un navigateur, il enregistre facilement le flux de l'écran vers un fichier vidéo.



En prévenant les inévitables prétentions à la qualité du code expérimental, je dirai tout de suite: je n'en suis pas fier. Probablement, il est dans un tel état, ce qui me suffit pour une utilisation acceptable.

Le code et les versions prêtes à l'emploi pour Linux et OSX sont sur GitHub .

Même si personne ne démarre le programme, ce fut pour moi un voyage passionnant à travers les fous du protocole USB, le débogage du noyau, la rétro-ingénierie du module et le format de décodage vidéo! Si vous aimez ces choses, vous pouvez consulter d' autres articles de blog .

Source: https://habr.com/ru/post/fr476434/


All Articles