Kürzlich stieß ich bei eBay auf eine Reihe interessanter USB-Geräte (Epiphan VGA2USB LR), die VGA-Eingaben empfangen und Videos als Webcam an USB senden. Ich war so begeistert von der Idee, dass ich mich nie wieder mit VGA-Monitoren beschäftigen müsste, und angesichts der erklärten Unterstützung für Linux habe ich die Chance genutzt und die gesamte Charge für etwa 25 US-Dollar (20 Pfund) gekauft.
Nachdem ich das Paket erhalten hatte, schloss ich das Gerät an, aber es schien nicht einmal als
UVC im System zu erscheinen. Was ist los?
Ich habe die Website des Herstellers studiert und festgestellt, dass ein spezieller Fahrer erforderlich ist, um zu arbeiten. Für mich war dies ein neues Konzept, da der Kern meiner Linux-Distribution normalerweise Treiber für alle Geräte enthält.
Leider wurde die Treiberunterstützung nur für diese Geräte unter Linux 4.9 eingestellt. Daher wird es auf keinem meiner Systeme angezeigt (Debian 10 unter Linux 4.19 oder die neueste Version von LTS Ubuntu unter Linux 5.0).
Aber es kann behoben werden, oder? Natürlich sind die Dateien im
DKMS-Paket enthalten , das den Treiber wie viele normale Treiber auf Anfrage aus dem Quellcode abholt ...
Es ist traurig. Aber hier ist es nicht so.
In dem Paket befand sich nur die vorkompilierte Binärdatei
vga2usb.o
. Ich begann es zu studieren und wunderte mich über die Komplexität des Reverse Engineering. Dabei fand ich einige interessante Zeilen:
$ strings vga2usb.ko | grep 'v2uco' | sort | uniq v2ucom_autofirmware v2ucom_autofirmware_ezusb v2ucom_autofirmware_fpga
Also ist es wirklich
FPGA- on-a-Stick? Wie kann man so etwas machen?
Ein anderer lustiger und etwas störender Fund war die Zeile mit den Parametern des privaten DSA-Schlüssels. Das hat mich gefragt: Was kann es im Fahrer schützen?
$ strings vga2usb.ko | grep 'epiphan' | sort | uniq epiphan_dsa_G epiphan_dsa_P epiphan_dsa_Q
Um den Treiber in seiner normalen Umgebung zu untersuchen, nahm ich eine virtuelle Debian 9-Maschine (letzte unterstützte Version) und
schaltete KVM-USB-Passthrough ein , um direkten Zugriff auf das Gerät zu ermöglichen. Dann habe ich den Treiber installiert und sichergestellt, dass es funktioniert.
Danach wollte ich sehen, wie das Kommunikationsprotokoll aussieht. Ich hatte gehofft, das Gerät würde rohe oder fast rohe Frames senden, da dies das Schreiben eines Treibers für den Benutzerraum erleichtern würde.
Dazu habe ich das
usbmon
Modul auf den Host der virtuellen Maschine geladen und Wireshark gestartet, um den USB-Verkehr zum und vom Gerät während des Starts und der Videoaufnahme zu erfassen.

Beim Start wurde festgestellt, dass eine große Anzahl kleiner Pakete an das Gerät übertragen wird, bevor mit der Aufnahme des Bildes begonnen wird. Es basiert wahrscheinlich auf der FPGA-Plattform ohne Datenspeicherung. Der Treiber hat nach jeder Verbindung die Firmware in Form eines
FPGA-Bitstreams auf das Gerät übertragen.
Davon war ich überzeugt, als ich eine der Schachteln öffnete:

Da Sie zum „Herunterladen“ des Geräts einen Bitstream / eine Firmware senden müssen, müssen Sie in vorkompilierten Binärdateien danach suchen. Ich habe
binwalk -x
und nach komprimierten Objekten (zlib) gesucht. Dazu habe ich ein Hex-Sequenz-Suchskript geschrieben und drei Bytes aus dem abgefangenen Paket angegeben.
$ 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
Nach dem Entpacken der Datei AA240.zlib stellte sich heraus, dass dort nicht genügend Daten für einen vollständigen Bitstream vorhanden waren. Also habe ich beschlossen, die Firmware aus den USB-Paketen zu holen.
Sowohl tshark als auch
tcpdump können USB-Pakete aus pcap-Dateien
lesen , aber beide speichern sie nur teilweise. Da jedes Dienstprogramm verschiedene Teile des Puzzles enthielt, habe ich ein
kleines Programm geschrieben , das die Ausgabe beider Programme in Go-Strukturen kombiniert, um die Pakete auf dem Gerät abzuspielen.
Zu diesem Zeitpunkt bemerkte ich, dass der Download in zwei Schritten erfolgt: zuerst einen USB-Controller und dann ein FPGA.
Ich steckte mehrere Tage fest: Es schien, als würde der gesamte Bitstream geladen, aber das Gerät wurde nicht gestartet, obwohl die Pakete des realen Treibers und meine Simulation ähnlich aussehen.
Am Ende löste ich das Problem, indem ich pcap unter Berücksichtigung der Antwortzeit für jedes Paket sorgfältig untersuchte - und bemerkte einen großen Zeitunterschied in einem bestimmten Paket:

Es stellte sich heraus, dass
die Aufnahme aufgrund eines kleinen
Tippfehlers im falschen Bereich des Geräts erfolgte. Es wird mir eine Lektion sein, wie man Werte manuell eingibt ...
Die LED am Gerät blinkte jedoch endlich! Eine riesen Leistung!
Es war relativ einfach, dieselben Pakete zu replizieren, die die Datenübertragung ausgelöst haben, sodass ich den USB-Massenendpunkt schreiben und die Daten sofort auf die Festplatte kopieren konnte!
Hier begannen die wirklichen Schwierigkeiten. Denn nach der Analyse stellte sich heraus, dass die Daten in keiner Weise explizit verschlüsselt wurden.
Um zu beginnen, habe ich
perf ausgeführt , um eine
grundlegende Vorstellung von der Ablaufverfolgung des Treiberstapels zur Laufzeit zu erhalten:

Obwohl ich Funktionen mit Rahmendaten abfangen konnte, konnte ich die Kodierung der Daten selbst nicht verstehen.

Um besser zu verstehen, was im realen Treiber vor sich geht, habe ich sogar das
Ghidra- Tool der NSA ausprobiert:

Obwohl Ghidra unglaublich ist (als ich es zum ersten Mal anstelle von IDA Pro verwendet habe), ist es immer noch nicht gut genug, um mir zu helfen, den Treiber zu verstehen. Reverse Engineering erforderte einen anderen Pfad.
Ich habe beschlossen, die virtuelle Windows 7-Maschine in die Hand zu nehmen und mir den Windows-Treiber anzuschauen, da werden plötzlich Ideen auftauchen. Und dann ist mir aufgefallen, dass es ein SDK für Geräte gibt. Eines der Tools erwies sich als besonders interessant:
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
Mit diesem Tool können Sie einzelne Frames "greifen", die anfangs nicht komprimiert werden, damit Sie Frames später auf einem schnelleren Computer verarbeiten können. Das ist fast perfekt, und ich habe die Sequenz der USB-Pakete repliziert, um diese unkomprimierten Blobs zu erhalten. Die Anzahl der Bytes entsprach ungefähr drei (RGB) pro Pixel!
Die anfängliche Verarbeitung dieser Bilder (nur das Akzeptieren der Ausgabe und das Schreiben als RGB-Pixel) erinnerte vage an das reale Bild, das das Gerät über VGA empfing:

Nach einigem Debuggen im Hex-Editor stellte sich heraus, dass jeder Marker alle 1028 Bytes wiederholt wird. Es ist ein bisschen peinlich, wie viel Zeit ich damit verbracht habe, einen Filter zu schreiben. Auf der anderen Seite könnte man sich dabei an einigen Beispielen zeitgenössischer Kunst erfreuen.

Dann wurde mir klar, dass die Neigung und Verzerrung des Bildes durch Überspringen und Pixelumbruch in jeder Zeile verursacht wird (x = 799 ist nicht gleich x = 800). Und dann habe ich endlich ein fast korrektes Bild bekommen, abgesehen von der Farbe:

Zuerst dachte ich, das Kalibrierungsproblem liege an der Datenabtastung, wenn der VGA-Eingang einfarbig ist. Zur Korrektur habe ich ein neues Testbild erstellt, um solche Probleme zu identifizieren. Im Nachhinein habe ich verstanden, dass Sie so etwas wie
eine Philips PM5544-Testkarte verwenden mussten .

Ich habe das Bild auf einen Laptop hochgeladen und es wurde ein solches VGA-Bild erstellt:

Dann habe ich die Erinnerung an eine alte Arbeit in 3D-Rendering / Shader. Es war
dem YUV-Farbschema sehr ähnlich.
Infolgedessen stürzte ich mich in die Lektüre der YUV-Literatur und erinnerte mich, dass das System beim Reverse Engineering des offiziellen
v2ucom_convertI420toBGR24
ohne die Möglichkeit einer Erneuerung einfrieren würde, wenn ich einen Haltepunkt für eine Funktion namens
v2ucom_convertI420toBGR24
. Vielleicht war die Eingabe also I420-Codierung (von
-pix_fmt yuv420p
) und die Ausgabe war RGB?
Nach Verwendung der integrierten Go-Funktion
YCbCrToRGB kam das Bild dem Original plötzlich viel näher.

Wir haben es geschafft! Sogar der Raw-Treiber produzierte 7 Frames pro Sekunde. Ehrlich gesagt, das reicht mir, da ich VGA nur im Falle eines Unfalls als Backup-Anzeige verwende.
Jetzt kennen wir dieses Gerät also gut genug, um den Algorithmus für das Starten von Anfang an zu erläutern:
- Sie müssen den USB-Controller initialisieren . Gemessen an der Informationsmenge übergibt der Treiber tatsächlich Code zum Herunterladen.
- Wenn Sie den USB-Stick geladen haben, wird das Gerät vom USB-Bus getrennt und kehrt nach einem Moment mit einem USB-Endpunkt zurück.
- Jetzt können Sie den FPGA-Bitstream senden , ein 64-Byte-USB-Paket für jede Steuerübertragung.
- Am Ende der Übertragung blinkt die Anzeige am Gerät grün. An dieser Stelle können Sie eine scheinbare Folge von Parametern (Overscan und andere Eigenschaften) senden.
- Führen Sie dann das Steuerpaket aus, um den Frame mit der angegebenen Berechtigung abzurufen. Wenn Sie eine Anforderung für einen 4: 3-Frame an den Breitbild-Eingang senden, führt dies normalerweise zu einer Beschädigung des Frames.
Für maximale Benutzerfreundlichkeit habe ich einen kleinen Webserver im Treiber implementiert. Über die browserbasierte
MediaRecorder-API wird der Stream vom Bildschirm auf einfache Weise in eine Videodatei aufgezeichnet.

Um den unvermeidlichen Anspruch auf die Qualität des experimentellen Codes zu vermeiden, sage ich gleich: Ich bin nicht stolz darauf. Wahrscheinlich befindet er sich in einem solchen Zustand, der mir für eine akzeptable Verwendung ausreicht.
Der Code und die vorgefertigten Builds für Linux und OSX
befinden sich auf GitHub .
Auch wenn niemand das Programm startet, war es für mich eine aufregende Reise durch die Wildnis des USB-Protokolls, das Debuggen des Kernels, das Reverse Engineering des Moduls und das Videodecodierungsformat! Wenn Sie diese Dinge mögen, können Sie
andere Blog-Beiträge lesen .