Да си напишем USB драйвери за изоставените устройства

Оригиналът е на Ben Cox

10
2630

Наскоро видях в eBay партида с много интересните устройства Epiphan VGA2USB LR, които на входа си приемат VGA сигнал и на изхода извеждат видеото към USB интерфейса съвсем като уеб камера. Идеята толкова много ми хареса и така се зарадвах, че повече няма да се занимавам с VGA монитори, и като погледнах, че има поддръжка и за Linux, рискувах и купих цялата партида за 20 паунда.

След като получих пратката, включих устройството, но то дори и не не си помисли да се появи в системата като UVC. Какво ли не е наред?

Погледнах в сайта на производителя и видях, че за работата на това устройство е необходим специален драйвер. За мен това е нещо ново, понеже обикновено в Linux ядрото на моята дистрибуция ги има всичките драйвери на използвания от мен хардуер.

За съжаление, поддръжката на драйверите именно за тези устройства е приключила при Linux 4.9. По този начин нито една от моите машини няма да го види (Debian 10 с Linux 4.19 и последната версия LTS Ubuntu с Linux 5.0).

Но сигурно може да се направи нещо, нали? Имам предвид, че файловете са в DKMS пакет, който според изискванията компилира драйвера от сорс кода, както много други драйвери…

Много печално, но тук не е така.

В пакета открих предварително компилирания двоичен файл vga2usb.o. Изучих го, докато обмислях възможностите на реверсивното инженерство, и намерих няколко интересни реда:

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

Тоест, това се оказа FPGA-on-a-stick? Как да накарам това чуди да работи?

Другото забавно, но и твърде тревожно откритие – параметрични редове с използването на затворения DSA ключ. Замислих се, какво толкова има да се защитава в един драйвер?

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

За да мога са изуча драйвера в неговата нормална среда, използвах виртуална машина в моята Debian 9 и направих KVM USB Passthrough, за да получа директен достъп до устройството. По-късно инсталирах драйвера и се убедих, че работи.

След това ми се поиска да прегледам какъв протокол на връзка се използва в този драйвер. Надеждата ми бе, че това устройство изпраща необработени или почти необработени фреймове, понеже това би улеснило написването на собствен драйвер за потребителското пространство.

За тази цел заредих в хоста на виртуалната машина модула usbmon и стартирах Wireshark, за да прихвана USB трафика на устройството както по време на неговото инициализиране, така и при прихващането на видеото от негова страна:

Забелязах, че при включването на устройството се обменят голям брой малки пакети с данни, преди да започне прихващането на видеото. Вероятно то наистина се базира на FPGA без собствено хранилище за данните. Виждам, че след всяко включване драйверът подава към устройството FPGA битстрийм.

И окончателно се убедих в това, след като отворих корпуса на едното устройство:

И така, ограденият с червено чип се оказа ISL98002CRZ-170, който работи като ADC за VGA сигналите. Ограденият с жълто чип е XC6SLX16 Xilinx Spartan 6 FPGA. Циановият е 64 MB DDR3 оперативна памет и с пурпурен цвят е обозначен USB контролера CY7C68013A.

За задействането на това устройство е необходимо да му бъде изпратен битстрийм за фърмуера и реших да го потърся в тези предварително компилирани двоични файлове. Стартирах binwalk -x и започнах да търся в компресираните със zlib обекти. За тази цел написах несложен скрипт за търсене последователности от hex числа, в който поставих три байта от прихванатия пакет с данни.

$ 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

След разкомпресирането на файла AA240.zlib се оказа, че там няма достатъчно данни за пълен битстрийм. Ето защо реших да се опитам да прихвана фърмуера от USB пакетите.

Изтеглянето на USB пакети от pcap файловете могат както tshark, така и tcpdump, но те ги записват частично. И тъй като всяка една от тези помощни програми дава различна част от тази главоблъсканица, написах неголяма програма, която обединява получените от тези програми данни в структура на go, за да мога самостоятелно да подам тези пакети на устройството.

В този момент забелязах, че зареждането на тези данни става на два етапа – първоначално данните за USB контролера, а след това за FPGA.

Тук заседнах за няколко дни – изглеждаше, че целият битстрийм се зарежда, но устройството не искаше да се стартира, въпреки че пакетите от реалния драйвер и от моята симулация изглеждаха съвсем еднакво.

В крайна сметка реших проблема след щателното изучаване на pcap, като внимателно се вгледах във времето на отговор на всеки пакет. Забелязах голяма разлика във времето на един конкретен пакет:

Оказа се, че заради един сгрешен символ записът се е осъществявал в неправилна област на устройството. Това ще ми е за урок да внимавам, когато въвеждам данните ръчно…

И ето че светодиода на устройството започна да мига! Огромен успех!

Бе сравнително лесно да репликирам същите пакети, които стартират обмена на данни, така че успях да намеря крайната точка USB Bulk и моментално да запиша данните на диск.

Тук започна да става наистина сложно. Данните се оказаха кодирани. В началото стартирах perf за да получа обща представа за трасирането на стека на драйверите по време на реалната работа:

Въпреки че успях да намеря функцията с данните на фреймовете, нямаше как да разбера как са кодирани тези данни.

За да мога по-добре да разбера какво се случва вътре в истинския драйвер дори използвах специалния инструмент Ghidra на АНС:

Въпреки че Ghidra е невероятна, което забелязах след като замених IDA Pro с нея, но все пак не е достатъчно добра, за ми помогне да разбера драйвера. За осъществяването на реверсивно инженерство явно трябваше да избера друг път.

Реших да сложа виртуална Windows 7 машина и да погледна какъв е драйверът за ОС Windows. И тогава забелязах, че за това устройство се предлага SDK. Един от инструментите в този пакет се оказа особено интересен:

PS> ls

Directory: epiphan_sdk-3.30.3.0007epiphanbin

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

Този инструмент дава възможност за отделянето на единичните фреймове, като хубавото е, че в началото те не се компресират, което дава възможност да се изследват с помощта на по-мощен компютър. Това е на практика идеално и аз копирах последователността на USB пакетите, за да получа тези коварни блобове, но без компресия чрез непознат алгоритъм. Броят на тези байтове съответстваше приблизително на на три (RGB) на пиксел.

Първоначалната обработка на тези изображения – просто чрез поемане на данните от входа и техния запис във вид на RGB пиксели, даде нещо, което напомня на реалното изображения, което устройството получава чрез VGA конектора:

След като прегледах тези байтове в hex редактор си изясних, че на всеки 1028 байта се повтаря някакъв маркер. Малко ме е срам, че изгубих толкова време за писането на филтъра за изображението. От друга страна, полученото изображение прилича на някои примери на най-съвременно изкуство:

Общо взето бързо разбрах, че наклонът и изкривяванията идват от изпускането на преноса на един пиксел на всеки ред. Досадно е, но забравих, че x=799 не е равно на x=800. Когато оправих това, получих правилното изображение, ако не броим цветовете:

Помислих си, че има проблем с калибрирането, когато видях толкова еднакви цветове. За да оправя това, създадох ново тестово изображение, за да видя какво ще се получи. Чак сега се сетих, че бих могъл да използвам нещо готово, като например тестовата картина Philips PM5544.

Заредих изображението в лаптопа и видях следната VGA картина:

Веднага си спомних, че преди време работих върху 3D рендиране и това много ми заприлича на цветовото кодиране YUV.

Прочетох основните материали по YUV и се сетих, че при използването реверсивното инженерство на драйвера, ако поставя точката на прекъсване точно на функцията с име v2ucom_convertI420toBGR24, то системата се срива без възможност за оправяне. Дали това не е I420 кодиране на входа, което идва от pix_fmt yuv420p, а изходът да е RGB?

След използването на вградената в програмния език Go функция YCbCrToRGB изображението внезапно стана почти същото като оригинала:

Успях! Наистина направих това! Дори първоначално сглобеният от мен драйвер даваше 7 кадъра в секунда. Честна казано, на мен това ми е достатъчно, понеже използвам VGA в случай че нещо се повреди и се наложи да използвам резервен дисплей.

И така, сега знаем достатъчно много за това устройство, за да обясним алгоритъма за неговото стартиране:

  1.  Необходимо е да бъде инициализиран USB контролера
  2. След като инициализацията на USB устройството приключи, устройството се изключва от USB шината и от време на време следи за появата на нови команди
  3. Сега вече може да бъде изпратен FPGA битстрийм – по един пакет от 64 байта
  4. След приключването на предаването на тези данни индикаторът на устройството започва да мига със зелен цвят. На този етап могат да бъдат изпратени необходимите параметри – overscan и другите свойства
  5. След това се изпраща контролния пакет за получаването на фрейм. Ако се изпрати команда за фрейм със съотношение на страните 4:3 към широкоекранен вход, това обикновено води до повреда на фрейма

За максимална простота при използването, в своя драйвер вмъкнах малък уеб сървър. Чрез браузърните MediaRecorder API той съвсем лесно записва потока от екрана във видео файл.

Веднага ще кажа, че не се гордея с написания от мен код. Но той става за приемливо използване.

Кодът и готовите компилации за Linux и OSX могат да бъдат изтеглени от GitHub.


Дори и тази програма никой никога да не стартира, за мен това бе изключително полезна работа – пътешествие в дебрите на USB протокола, работа с ядрото, реверсивно инженерство на сложен код и преглед на форматите за декодиране на видео. Научих много.

10
ДОБАВИ КОМЕНТАР

avatar
7 Коментари
3 Отговори на коментарите
1 Последователи
 
Коментарът с най-много реакции
Най-горещият коментар
  Абонирай се  
нови стари оценка
Извести ме за
Изидорус Б.
Изидорус Б.

Има технологично устройство – има проблем,
няма технологично устройство – няма проблем.

Нека се върнем към добрия стар материален свят и да зарежем дигиталния.

Виво
Виво

дайте статии тип „Физика за 6-ти клас“, че загубихте аудиторията
или нящо за Мъск…Тесла…

115
115

или пък за хуауей :)))

Тахтун
Тахтун

Интересна статия, описана изчерпателно. Но малко са интусиастите, които имат такива познания, за да разглеждат и пренаписват драйвери. Все пак благодаря на автора!

ллл
ллл

Този пък смешко, гидра била по добра от ида про. Затова и в АНС използват масово ида нали?

Димитър
Димитър

Това от стажа там ли го знаеш 😀

Димитър
Димитър

Това си го пропуснал малко, ходи по-често на стаж 😀
https://www.nsa.gov/resources/everyone/ghidra/

емил
емил

респект за заиграването

115
115

Човек без работа…

plamen
plamen

Има желание човека. Но е лош потребител, купува голяма бройка устройства без да проучи съвместимост.