如果你曾经将iPhone、iPad或iPod连接到Windows PC上,你可能会注意到,这些设备会根据你的操作显示为不同类型的设备。例如,如果你正在给iPhone充电,它可能会显示为“USB复合设备”,但如果你正在与iTunes同步音乐,它可能会显示为“苹果移动设备USB驱动程序”。你有没有想过这是怎么回事?事实证明,苹果在Windows电脑上有一个USB低级过滤器,可以帮助他们控制操作系统使用哪些USB配置。
本文中,我们会首先介绍苹果的USB低级过滤器是如何工作的,它是做什么,以及无论是否安装了苹果软件,它如何提供不同的体验;其次,我们将研究为什么当设备的WPD属性WPD_DEVICE_PROTOCOL表明设备正在使用媒体传输协议(MTP)时,iphone的开箱操作如此有限。我们将深入研究诸如Windows便携式设备(WPD),USB描述符和用户模式驱动程序框架(UMDF)等话题。
苹果设备将自己呈现为具有多个接口的复合设备,以确保它们的设备被正确识别,并加载所有必要的驱动程序。这是因为苹果设备通常有多个接口,提供不同的功能,如音频、视频和控制。当我们将苹果设备插入Windows设备时,总线适配器识别设备并向操作系统提供其hardwareid和compatibleid。这些id用于根据id的匹配质量在driver Store中搜索最佳驱动程序。
对于总线驱动器来说,要将该设备视为复合设备,必须满足一定的要求。如果不满足这些要求,操作系统将不会自动加载USB复合设备类驱动程序(usbccgp)。在这种情况下,我们需要提供一个INF来加载通用的父驱动程序,对于苹果来说是文件AppleUSB.inf。
在iPhone的情况下,不满足的要求是设备具有多个配置(bNumConfigurations == 4)。
这个INF文件包含不同设备的各种设置配置(例如AppleUSB, AppleUsbHomePod和AppleUsbWatch)。对于iOS设备,HardwareId将完全匹配,因此操作系统将应用AppleUSB设置配置,这将复制AppleLowerFilter.sys,并将在设备特定的注册表项下添加以下值:
OriginalConfigurationValue是一个可以在设备的硬件注册表项中为Usbccgp.sys驱动程序设置的值。它确定复合设备的哪个配置应用作默认配置。首次插入复合设备时,系统读取OriginalConfigurationValue并加载指定的配置。这对于具有多个配置的复合设备非常有用,其中一个配置可能是首选配置。
安装驱动程序包后,微软将详细说明以下步骤。设备将重新启动,重启后,PnP管理器识别设备的功能驱动程序和任何可选的过滤器驱动程序,构建设备堆栈,在该样本中,FDO是Usbccgp和LowerFiDO是AppleLowerFilter,并通过调用DriverEntry例程启动设备,为任何尚未加载的所需驱动程序。然后为每个驱动程序调用AddDevice例程,从低过滤驱动程序开始,然后是函数驱动程序。如果需要,将分配资源,PnP管理器将IRP_MN_START_DEVICE发送给设备的驱动程序。
USB低级过滤器工作原理
介绍了AppleLowerFilter的枚举和安装背后的理论之后,我们现在将仔细研究驱动程序是如何工作的,以及它在Windows设备上启用Apple设备功能时所起的作用。
作为一个WDF驱动程序,PnP调用DriverEntry的第一步是初始化框架并绑定WDF版本(在本例中为WDF 1.15)。一旦完成,框架将调用我们的DriverEntry函数,在AppleLowerFilter的情况下,他们的驱动项将简单地创建一个驱动对象,并在WDF_DRIVER_CONFIG中只设置一个AddDevice例程。AppleLowerFilter的AddDevice例程将进行如下操作:
1.通过调用WdfFdoInitSetFilter将自己标识为FiDO;
2.为事件注册PnP和电源管理回调:
EvtDevicePrepareHardware;
EvtDeviceReleaseHardware;
EvtDeviceD0Entry;
EvtDeviceD0Exit;
3.为IRP设置两个IRP预处理回调:
IRP_MJ_PNP;
IRP_MJ_INTERNAL_DEVICE_CONTROL;
4.使用名为FILTER_EXTENSION(sizeof==0x50)的上下文类型信息创建一个DO;
本文不深入讨论WDF框架的所有细节,但鼓励每个人都深入研究Github上的源代码。它是一个设计良好的软件,使编写驱动程序变得更加容易和直观,因此研究代码是一个很好的练习。
在上电序列( power-up sequence)中,下一步是为上电准备硬件,这意味着调用过滤器注册的EvtDevicePrepareHardware回调。这可能是AppleLowerFilter中最有趣的步骤。
Callback的第一步是检索USB描述符,这是通过被称为GetUsbDeviceDescriptor的函数完成的。此函数用于检索USB设备的USB设备描述符。这是通过为URB (USB请求块)分配内存并使用URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE类型完成的,这是一个从USB设备检索描述符的请求。被请求的描述符是USB_DEVICE_DESCRIPTOR_TYPE,它提供有关USB设备的信息,如其供应商和产品id、设备类和协议。该函数同步提交URB以检索描述符。
对于大多数与USB相关的操作,苹果使用usbdlib,这有点令人不可思议,因为这是一个WDF驱动程序,他们可以使用wdfusb标头,简化事情。
然后,驱动程序将bNumConfigurations存储到FILTER_EXTENSION上下文中,并继续调用我认为是该驱动程序的主要函数GetPreferredConfig。在描述其内部结构之前,先看一下这个函数的简单伪代码:
该函数将首先检查设备的配置数是否为1。如果是,则将首选配置设置为1;如果没有,代码将发送一个URB来检索首选配置;如果URB请求失败,则首选配置为3。然后代码在FilterCtx结构中设置DeviceConfig字段。然后,它检查QueryAppleSoftwarePresent函数是否返回false(表示未安装AppleSoftware),如果是,则将首选配置设置为1。然后,代码检查首选配置是否大于或等于设备描述符中指定的配置数;如果是,则将首选配置设置为最大配置数。最后,如果首选配置大于或等于5,则代码返回5。
在这个函数中有几个关键点,首先突出的是getdeviceconfigure函数,该函数将为URB分配内存,设置URB以发出特定于供应商的控制请求,然后将URB同步提交给USB驱动程序堆栈。正在进行的特定供应商请求是请求类型为69的控制传输和包含一个字节数据的传输缓冲区。
选择首选配置的下一个关键点是QueryAppleSoftwarePresent函数,这个函数起着很大的作用,因为它的返回值将决定我们是否总是被限制为只有一个首选配置。这个函数将做以下事情:
回到GetPreferredConfig函数,这个值起着很大的作用,因为这个函数返回的数字将用于覆盖设备注册表项中的OriginalConfigurationValue。
注意:GetPreferredConfig返回的值将被减去1,因为OriginalConfigurationValue描述的注册表值对应于usb定义的配置索引,由配置描述符(USB_CONFIGURATION_DESCRIPTOR)的bConfigurationValue成员指示,而不是由设备配置描述符中报告的bConfigurationNum值表示。
我们刚刚看到的是为什么即使INF在OriginalConfigurationValue中写入值2,如果你将iPhone插入没有安装iTunes的PC,你会在注册表中看到以下内容:
在注册表中设置OriginalConfigurationValue之后,EvtDevicePrepareHardware函数将调用wdfusbtargetdevicecrecreate来创建USB目标设备对象。USB目标设备对象表示底层USB设备,并为驱动程序与设备通信提供了一种方式。
为了定期检查首选配置,该函数设置了一个WDFTIMER。计时器的回调函数将被定期调用,以检查首选配置是否已更改。如果首选配置已更改,则函数将调用WdfUsbTargetDeviceCyclePortSynchronously,以便意外删除并重新枚举设备,从而使用新配置进行加载。
计时器的周期设置为0,因此框架不会调用计时器。另一方面,过滤器将在计时器的回调函数和D0Entry回调中调用WdfTimerStart, DueTime为5s(相对于当前系统时间)。
现在让我们看看这两个IRP预处理回调,以获得驱动程序工作流的全貌。IRP的预处理允许驱动程序在将IRP发送到默认处理程序或堆栈中的另一个驱动程序之前修改或重定向IRP。
首先看一下内部设备控制请求的处理程序。该函数将检查IRP是否为IOCTL_INTERNAL_USB_SUBMIT_URB请求,以选择USB配置。如果是,函数将获得设备的句柄,转发IRP,然后检索USB接口的管道句柄。Interrupt、BulkIn和BulkOut的管道句柄将存储在设备上下文中。
现在让我们看一下PnP IRPs预处理的处理程序。在本文的示例中,句柄将处理两种情况:IRP_MN_QUERY_DEVICE_RELATIONS 和IRP_MN_QUERY_ID。
QueryID IRP的情况非常简单,函数将检查FilterCtx->DeviceConfig(记住这个值是通过供应商特定的URB获得的)是否被设置为1,如果是,函数将字符串&RESTORE_MODE附加到BusQueryHardwareIDs和BusQueryDeviceID请求返回的信息中。
另一方面,querydevicerrelation更有趣一些。首先,这个处理程序只会在某个计时器没有运行并且设备上安装了Apple Software的情况下执行。它将只处理BusRelations IRP,它将同步转发请求并检查状态是否成功。如果返回任何信息,它将在返回的设备对象列表中查找其CompatibleId包含USB\Class_06的设备。如果找到了,它会取消对这个DO的引用,然后将其从列表中删除并更新设备计数,这样即使usbccgp为WPD设备创建了PDO, PnP也不会看到这个DO,因为返回的列表中没有它。不久,我们将看到低级过滤器如何处理这一点。
如果找到了类6的设备,那么该函数将根据DbgPrints设置另一个WDF定时器,我们将其称为PtpTimer,它在5秒后被触发。当触发回调时,将在deviceContext中设置一个标志,因此QueryDeviceRelations处理程序不再处理请求,将检查iTunes是否存在,如果存在,它将发送以下一组PTP/MTP操作请求包到USB设备。
OpenSession - OperationCode: 0x1002;
vendoreextensionoperationcode: 0x9008;
CloseSession - OperationCode: 0x1003;
下面的USB数据包捕获说明了这些操作的执行,注意它们是如何在该端口上捕获大约5秒后发生的。
尽管尝试了所有的手段来获得操作0x9008的更多信息,但似乎没有任何关于它的苹果设备的信息。所能得到的最好结果是ChatGPT说“PTP/MTP数据包中的操作命令0x9008通常对应于“Apple Device Info”命令”。不幸的是,当要求提供证明这一点的文档/引用时,而聊天给到的每个链接要么是无效的,要么是不可用的/废弃的苹果文档。给定名称“苹果设备信息”,笔者认为它类似于PTP/MTP命令“GetDeviceInfo”,但在设备命令0x9008上尝试的每个测试似乎都没有数据阶段,所以最好的猜测是,要么不是“设备信息”命令,要么苹果设备不再响应该命令。
最后,在发送PTP/MTP请求后,PtpTimer将调用IoInvalidateDeviceRelations,其关系类型为BusRelation,这将触发一个新的IRP QueryDeviceRelations,但由于这次计时器已经执行,处理程序不会从设备列表中删除WPD设备。这次PnP管理器我们会看到WPD设备的PDO并开始为它构建堆栈。下图显示了通过将LowerFilter添加到堆栈中并跟踪Pre和Post捕获的这种行为,IRP由AppleLowerFilter处理。
目前猜测是带有operationCode 0x9008的PTP包以某种方式,通知设备iTunes存在于主机上或这些行周围的东西。除此之外,没有注意到WPD设备在安装iTunes或没有安装iTunes的情况下有任何不同的行为,除了WPD设备实际显示需要5秒钟。从设备的LowerFilters列表中删除AppleLowerFilter似乎对WPD设备的行为没有任何重大影响。
这几乎就是AppleLowerFilter的行为方式,可以看到它主要在设备初始化期间工作,除了检查活动配置的计时器每5秒在后台运行一次之外,查看端口时必须重新举例。
参考及来源:https://n4r1b.com/posts/2023/03/the-intersection-of-apples-usb-lower-filter-and-iphone-wpd-integration/