我们为废弃设备编写USB驱动程序



最近在eBay上,我遇到了一批有趣的USB设备(Epiphan VGA2USB LR),它们可以接收VGA输入并将视频作为网络摄像头发送到USB。 我对自己再也不必为VGA显示器而烦恼的想法感到非常高兴,并且由于宣布了对Linux的支持,我借此机会以大约20英镑(25美元)的价格购买了整批显示器。

收到包裹后,我连接了设备,但它甚至都没有想到在系统中显示为UVC 。 怎么了

我研究了制造商的网站,发现需要一个特殊的驱动程序才能工作。 对我而言,这是一个新概念,因为Linux发行版的内核通常具有适用于所有设备的驱动程序。

不幸的是,对这些设备的驱动程序支持在Linux 4.9中终止。 因此,我的系统都看不到它(Linux 4.19上的Debian 10或Linux 5.0上的最新版本的LTS Ubuntu)。

但是可以解决,对吧? 当然,这些文件位于DKMS软件包中 ,该软件包根据需要从源代码中收集驱动程序,就像许多普通驱动程序一样...

真伤心 但事实并非如此。

软件包内部只有预编译的二进制vga2usb.o 。 我开始研究它,想知道反向工程的复杂性,发现了一些有趣的内容:

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

那么,真的是FPGA吗? 如何做这样的事情?

另一个有趣且有点令人不安的发现是带有DSA私钥参数的行。 这让我感到奇怪:它可以在驱动程序内部保护什么?

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

为了研究在正常环境下的驱动程序,我选择了Debian 9虚拟机(最新支持的发行版)并制作了KVM USB Passthrough以直接访问设备。 然后,我安装了驱动程序,并确保它可以工作。

在那之后,我想看看通信协议是什么样的。 我希望设备能够发送原始或几乎原始的帧,因为这样可以更轻松地为用户空间编写驱动程序。

为此,我在虚拟机usbmon上加载了usbmon模块,并启动了Wireshark以在启动和视频捕获期间捕获与设备之间的USB流量。



我发现启动该设备后,大量小数据包会在开始捕获图片之前先传输到该设备。 它可能基于没有数据存储的FPGA平台。 每次连接后,驱动程序都会以FPGA比特流的形式将固件传输到设备。

通过打开其中一个框使我深信不疑:



红色的

ISL98002CRZ-170-用作VGA信号的ADC

黄色

XC6SLX16-Xilinx Spartan 6 FPGA

青色

64 MB DDR3

洋红色

CY7C68013A-USB控制器/前端


由于要“下载”设备,您需要向其发送比特流/固件,因此必须在预编译的二进制文件中进行搜索。 我运行binwalk -x并开始寻找一些压缩对象(zlib)。 为此,我编写了一个十六进制序列搜索脚本-并从截获的数据包中指定了三个字节。

 $ 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软件包中获取固件。

tsharktcpdump都可以从pcap文件读取 USB数据包,但都只能部分保存它们。 由于每个实用程序都有不同的谜题,因此我编写了一个小程序 ,将这两个程序的输出组合到go结构中,以便将数据包播放回设备。

至此,我注意到下载过程分为两个阶段:首先是USB控制器,然后是FPGA。

我被困了几天:似乎整个比特流都在加载,但是设备没有启动,尽管来自真实驱动程序和我的模拟的程序包看起来很相似。

最后,我通过仔细检查pcap并考虑到每个数据包的响应时间来解决了这个问题-并注意到在一个特定的程序包中存在很大的时间差异:



事实证明,由于打字错误,记录发生在设备的错误区域。 这将是我的课程,如何手动输入值...

但是,LED最终在设备上闪烁! 巨大的成就!


复制触发数据传输的相同软件包相对容易,因此我可以编写USB Bulk端点并将数据立即刷新到磁盘!

这是真正的困难开始的地方。 因为经过分析,结果表明数据没有以任何方式进行显式编码。

首先,我运行perf以获得运行时驱动程序堆栈跟踪的基本概念:



尽管我可以使用帧数据捕获函数,但是我无法理解数据本身的编码。



为了更好地了解实际驱动程序内部发生了什么,我什至尝试了NSA的Ghidra工具:



尽管Ghidra令人难以置信(当我初次使用它而不是IDA Pro时),但它仍然不足以帮助我理解驱动程序。 逆向工程需要一条不同的道路。

我决定拿起Windows 7虚拟机并看一看Windows驱动程序,突然间它将产生一些想法。 然后我注意到有一个用于设备的SDK。 其中一种工具特别有趣:

 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 

该工具允许您“抓取”单个帧,并且最初它们不会被压缩,因此以后可以在更快的计算机上处​​理帧。 这几乎是完美的,我复制了USB数据包序列以获取这些未压缩的Blob。 字节数大约相当于每个像素三个(RGB)!

这些图像的初始处理(仅接受输出并将其写入为RGB像素)就使人联想到设备通过VGA接收到的真实图片:



在十六进制编辑器中进行了一些调试后,结果发现每个标记每1028个字节重复一次。 我花了多少时间编写过滤器有点尴尬。 另一方面,在此过程中,您可以欣赏当代艺术的一些实例。



然后,我意识到图像的倾斜和变形是由每行上的跳过和像素环绕引起的(x = 799不等于x = 800)。 最后,除了颜色,我得到了几乎正确的图像:



起初,我认为校准问题是由于VGA输入卡在纯色时导致的数据采样所致。 为了更正,我制作了一个新的测试图像以识别此类问题。 事后看来,我了解到您必须使用飞利浦PM5544测试卡之东西。



我将图像上传到笔记本电脑,并产生了这样的VGA图片:



然后,我得到了3D渲染/着色器中一些旧工作的记忆。 它与YUV配色非常相似。

结果,我沉迷于阅读YUV文献,并想起了在对官方内核驱动程序进行反向工程的过程中,如果在名为v2ucom_convertI420toBGR24的函数上设置了断点,则系统将崩溃而无法更新。 因此,也许输入是I420编码(来自-pix_fmt yuv420p ),而输出是RGB?

使用内置的Go函数YCbCrToRGB后,图像突然变得更接近原始图像。



我们做到了! 甚至原始驱动程序也每秒产生7帧。 老实说,这对我来说就足够了,因为我仅在发生事故时才使用VGA作为备用显示器。

因此,现在我们已经足够了解此设备,可以从一开始就说明启动它的算法:

  1. 您需要初始化USB控制器 。 从信息量来看,实际上,驱动程序将代码传递给它进行下载。
  2. 当您完成USB的加载后,设备将断开与USB总线的连接,稍后将返回一个USB端点。
  3. 现在,您可以发送FPGA比特流 ,每次控制传输一个64字节USB数据包。
  4. 传输结束时,设备上的指示灯将闪烁绿色。 在这一点上,您可以发送看似一系列的参数(过扫描和其他属性)。
  5. 然后运行控制程序包以获取frame ,程序包指定的权限。 如果您向宽屏输入发送4:3帧的请求,通常会导致帧损坏。

为了最大程度地易于使用,我在驱动程序中实现了一个小型Web服务器。 通过基于浏览器的MediaRecorder API,它可以轻松地记录从屏幕到视频文件的流。



为了避免不可避免地对实验代码的质量提出要求,我将立即说:我并不为此感到骄傲。 可能他处于这种状态,这足以让我接受使用。

用于Linux和OSX的代码和现成的版本位于GitHub上

即使没有人启动该程序,对我来说,这也是经历USB协议狂野,调试内核,对模块和视频解码格式进行反向工程的激动人心的旅程! 如果您喜欢这些东西,可以查看其他博客文章

Source: https://habr.com/ru/post/zh-CN476434/


All Articles